[英]Create a type for each Integer in Haskell?
I would like to create a data type in Haskell that represents the integers mod n
, and which is an instance of Num
to help perform simple modular arithmetic operations.我想在 Haskell 中创建一个表示整数 mod
n
的数据类型,它是Num
一个实例,以帮助执行简单的模算术运算。 My first attempt at this looked like this我第一次尝试这个看起来像这样
data Z n e = El n e
instance (Show n, Show e) => Show (Z n e) where
show (El n e) = show e ++ " (mod " ++ show n ++ ")"
instance (Integral k, Integral e) => Num (Z k e) where
(+) (El n a) (El m b) = El n (mod (a + b) n)
(-) (El n a) (El m b) = El n (mod (a - b) n)
(*) (El n a) (El m b) = El n (mod (a * b) n)
negate (El n a) = El n (mod (0 - a) n)
abs (El n a) = El n a
signum (El n a) = El n a
fromInteger i = -- problematic...
This compiles but is problematic not only because its unclear how to implement fromInteger
since k
is out of scope, but also because it is permissible to add an integer mod 4
with an integer mod 5
without a type error.这可以编译但有问题,不仅因为它不清楚如何实现
fromInteger
因为k
超出范围,而且还因为允许将整数mod 4
与整数mod 5
相加而不会出现类型错误。 In my second attempt I tried to resolve these issues在我的第二次尝试中,我试图解决这些问题
data Z n = El Integer
instance (Show n) => Show (Z n) where
show (El n e) = show e ++ " (mod " ++ show n ++ ")"
instance (Integral k) => Num (Z k) where
(+) (El k a) (El k b) = El k (mod (a + b) k)
(-) (El k a) (El k b) = El k (mod (a - b) k)
(*) (El k a) (El k b) = El k (mod (a * b) k)
negate (El k a) = El k (mod (0 - a) k)
abs (El k a) = El k a
signum (El k a) = El k a
fromInteger i = El (fromIntegral i) k
but I am running into trouble implementing the Num
interface because of conflicting definitions of k
which is still out of scope.但是我在实现
Num
接口时遇到了麻烦,因为k
定义仍然超出范围。 How can I make such a data type in Haskell?如何在 Haskell 中创建这样的数据类型?
As noted in the comments, the idea is to make use of a type-level representation of natural numbers, so you have separate identifiable types for 2 versus 3 versus 4, etc. This requires an extension:正如评论中所指出的,这个想法是利用自然数的类型级表示,所以你有 2、3 和 4 的单独可识别类型,等等。这需要扩展:
{-# LANGUAGE DataKinds #-}
There are two main methods for representing naturals as types.将自然物表示为类型有两种主要方法。 The first is to define a recursive data type:
首先是定义递归数据类型:
data Nat' = Z | S Nat'
which the DataKinds
extension automatically lifts to the type level. DataKinds
扩展自动提升到类型级别。 You can then use this as, among other things, a type-level tag to define a family of related but distinct types:然后,您可以将其用作类型级标记来定义一系列相关但不同的类型:
{-# LANGUAGE KindSignatures #-}
data Foo (n :: Nat') = Foo Int
twoFoo :: Foo (S (S Z))
twoFoo = Foo 10
threeFoo :: Foo (S (S (S Z)))
threeFoo = Foo 20
addFoos :: Foo n -> Foo n -> Foo n
addFoos (Foo x) (Foo y) = Foo (x + y)
okay = addFoos twoFoo twoFoo
bad = addFoos twoFoo threefoo -- error: different types
The second is to use a built-in GHC facility that automatically lifts integer literals, like 2
and 3
to the type level.第二种是使用内置的 GHC 工具,自动将整数文字(如
2
和3
提升到类型级别。 It works much the same way:它的工作方式大致相同:
import GHC.TypeLits (Nat)
data Foo (n :: Nat) = Foo Int
twoFoo :: Foo 2
twoFoo = Foo 10
threeFoo :: Foo 3
threeFoo = Foo 20
addFoos :: Foo n -> Foo n -> Foo n
addFoos (Foo x) (Foo y) = Foo (x + y)
okay = addFoos twoFoo twoFoo
bad = addFoos twoFoo threefoo -- type error
When you're using naturals only to "tag" a type, it's generally more convenient to use the GHC.TypeLits
version of Nat
.当您仅使用自然来“标记”类型时,使用
Nat
的GHC.TypeLits
版本通常更方便。 If you have to actually do type-level computations on the types, some computations are more easily done using the recursive version.如果您必须实际对类型进行类型级计算,则使用递归版本更容易完成某些计算。
Since we only need the naturals as tags, we can stick with the GHC.TypeLits
solution.由于我们只需要自然作为标签,我们可以坚持使用
GHC.TypeLits
解决方案。 So, we'd define your data type like so:所以,我们会像这样定义你的数据类型:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
import GHC.TypeLits
data Z (n :: Nat) = El Integer
In the Show
instance, we need to make use of some other facilities in GHC.TypeLits
to convert the type-level Nat
to a value-level Integer
that we can include in the printed representation:在
Show
实例中,我们需要利用GHC.TypeLits
的一些其他功能将类型级别的Nat
转换为我们可以包含在打印表示中的值级别的Integer
:
instance (KnownNat n) => Show (Z n) where
show el@(El e) = show e ++ " (mod " ++ show (natVal el) ++ ")"
There's magic going on here!这里有魔法! The
natVal
function has signature: natVal
函数具有签名:
natVal :: forall n proxy. KnownNat n => proxy n -> Integer
meaning that for a "KnownNat"
, whatever that means, it can take a proxy value whose type is of form proxy n
, and return the actual integer corresponding to the type-level argument n
.这意味着对于
"KnownNat"
,无论这意味着什么,它都可以采用类型为proxy n
的代理值,并返回与类型级别参数n
对应的实际整数。 Fortunately, our original element has type Z n
which fits the proxy n
type pattern just fine, so by running natVal el
, we get the value-level Integer
corresponding to the type-level n
in Z n
.幸运的是,我们原始元素的类型
Z n
正好适合proxy n
类型模式,因此通过运行natVal el
,我们获得了Z n
与类型级别n
对应的值级别Integer
。
We'll use the same magic in the Integral
instance:我们将在
Integral
实例中使用相同的魔法:
instance (KnownNat k) => Num (Z k) where
(+) el@(El a) (El b) = El (mod (a + b) k) where k = natVal el
(-) el@(El a) (El b) = El (mod (a - b) k) where k = natVal el
(*) el@(El a) (El b) = El (mod (a * b) k) where k = natVal el
negate el@(El a) = El (mod (0 - a) k) where k = natVal el
abs el@(El a) = El a where k = natVal el
signum el@(El a) = El 1
fromInteger i = El (fromIntegral i)
Note that the k
disappears from the El
constructor, because it's not a data-level quantity.请注意,
k
从El
构造函数中消失了,因为它不是数据级数量。 Where needed, it can be retrieved using natVal el
using the KnownNat
instance.在需要的地方,可以使用
KnownNat
实例使用natVal el
来检索它。
The full program is:完整的程序是:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
import GHC.TypeLits
data Z (n :: Nat) = El Integer
instance (KnownNat n) => Show (Z n) where
show el@(El e) = show e ++ " (mod " ++ show (natVal el) ++ ")"
instance (KnownNat k) => Num (Z k) where
(+) el@(El a) (El b) = El (mod (a + b) k) where k = natVal el
(-) el@(El a) (El b) = El (mod (a - b) k) where k = natVal el
(*) el@(El a) (El b) = El (mod (a * b) k) where k = natVal el
negate el@(El a) = El (mod (0 - a) k) where k = natVal el
abs el@(El a) = El a where k = natVal el
signum el@(El a) = El 1
fromInteger i = El (fromIntegral i)
and it works as intended:它按预期工作:
> :set -XDataKinds
> (El 2 :: Z 5) + (El 3 :: Z 5)
0 (mod 5)
> (El 2 :: Z 5) + (El 3 :: Z 7)
<interactive>:15:18: error:
• Couldn't match type ‘7’ with ‘5’
Expected type: Z 5
Actual type: Z 7
• In the second argument of ‘(+)’, namely ‘(El 3 :: Z 7)’
In the expression: (El 2 :: Z 5) + (El 3 :: Z 7)
In an equation for ‘it’: it = (El 2 :: Z 5) + (El 3 :: Z 7)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.