具有算子重载的Haskell素数有限域(Z/pZ)
我现在有一些代码,它创建了一个n阶素数域,包含计算加法、乘法及其逆的所有必要函数。它工作得很好,但我确实希望能够重载+、-、*、/、和^的整数和Num中缀函数,但我不知道如何做到这一点。以下是我当前的代码:具有算子重载的Haskell素数有限域(Z/pZ),haskell,operator-overloading,Haskell,Operator Overloading,我现在有一些代码,它创建了一个n阶素数域,包含计算加法、乘法及其逆的所有必要函数。它工作得很好,但我确实希望能够重载+、-、*、/、和^的整数和Num中缀函数,但我不知道如何做到这一点。以下是我当前的代码: import Data.Maybe data FiniteField = FiniteField {add::(FieldElement -> FieldElement -> FieldElement), addinv::(FieldElement
import Data.Maybe
data FiniteField = FiniteField {add::(FieldElement -> FieldElement -> FieldElement),
addinv::(FieldElement -> FieldElement),
mul::(FieldElement -> FieldElement -> FieldElement),
mulinv::(FieldElement -> FieldElement),
new::(Integer -> FieldElement)
}
newtype FieldElement = FieldElement Integer deriving (Eq, Show, Read)
toInt :: FieldElement -> Integer
toInt (FieldElement x) = x
gcdExt :: Integer -> Integer -> (Integer, Integer, Integer)
gcdExt a 0 = (1, 0, a)
gcdExt a b = (t, s - q * t, g)
where (q, r) = quotRem a b
(s, t, g) = gcdExt b r
modMulInv :: Integer -> Integer -> Integer
modMulInv a n = i
where (i, _, _) = gcdExt a n
isPrimeLoop :: Integer -> Integer -> Bool
isPrimeLoop n i
| i == n = True
| i == 2 = (mod n i /= 0) && isPrimeLoop n (i+1)
| otherwise = (mod n i /= 0) && isPrimeLoop n (i+2)
isPrime :: Integer -> Bool
isPrime n = isPrimeLoop n 2
newFiniteField :: Integer -> Maybe FiniteField
newFiniteField n
| not (isPrime n) = Nothing
| otherwise = Just (FiniteField add addinv mul mulinv new)
where
add = (\x y -> FieldElement (mod (toInt x + toInt y) n) )
addinv = (\x -> FieldElement (mod (n - toInt x) n) )
mul = (\x y -> FieldElement (mod (toInt x * toInt y) n) )
mulinv = (\x -> FieldElement (mod (modMulInv (toInt x) n) n) )
new = (\x -> FieldElement x)
您无法有效地将
Num
用于此设计。关于类型类的重要一点是,分派是按类型而不是按值完成的。FieldElement
的Num
实例无法知道它属于什么FiniteField
,因此它的操作不能取决于您在哪个字段中操作
您可以从以下几个方面入手使用Num
第一种方法是使FieldElement
成为一种表达式类型,它使用Num
实例建立表达式树,然后可以在特定的FiniteField
中进行计算。这具有使用非常简单的技术的优点。当计算变得复杂时,它的缺点是对内存和性能非常不利
第二种是遵循类似于Data.Fixed
的模式。您可以将FiniteField
更改为一个类,并在一些表示各种特定字段的空类型上实现它,例如名称为F17
。然后使用类型参数对FieldElement
进行参数化,该参数用于标记它们属于哪个FiniteField
。最后,FiniteElement
的Num
实例要求其参数具有一个FiniteField
实例,该实例在其实现中使用。这种方法的优点是非常好用。缺点是需要为每个要处理的字段提供样板文件FiniteField
实例
第三个选项与上面的选项非常类似,但将自定义的类似于
F17
的数据类型替换为某种类型级别的自然类型。(手动或从-XDataKinds
中)。然后可以根据类型级别实现Num
实例。这里的优点是,您摆脱了前面方法中的所有样板实例。缺点是它不要求类型级参数是素数,如果它不是素数,则有几次计算都是错误的。您必须解决的主要问题是,不允许在不同的顺序下对FiniteField
的值进行加法/乘法/etc。从类型系统的角度来看,解决方案非常简单:为不同的类型提供不同顺序的值
newtype FieldElem (n :: Nat) = FieldElem Integer
Nat
是一种(来自GHC.TypeLits模块),它的居民是类型级别的数字文字,如1
、2
、3
等
现在,您有不同的类型:
FieldElem 7 -- the type of an element of a finite field of order 7
FieldElem 11 -- the type of an element of a finite field of order 11
因此,如果尝试添加两个不同类型的值,则会出现编译错误
> (x :: FieldElem 7) + (y :: FieldElem 11)
Error! You can only use + on two things of the same type!
> (x :: FieldElem 7) + (y :: FieldElem 7)
-- result: something of type FieldElem 7
现在您可以实现Num
实例:
instance Num (FieldElem n) where
(+) = ...
(*) = ...
这里的一个问题是,(+)
需要知道顺序是什么,唯一的信息是FieldElem
的类型。为了解决这个问题,我们需要n
成为KnownNat
typeclass(也来自GHC.TypeLits)的一个实例,它允许我们在运行时获取其整数值作为一个值:
natVal :: KnownNat n => Proxy n -> Integer
所以
因此我们的最终设计:(需要ScopedTypeVariables
让我们使用n
类型变量)
等等
您可以使用智能构造函数将Integer
s引入FieldElem
:
mkFieldElem :: forall n. KnownNat n => Integer -> Maybe (FieldElem n)
mkFieldElem x | isPrime n = Just (FieldElem (mod x n))
| otherwise = Nothing
where
n = natVal (Proxy :: Proxy n)
很好的一点是,您可以使用Haskell的类型推断来指定所需的顺序:
> mkFieldElem 10 :: Maybe (FieldElem 23)
Just (FieldElem 10) -- :: Maybe (FieldElem 23)
而不是手动将其作为参数传递!:)
例如,通过使用智能构造函数(并隐藏实际的构造函数),您可以确保用户从未拥有任何类型为FieldElem 8
的值,因此您不必担心将坏顺序的字段添加到一起
请注意,不幸的是,frominger::KnownNat n=>Integer->FieldElem n
必须是部分的。它必须拒绝坏命令。但是在base中有大量实例部分实现了fromInteger
:|但是,fromInteger
在Num
中无论如何都是一个坏主意,Num
是一个坏类型类,所以这是Num
的错误:)
EDIT有一种可能的方法可以使
fromInteger
不部分/全部:我们可以创建Prime
类型类,并且只有Nat
参数为Prime的实例:
class KnownNat n => Prime (n :: Nat)
然后你可以做:
mkFieldElem :: Prime n => Integer -> FieldElem n
mkFieldElem x = FieldElem (mod x n)
where
n = natVal (Proxy :: Proxy n)
现在如果你有:
instance Prime n => Num (FieldElem n) where
...
fromInteger = mkFieldElem
fromInteger
将是一个total函数,因为只有prime-order字段才有实例
但是,为了使其工作,您需要以GHC能够理解的方式获取Prime
的实例。理论上,这可以通过使用GHC类型检查器扩展来实现——您可以编写自己的类型检查器扩展,这样n
就会得到一个Prime
实例,如果它在编译时是Prime。然而,这还没有完成。。。您可以做的下一件最好的事情是提供运行时的素数证明:
witPrime :: forall n.KnownNat n => Proxy n -> Maybe (Dict (Prime n))
witPrime p | isPrime (natVal p) = Just (unsafeCoerce (Dict :: Dict (KnownNat n))
| otherwise = Nothing
这是使用库中的Dict
,这是在运行时生成typeclass实例的一种方法。如果在类型为Dict c
的值的Dict
构造函数上进行模式匹配,则实例c
在该case语句中为“范围内”
那么,在我们的情况下,我们可以做到:
case witPrime (Proxy :: Proxy 11) of
Just Dict -> ... -- in this branch, `Prime 11` is an instance we can use
Nothing -> ... -- here, it isn't
或者我们可以在GHCi中运行它:
> let x = mkFieldElem 6 :: FieldElem 11
Error: No instance for (Prime 11)
> case witPrime (Proxy :: Proxy 11) of
Just Dict -> let x = mkFieldElem 6 :: FieldElem 11 -- okay, because of Dict constructor match
in print x
FieldElem 6 -- :: FieldElem 11
有没有办法改变设计,让我可以使用
Num
?@Izakweiss有很多选项-我在过去12分钟左右的编辑中介绍了其中的一些选项。
case witPrime (Proxy :: Proxy 11) of
Just Dict -> ... -- in this branch, `Prime 11` is an instance we can use
Nothing -> ... -- here, it isn't
> let x = mkFieldElem 6 :: FieldElem 11
Error: No instance for (Prime 11)
> case witPrime (Proxy :: Proxy 11) of
Just Dict -> let x = mkFieldElem 6 :: FieldElem 11 -- okay, because of Dict constructor match
in print x
FieldElem 6 -- :: FieldElem 11