18

我目前正在学习 Haskell,并试图了解一个使用 Haskell 实现加密算法的项目。在在线阅读Learn You a 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 }

我能找到的所有信息源都说 @ 是 as-pattern,但这段代码显然不是那种情况。我查看了https://www.haskell.org/definition/haskell2010.pdf上的在线教程、博客甚至Haskell 2010 语言报告。这个问题根本没有答案。

在这个项目中也可以通过这种方式使用 @ 找到更多代码片段:

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

我非常感谢这方面的任何帮助。

4

1 回答 1

16

@n是现代 Haskell 的一项高级功能,通常不会在 LYAH 之类的教程中涵盖,也无法在报告中找到。

它被称为类型应用程序,是一种 GHC 语言扩展。为了理解它,考虑这个简单的多态函数

dup :: forall a . a -> (a, a)
dup x = (x, x)

直观的调用dup工作如下:

  • 调用者选择一种类型 a
  • 调用者选择先前选择的类型的 xa
  • dup然后以 type 的值回答(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代表一种自然数值。在这里,我们正在深入研究相当高级的扩展,比如上面提到的 plus 、 maybe和一些类型类机器。虽然我不能解释一切,但希望我能提供一些基本的见解。直觉上,DataKindsGADTs

foo :: forall n . some type using n

接受作为参数@n,一种编译时自然的,在运行时不传递。反而,

foo :: forall n . C n => some type using n

需要@n(编译时),以及满足约束的证明。后者是一个运行时参数,它可能会暴露. 确实,在你的情况下,我猜你有一些模糊的相似之处nC nn

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哪个更好,但即使在nis not时仍然允许调用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 :-)

于 2020-04-28T16:30:42.470 回答