除了模式外,@在Haskell中还有什么意思?


15

我目前正在研究Haskell,并尝试了解一个使用Haskell实施密码算法的项目。在在线阅读向您了解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 }

我可以找到的所有信息源都说@是按样式排列,但是这段代码显然不是这种情况。我在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))

我对此深表感谢。


11
这些是类型应用程序。另请参阅此问答。您还可以查看将它们引入代码中的提交
MikaelF

非常感谢您的链接!这些正是我想要的。令人惊讶的是,您甚至可以确定代码的提交!非常感谢。只是好奇如何找到它?@MikaelF
SigurdW

2
Github拥有自己的git blame界面,它将告诉您上一行修改的提交位置。
MikaelF

非常感谢这个有用的提示:)
SigurdW

1
@MichaelLitchard很高兴您可以从中受益。我很感激善良的人们花时间帮助我。希望答案也能帮助其他人。
SigurdW

Answers:


16

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

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

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

直观地调用dup如下:

  • 所述呼叫者选择一个类型 a
  • 所述呼叫者选择一个 x的先前选择的类型的a
  • dup 然后用类型值回答 (a,a)

从某种意义上说,dup有两个参数:type a和value 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 ,也许以及某些类型类的机器。尽管我无法解释所有内容,但希望我能提供一些基本见解。凭直觉DataKindsGADTs

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

防止出现问题。编译器将静态检查所有内容。是的,代码更难使用,但从某种意义上讲,这更难于使用是要点:我们希望使用户无法尝试添加错误的模数。

结论:这些是非常高级的扩展。如果您是初学者,则需要慢慢地朝着这些技术发展。如果只经过短暂的学习就不能抓住它们,不要气disc,这会花费一些时间。一次走一小步,为每个功能解决一些练习以了解其要点。当您被卡住时,您将始终拥有StackOverflow :-)


非常感谢您的详细解释!它确实解决了我的问题,我想我需要更多时间自己找到答案。也感谢您的建议!
SigurdW
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.