Haskell: Typeclass проти передачі функції


16

Мені здається, що ви завжди можете передавати аргументи функцій, а не використовувати клас. Наприклад, замість визначення класу рівності:

class Eq a where 
  (==)                  :: a -> a -> Bool

І використовувати його в інших функціях для вказівки аргументу типу повинно бути екземпляром Eq:

elem                    :: (Eq a) => a -> [a] -> Bool

Чи не можемо ми просто визначити нашу elemфункцію, не використовуючи тип класу, а замість цього передати аргумент функції, який виконує завдання?


2
це називається проходження словника. Ви можете вважати обмеження typeclass як неявні аргументи.
Поскат

2
Можна це зробити, але, очевидно, набагато зручніше не потрібно передавати функцію, а просто використовувати її "стандартну" залежно від типу.
Робін Зігмонд

2
Можна так сказати, так. Але я заперечую, що є хоча б одна важлива перевага: можливість запису поліморфних функцій, які працюють на будь-якому типі, який реалізує певний "інтерфейс" або набір функцій. Я думаю, обмеження типу typeclass виражають це дуже чітко таким чином, що передача додаткових аргументів функції не робить. Зокрема, через (сумно неявні) "закони", яким мають задовольнятися багато типів класів. Monad mОбмеження говорить для мене більше , ніж передачі додаткових аргументів функції типів a -> m aі m a -> (a -> m b) -> m b.
Робін Зігмонд


1
TypeApplicationsРозширення дозволяє зробити неявний аргумент явним. (==) @Int 3 5порівнює 3та 5конкретно як Intзначення. Ви можете вважати @Intключовим словником функцій рівності, що стосуються певного типу, а не Intсамою спеціальною функцією порівняння.
чепнер

Відповіді:


19

Так. Це називається "стиль проходження словника". Іноді, коли я роблю якісь особливо складні речі, мені потрібно зібрати клавіатурний клас і перетворити його на словник, оскільки передача словника є більш потужною 1 , але часто досить громіздкою, тому концептуально простий код виглядає досить складно. Я іноді використовую стиль передачі словника іноді на мовах, які не є Haskell, для імітації типів класів (але я дізнався, що це зазвичай не настільки чудова ідея, як це звучить).

Звичайно, щоразу, коли є різниця в виражальній силі, відбувається компроміс. Хоча ви можете використовувати даний API більше способів, якщо він написаний за допомогою DPS, API отримує більше інформації, якщо ви не можете. Один із способів цього виявляється на практиці: це Data.Setспирається на те, що існує лише один Ordсловник на тип. У Setмагазинах його елементи упорядковано в відповідно до Ord, і якщо ви будуєте набір з одного словника, а потім вставити елемент , використовуючи іншу, як це було б можливо з DPS, ви можете розбити Set«s інваріантної і привести до аварії. Цю унікальну проблему можна пом'якшити за допомогою фантомного екзистенціалувведіть для позначення словника, але, знову ж таки, ціною досить прикрої складності в API. Це також відображається приблизно так само в TypeableAPI.

Біт унікальності з'являється не дуже часто. У яких класних класах відмінно входить, - це написання коду для вас. Наприклад,

catProcs :: (i -> Maybe String) -> (i -> Maybe String) -> (i -> Maybe String)
catProcs f g = f <> g

який займає два "процесори", які беруть вхід і можуть дати вихід, і об'єднуючи їх, вирівнюючи Nothing, потрібно було б записати в DPS приблизно так:

catProcs f g = (<>) (funcSemi (maybeSemi listSemi)) f g

По суті, нам довелося прописати тип, який ми використовуємо заново, хоча ми вже прописали його в підписі типу, і навіть це було зайвим, оскільки компілятор вже знає всі типи. Оскільки існує лише один спосіб побудувати заданий Semigroupтип, компілятор може зробити це за вас. Це має ефект типу "складений інтерес", коли ви починаєте визначати велику кількість параметричних екземплярів і використовуєте структуру своїх типів для обчислення для вас, як у Data.Functor.*комбінаторах, і це використовується для найкращого ефекту, deriving viaколи ви зможете отримати все "стандартна" алгебраїчна структура вашого типу, написана для вас.

І навіть не запускайте мене на MPTC та фундаменти, які повертають інформацію назад у перевірку типу та висновок. Я ніколи не намагався перетворити таку річ у DPS - я підозрюю, що це передбачає проходження багатьох доказів рівності типу - але в будь-якому випадку я впевнений, що це могло б набагато більше працювати для мого мозку, ніж мені було б зручно з.

-

1 U nless використовувати reflectionв цьому випадку вони стають еквівалентними в силі - але reflectionтакож може бути обтяжливим для використання.



Мене дуже цікавлять фундаменти, висловлені через DPS. Чи знаєте ви деякі рекомендовані ресурси на цю тему? У всякому разі, дуже зрозуміле пояснення.
боб

@bob, не від руки, але це було б цікаве дослідження. Може, задати нове запитання про це?
luqui

5

Так. Це (називається передача словника) - це в основному те, що компілятор робить для набору класів. Для цієї функції, виконаної буквально, це виглядало б приблизно так:

elemBy :: (a -> a -> Bool) -> a -> [a] -> Bool
elemBy _ _ [] = False
elemBy eq x (y:ys) = eq x y || elemBy eq x ys

elemBy (==) x xsЗараз дзвінки еквівалентні elem x xs. І в цьому конкретному випадку ви можете піти на крок далі: eqкожен раз має один і той же перший аргумент, тож ви можете покласти на нього відповідальність за те, щоб той, хто телефонує, застосував це, і закінчити це:

elemBy2 :: (a -> Bool) -> [a] -> Bool
elemBy2 _ [] = False
elemBy2 eqx (y:ys) = eqx y || elemBy2 eqx ys

elemBy2 (x ==) xsЗараз дзвінки еквівалентні elem x xs.

...Зачекайте. Це просто any. (А насправді в стандартній бібліотеціelem = any . (==) .)


Проходження словника AFAIU - це підхід Скали до кодування класів типів. Ці додаткові аргументи можна оголосити як, implicitі компілятор введе їх для вас із сфери застосування.
мічід
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.