Помимо as-pattern, что еще может означать @ в Haskell?

15

В настоящее время я изучаю Haskell и пытаюсь понять проект, который использует Haskell для реализации криптографических алгоритмов. После того, как я прочел в Интернете « Изучаю тебя на гаскелле для хорошего блага» , я начал понимать код этого проекта. Затем я обнаружил, что застрял в следующем коде с символом «@»:

-- | Generate an @n@-dimensional secret key over @rq@.
genKey :: forall rq rnd n . (MonadRandom rnd, Random rq, Reflects n Int)
       => rnd (PRFKey n rq)
genKey = fmap Key $ randomMtx 1 $ value @n

Здесь randomMtx определяется следующим образом:

-- | A random matrix having a given number of rows and columns.
randomMtx :: (MonadRandom rnd, Random a) => Int -> Int -> rnd (Matrix a)
randomMtx r c = M.fromList r c <$> replicateM (r*c) getRandom

И PRFKey определяется ниже:

-- | A PRF secret key of dimension @n@ over ring @a@.
newtype PRFKey n a = Key { key :: Matrix a }

Все источники информации, которые я могу найти, говорят, что @ является шаблоном as, но этот фрагмент кода явно не тот случай. Я проверил онлайн-учебник, блоги и даже языковой отчет по Haskell 2010 по адресу https://www.haskell.org/definition/haskell2010.pdf . На этот вопрос просто нет ответа.

Другие фрагменты кода можно найти в этом проекте, используя @ также следующим образом:

-- | Generate public parameters (\( \mathbf{A}_0 \) and \(
-- \mathbf{A}_1 \)) for @n@-dimensional secret keys over a ring @rq@
-- for gadget indicated by @gad@.
genParams :: forall gad rq rnd n .
            (MonadRandom rnd, Random rq, Reflects n Int, Gadget gad rq)
          => rnd (PRFParams n gad rq)
genParams = let len = length $ gadget @gad @rq
                n   = value @n
            in Params <$> (randomMtx n (n*len)) <*> (randomMtx n (n*len))

Я глубоко признателен за любую помощь в этом.

SigurdW
источник
11
Это типовые приложения . Смотрите также этот Q & A . Вы также можете посмотреть коммит, который ввел их в код.
MikaelF
Большое спасибо за ссылки! Это именно то, что я ищу. Удивительно, но вы даже идентифицируете коммит кода! Большое спасибо за это. Просто интересно, как ты это нашел? @MikaelF
SigurdW
2
Github имеет собственный интерфейс для git blame , который сообщит вам, в каком коммите каждая строка была изменена последней.
MikaelF
Большое спасибо за этот полезный совет :)
SigurdW
1
@MichaelLitchard Очень рад, что вы можете извлечь из этого пользу. Я благодарен добрым людям, которые проводят время, чтобы помочь мне. Надеюсь, что ответ может помочь другим.
SigurdW

Ответы:

17

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

чи
источник
Большое спасибо за ваше подробное объяснение! Это действительно решает мою проблему, и я думаю, мне нужно гораздо больше времени, чтобы найти ответ самому. Также спасибо за ваше предложение!
SigurdW