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