作为一个玩具项目,我想了解如何在Haskell中建模数学组。
首先,我们首先观察要定义的Group
只是一个带有反转的Monoid
。
class (Monoid m) => Group m where
minvert :: m -> m
接下来,我们首先将自己局限于循环组,然后定义12阶循环组。
data Cyclic12 = Cyclic12 Int deriving (Show, Eq)
最后,我们为Cyclic12
实例化两个类。
instance Monoid Cyclic12 where
mempty = Cyclic12 0
mappend (Cyclic12 x) (Cyclic12 y) = Cyclic12 ((x + y) `mod` 12)
instance Group Cyclic12 where
minvert (Cyclic12 x) = Cyclic12 ((0 - x) `mod` 12)
如何从特定值12中抽象出先前的定义,以允许对不同的循环组进行更通用的定义?
理想情况下,我想编写像
这样的定义instance Monoid (Cyclic k) where
mempty = Cyclic k 0
mappend (Cyclic k x) (Cyclic k y) = Cyclic k ((x + y) `mod` k)
instance Group (Cyclic k) where
minvert (Cyclic k x) = Cyclic k ((0 - x) `mod` k)
但是有一个像
这样的数据定义data Cyclic = Cyclic Int Int deriving (Show, Eq)
我们仍然没有走得太远,因为k
“不在范围内”。关于它显而易见的微不足道,我有一种感觉,就是错过了一些基本概念。在此先感谢您的帮助。
答案 0 :(得分:8)
您必须制作该类型的循环组部分的顺序。一种方法是使用the builtin type level natural numbers GHC gives us。
{-# LANGUAGE DataKinds, KindSignatures, ScopedTypeVariables #-}
import GHC.TypeLits
import Data.Proxy (Proxy(..))
data Cyclic (n :: Nat) = Cyclic Integer deriving (Show, Eq)
这让我们可以轻松地完成这两个实例:
instance KnownNat n => Monoid (Cyclic n) where
mempty = Cyclic 0
Cyclic x `mappend` Cyclic y = Cyclic $ (x + y) `mod` natVal (Proxy :: Proxy n)
instance KnownNat n => Group (Cyclic n) where
minvert (Cyclic x) = Cyclic $ negate x `mod` natVal (Proxy :: Proxy n)
签名的KnownNat
部分基本上表示无论n :: Nat
最终是什么,它都是我们应该能够使用natVal
提取的价值。
然后,一旦装入GHCi:
ghci> :set -XDataKinds
ghci> type Z12 = Cyclic 12
ghci> mappend (Cyclic 8 :: Z12) (Cyclic 7 :: Z12)
Cyclic 3
ghci> minvert (Cyclic 4 :: Z12)
Cyclic 8
DataKinds
让我们有一个类型参数n
,它不是一个类型。我们说它的种类不是类型(*
)。在这种情况下,n
有Nat
(n :: Nat
)种。KindSignatures
只是让我们写n :: Nat
,其中::
表示“有善意”(而不是“有类型”),当它有意义时。ScopedTypeVariables
使n
中的Proxy :: Proxy n
类型变量与实例头instance KnownNat m => Monoid (Cyclic n) where
中的变量相同。