Это @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, когда вы застряли :-)