Окрім as-pattern, що ще може означати @ у Haskell?


15

Зараз я вивчаю Haskell і намагаюся зрозуміти проект, який використовує Haskell для реалізації криптографічних алгоритмів. Прочитавши « Learn You Haskell for Great Good» в Інтернеті, я починаю розуміти код у цьому проекті. Потім я виявив, що я застряг у наступному коді із символом "@":

-- | 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 }

Усі джерела інформації, які я можу знайти, говорять про те, що @ є типовою схемою, але цей фрагмент коду, мабуть, не так. Я перевірив онлайн-підручник, блоги та навіть звіт про мову 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))

Я дуже вдячний за будь-яку допомогу з цього приводу.


11
Це додатки типу . Дивіться також це питання . Ви також можете переглянути комісію, яка ввела їх у код.
MikaelF

Дякую за посилання! Це саме те, що я шукаю. Дивно, але ви навіть ідентифікуєте фіксацію коду! Велике спасибі за це. Просто цікаво, як ви її знайдете? @MikaelF
SigurdW

2
У Github є власний інтерфейс для git blame , який підкаже вам, в якій комісії кожен рядок востаннє змінено.
MikaelF

Велике спасибі за цю корисну пораду :)
SigurdW

1
@MichaelLitchard Дуже радий, що ти можеш отримати від цього користь. Я вдячний добрим людям, які витрачають час, щоб допомогти мені. Сподіваюся, що відповідь також може допомогти іншим.
SigurdW

Відповіді:


16

Це @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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.