Це @n
вдосконалена особливість сучасного Haskell, який, як правило, не охоплюється навчальними посібниками на зразок LYAH, а також не можна знайти Звіт.
Це називається додаток типу та є розширенням мови GHC. Щоб зрозуміти це, розглянемо цю просту поліморфну функцію
dup :: forall a . a -> (a, a)
dup x = (x, x)
Інтуїтивно виклик dup
працює так:
- абонент вибирає тип
a
- абонент вибирає значення
x
з раніше обраного типуa
dup
то відповіді зі значенням типу (a,a)
У певному сенсі dup
приймає два аргументи: тип a
і значення x :: a
. Однак GHC, як правило, здатний зробити висновок про тип a
(наприклад x
, з контексту, в якому ми використовуємо dup
), тому ми зазвичай передаємо лише один аргумент dup
, а саме x
. Наприклад, у нас є
dup True :: (Bool, Bool)
dup "hello" :: (String, String)
...
А що робити, якщо ми хочемо пройти a
явно? Ну, у такому випадку ми можемо включити TypeApplications
розширення та написати
dup @Bool True :: (Bool, Bool)
dup @String "hello" :: (String, String)
...
Зверніть увагу на @...
аргументи, що несуть типи (не значення). Це те, що існує під час компіляції, лише - під час виконання аргументу не існує.
Чому ми цього хочемо? Ну, іноді немає x
навколо, і ми хочемо запропонувати компілятору вибрати правильний a
. Напр
dup @Bool :: Bool -> (Bool, Bool)
dup @String :: String -> (String, String)
...
Програми типу часто корисні в поєднанні з деякими іншими розширеннями, які роблять висновок про тип нездійсненним для GHC, як, наприклад, неоднозначні типи або сімейства типів. Я не буду обговорювати це, але ви можете просто зрозуміти, що іноді вам справді потрібно допомогти компілятору, особливо при використанні потужних функцій рівня типу.
Тепер про ваш конкретний випадок. У мене немає всіх деталей, я не знаю бібліотеки, але дуже ймовірно, що ваш n
представляє якесь значення природного числа на рівні типу . Тут ми занурюємось у досить просунуті розширення, як, наприклад, вищезгадані плюс DataKinds
, можливо GADTs
, і деякі типові машини. Хоча я не можу все пояснити, сподіваюся, я можу дати деяке основне розуміння. Інтуїтивно,
foo :: forall n . some type using n
В якості аргументу береться @n
природний час компіляції, який не передається під час виконання. Натомість
foo :: forall n . C n => some type using n
приймає @n
(час збирання) разом із доказом, що n
задовольняє обмеження C n
. Останній є аргументом часу виконання, який може викрити фактичне значення n
. Дійсно, у вашому випадку, я думаю, у вас щось нечітко нагадує
value :: forall n . Reflects n Int => Int
що по суті дозволяє коду привести природний рівень до рівня терміна, по суті отримуючи доступ до "типу" як "значення". (Вищенаведений тип, до речі, вважається "неоднозначним" - вам дійсно потрібно @n
роз'єднуватись.)
Нарешті: навіщо нам хотіти переходити n
на рівні типу, якщо потім перетворимо це на рівень терміна? Не було б простіше просто виписати такі функції, як
foo :: Int -> ...
foo n ... = ... use n
замість більш громіздких
foo :: forall n . Reflects n Int => ...
foo ... = ... use (value @n)
Чесна відповідь: так, було б простіше. Однак наявність n
на рівні типу дозволяє компілятору проводити більше статичних перевірок. Наприклад, ви можете захотіти, щоб тип представляв "цілий модуль n
", і дозволити їх додавати. Маючи
data Mod = Mod Int -- Int modulo some n
foo :: Int -> Mod -> Mod -> Mod
foo n (Mod x) (Mod y) = Mod ((x+y) `mod` n)
працює, але немає перевірки на те, x
і y
вони однакового модуля. Ми можемо додати яблука та апельсини, якщо не будемо обережні. Ми могли замість цього написати
data Mod n = Mod Int -- Int modulo n
foo :: Int -> Mod n -> Mod n -> Mod n
foo n (Mod x) (Mod y) = Mod ((x+y) `mod` n)
що краще, але все ж дозволяє телефонувати foo 5 x y
навіть тоді, коли n
ні 5
. Не добре. Натомість
data Mod n = Mod Int -- Int modulo n
-- a lot of type machinery omitted here
foo :: forall n . SomeConstraint n => Mod n -> Mod n -> Mod n
foo (Mod x) (Mod y) = Mod ((x+y) `mod` (value @n))
заважає, щоб справи пішли не так. Компілятор статично перевіряє все. Код важче використовувати, так, але в певному сенсі, що ускладнює його використання, вся суть: ми хочемо зробити неможливим для користувача, щоб спробувати додати щось неправильного модуля.
Висновок: це дуже розширені розширення. Якщо ви новачок, вам потрібно буде повільно просуватися до цих методів. Не відволікайтеся, якщо ви не можете зрозуміти їх лише після короткого вивчення, це займе певний час. Зробіть невеликий крок за один раз, вирішіть кілька вправ для кожної функції, щоб зрозуміти суть її. І у вас завжди буде StackOverflow, коли ви застрягли :-)