简体   繁体   English

在 Haskell 中为每个整数创建一个类型?

[英]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 工具,自动将整数文字(如23提升到类型级别。 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 .当您仅使用自然来“标记”类型时,使用NatGHC.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.请注意, kEl构造函数中消失了,因为它不是数据级数量。 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM