除了as-pattern,@在Haskell中还有什么意思?
Besides as-pattern, what else can @ mean in Haskell?
我目前正在研究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,但是这段代码显然不是这样。我查看了在线教程、博客,甚至 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))
非常感谢对此的任何帮助。
@n
是现代 Haskell 的高级功能,LYAH 之类的教程通常不会涵盖它,也不会在报告中找到它。
它被称为 type application 并且是 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
这实质上允许代码将类型级自然带到术语级,本质上是将 "type" 作为 "value" 访问。 (顺便说一下,上面的类型被认为是 "ambiguous" 类型——你确实需要 @n
来消除歧义。)
最后:如果我们稍后将其转换为术语级别,为什么还要在类型级别传递 n
?简单地写出像
这样的函数不会更容易
foo :: Int -> ...
foo n ... = ... use n
而不是更繁琐
foo :: forall n . Reflects n Int => ...
foo ... = ... use (value @n)
诚实的回答是:是的,这样会更容易。但是,在类型级别使用 n
允许编译器执行更多的静态检查。例如,您可能想要一个类型来表示 "integers modulo 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))
防止事情出错。编译器静态检查所有内容。代码更难使用,是的,但从某种意义上说,让它更难使用是重点:我们想让用户无法尝试添加错误的模数。
结论:这些是非常高级的扩展。如果您是初学者,则需要慢慢地学习这些技巧。如果您只学习了一小段时间就无法掌握它们,请不要气馁,这确实需要一些时间。一次一小步,针对每个特征做一些练习以理解它的要点。当您遇到困难时,您将始终拥有 Whosebug :-)
我目前正在研究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,但是这段代码显然不是这样。我查看了在线教程、博客,甚至 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))
非常感谢对此的任何帮助。
@n
是现代 Haskell 的高级功能,LYAH 之类的教程通常不会涵盖它,也不会在报告中找到它。
它被称为 type application 并且是 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
这实质上允许代码将类型级自然带到术语级,本质上是将 "type" 作为 "value" 访问。 (顺便说一下,上面的类型被认为是 "ambiguous" 类型——你确实需要 @n
来消除歧义。)
最后:如果我们稍后将其转换为术语级别,为什么还要在类型级别传递 n
?简单地写出像
foo :: Int -> ...
foo n ... = ... use n
而不是更繁琐
foo :: forall n . Reflects n Int => ...
foo ... = ... use (value @n)
诚实的回答是:是的,这样会更容易。但是,在类型级别使用 n
允许编译器执行更多的静态检查。例如,您可能想要一个类型来表示 "integers modulo 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))
防止事情出错。编译器静态检查所有内容。代码更难使用,是的,但从某种意义上说,让它更难使用是重点:我们想让用户无法尝试添加错误的模数。
结论:这些是非常高级的扩展。如果您是初学者,则需要慢慢地学习这些技巧。如果您只学习了一小段时间就无法掌握它们,请不要气馁,这确实需要一些时间。一次一小步,针对每个特征做一些练习以理解它的要点。当您遇到困难时,您将始终拥有 Whosebug :-)