为Haskell中的每个Integer创建一个类型? [英] Create a type for each Integer in Haskell?

查看:44
本文介绍了为Haskell中的每个Integer创建一个类型?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想在Haskell中创建一个表示整数mod n 的数据类型,该数据类型是 Num 的实例,以帮助执行简单的模块化算术运算.我的第一次尝试看起来像这样

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

这可以编译,但是有问题,不仅因为由于 k 超出范围而不清楚如何实现 fromInteger ,而且还因为它允许添加整数 mod 4 ,且整数 mod 5 没有类型错误.在我的第二次尝试中,我试图解决这些问题

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

但是我在实现 Num 接口时遇到了麻烦,因为 k 的定义有冲突,但仍然超出范围.如何在Haskell中创建这样的数据类型?

but I am running into trouble implementing the Num interface because of conflicting definitions of k which is still out of scope. How can I make such a data type in Haskell?

推荐答案

如注释中所述,其想法是利用自然数的类型级别表示形式,因此对于2,3或3分别具有可识别的类型4等.这需要扩展:

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:

{-# 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'

DataKinds 扩展名会自动提升为类型级别.然后,您可以将其用作类型级别的标记,以定义一系列相关但不同的类型:

which the DataKinds extension automatically lifts to the type level. 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

第二个方法是使用内置的GHC功能,该功能会自动将整数文字(例如 2 3 )提升到类型级别.它的工作方式大致相同:

The second is to use a built-in GHC facility that automatically lifts integer literals, like 2 and 3 to the type level. 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

当您仅使用自然符号来标记"类型时,通常使用 Nat GHC.TypeLits 版本更为方便.如果您必须实际对类型进行类型级别的计算,则使用递归版本更容易完成某些计算.

When you're using naturals only to "tag" a type, it's generally more convenient to use the GHC.TypeLits version of Nat. If you have to actually do type-level computations on the types, some computations are more easily done using the recursive version.

由于我们只需要使用自然字符作为标记,因此我们可以坚持使用 GHC.TypeLits 解决方案.因此,我们将像这样定义您的数据类型:

Since we only need the naturals as tags, we can stick with the GHC.TypeLits solution. So, we'd define your data type like so:

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
import GHC.TypeLits
data Z (n :: Nat) = El Integer

Show 实例中,我们需要利用 GHC.TypeLits 中的其他功能将类型级别的 Nat 转换为我们可以在印刷表示形式中包含的值级 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:

instance (KnownNat n) => Show (Z n) where
  show el@(El e) = show e ++ " (mod " ++ show (natVal el) ++ ")"

这里正在发生魔术! natVal 函数具有签名:

There's magic going on here! The natVal function has signature:

natVal :: forall n proxy. KnownNat n => proxy n -> Integer

意味着对于"KnownNat" ,无论意味着什么,它都可以采用类型为 proxy n 形式的代理值,并返回对应于的实际整数类型级参数 n .幸运的是,我们的原始元素的类型为 Z n ,它恰好适合 proxy n 类型的模式,因此通过运行 natVal el ,我们可以获得值级别 Integer 对应于 Z n 中类型级别 n .

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

我们将在 Integral 实例中使用相同的魔术:

We'll use the same magic in the Integral instance:

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)

请注意, k El 构造函数中消失了,因为它不是数据级别的数量.可以在需要的地方使用 KnownNat 实例使用 natVal el 对其进行检索.

Note that the k disappears from the El constructor, because it's not a data-level quantity. Where needed, it can be retrieved using natVal el using the KnownNat instance.

完整程序为:

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

它可以按预期工作:

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

这篇关于为Haskell中的每个Integer创建一个类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆