我试图通过在有限(Galois)字段上编写一小组函数来学习一些Haskell。几年前,我为计算机代数系统GNU Maxima(see here)编写了第一个类似库的版本,我想我会尝试用Haskell做同样的事情。
但是,我对自己的数据类型感到困惑。对于有限域,您需要一个基本素数q(该域的特征),以及一个多项式p(x),它是不可约的模q。如果p(x)具有阶数n,那么该字段的顺序是q ^ n,并且其元素都是n-1或更小的多项式(模q)。
我们可以将多项式表示为它们的系数列表,因此该字段的元素只是Z_q和长度为n的元素的列表(或向量,如果您愿意)。加法以分量方式模q进行,乘法以p(x)为模进行。
我认为如果我能获得数据类型并且其他数据类型已经整理好,那么其余的就很简单了。我的第一次尝试就是:
import Data.List
data GF = GF {characteristic::Int
,power::Int
,poly::[Int]
,irreducible::[Int]
} deriving(Eq, Show)
功率元素是不必要的 - 它毕竟只是一个小于不可约多项式的长度 - 但它是一个方便的拥有它而不是必须计算它。
然后我的添加功能为:
addGF :: GF -> GF -> GF
addGF x y = GF q n zp p
where
q = characteristic x
n = power x
zp = zipWith (\i j -> rem (i+j) q) xp yp
where
xp = poly x
yp = poly y
p = irreducible x
这很有效,但不够优雅,而且我非常确定"非Haskell-ish"。部分问题在于我不知道如何将伽罗瓦域的定义(或类型)与其元素分离。
我需要做的是为字段提供一般类型,并在其上定义元素的元素。毕竟,我可能想要对一个独立于其元素的字段做的事情,例如生成普通基础,找到原始元素,生成基元元素的对数表,生成随机元素等。
所以我想我的问题是:如何定义泛型字段的泛型类型,以便对其元素的操作尽可能自然?
我已经阅读了许多关于定义数据类型,类等的页面,毫无疑问,其中一个页面包含我的问题的解决方案。但是我读的越多,我就越困惑。我想要的只是让某些人轻轻地指出我,但是我正确地指向正确的方向。谢谢!
答案 0 :(得分:2)
我不认为你的GF
类型很丑或不正确。我看到的主要问题是addGF
没有强制实际添加元素。相反,你可以这样做:
addGF :: GF -> GF -> Maybe GF
addGF x y -- the pipes below are called "guards", a multi-way `if` syntax
| q == characteristic y && n == power y && p == irreducible y = Just $ GF q n zp p
| otherwise = Nothing
where
q = characteristic x
n = power x
zp = zipWith (\i j -> rem (i+j) q) xp yp
where
xp = poly x
yp = poly y
p = irreducible x
将字段的概念与其元素分开可能更符合人体工程学且有用(但不是根本不同的解决方案),如下所示:
-- these names are probably not appropriate
data Field
= Field { characteristic::Int
, power::Int
, irreducible::[Int]
} deriving(Eq, Show)
-- formerly `GF`:
data FieldElement
= FieldElement
{ field::Field
, poly::[Int]
} deriving(Eq, Show)
然后在上面的警卫中,你只需要这样做。
...
| field x == field y = Just $ ...
当您希望使用记录名称时, RecordWildCards
也是删除样板文件的一个很好的扩展。
如果您知道您将使用编译时已知参数的特定字段,那么您可以允许类型检查器为您强制addGF
中的不变量。一种方式是这样的:
-- see `Data.Proxy` for info about this idiom
class SomeField f where
characteristic :: proxy f -> Int
power :: proxy f -> Int
irreducible :: proxy f -> [Int]
-- A field element is just the polynomial, tagged with its field using the `f` type parameter
-- you may want to not expose the internals of `GF` but instead expose a
-- makeGF function which enforces whatever invariants should hold between the parameters
-- of a field and the polynomial of its element.
newtype GF f = GF { poly :: [Int] }
-- `addGF` is no longer partial; the type system enforces that both arguments are elements of the same field
addGF :: (SomeField f)=> GF f -> GF f -> GF f
addGF x@(GF xp) (GF yp) = GF $ zipWith (\i j -> rem (i+j) q) xp yp
where q = characteristic x
我提到"载体"只是导致问题,你在这里向你开放的各种方法与你用矢量算术的方法相同,例如只能添加相同尺寸的矢量。
答案 1 :(得分:2)
在现代Haskell(GHC> = 7.8)中将characteristic
和power
提升到类型系统中很容易,也就是说,
{-# LANGUAGE TypeOperators, DataKinds #-}
{-# LANGUAGE FlexibleContexts, TypeFamilies, UndecidableInstances #-}
{-# LANGUAGE StandaloneDeriving #-}
并表示多项式的系数来自finite group,其大小是特征:
import GHC.TypeLits
import Data.Modular
data GF χ -- characteristic
n -- power
= GF { irreducible :: [ℤ/χ]
, poly :: [ℤ/χ]
}
这已经免费为您提供多项式的任何添加都将以模χ
为模。
你可以furthermore express总是有n + 1
个系数:
import qualified Data.Vector.Fixed as Fix
import qualified Data.Vector.Fixed.Boxed as Fix
data GF χ n
= GF { irreducible :: Fix.Vec (n+1) (ℤ/χ)
, poly :: Fix.Vec (n+1) (ℤ/χ)
}
deriving instance (KnownNat χ, Fix.Arity (n+1)) => Show (GF χ n)
addGF :: (KnownNat χ, Fix.Arity (n+1))
=> GF χ n -> GF χ n -> GF χ n
addGF (GF irr xp) (GF irr' yp)
| irr==irr' = GF irr $ Fix.zipWith (+) xp yp
| otherwise = error "Cannot add elements of finite fields with different irreducible polynomials!"
main = print (GF irr (Fix.fromList [0,0,1]) `addGF` GF irr (Fix.fromList [0,1,1])
:: GF 2 2)
where irr = Fix.fromList [1,1,1]
结果:
GF {irreducible = fromList [1,1,1], poly = fromList [0,1,0]}
我们还必须运行时检查不可约多项式,这仍然很难看。虽然原则上可以将其提升到类型级别,但我不确定这是否真的能够很好地发挥作用;我们已经在努力克服Haskell作为依赖类型语言使用的界限。也许只有一次总是被使用的不可约多项式才能选择每一个特征和能力?