具有运算符重载的Haskell中的素数有限域(Z / pZ)

时间:2016-10-03 01:25:55

标签: haskell operator-overloading

我现在有一些代码可以创建一个n阶素数域,其中包含计算加法,乘法和逆的所有必要函数。它工作正常,但我真的希望能够重载+, - ,*,/和^的Integral和Num中缀函数,但我不知道如何做到这一点。这是我目前的代码:

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)

2 个答案:

答案 0 :(得分:2)

您必须解决的主要问题是您不应该被允许添加/乘法/等。不同订单下FiniteField的值。从类型系统的角度来看,解决方案非常简单:给不同类型的不同类型的值。

newtype FieldElem (n :: Nat) = FieldElem Integer

Nat是一种(来自GHC.TypeLits模块),其居民是类型级数字文字,如123等。

现在,您有不同的类型:

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类型类的实例(也来自GHC.TypeLits),这样我们就可以在运行时将其整数值作为值:

natVal :: KnownNat n => Proxy n -> Integer

所以,

> natVal (Proxy :: Proxy 10)
10
> natVal (Proxy :: Proxy 19)
19

所以我们的最终设计:(需要ScopedTypeVariables让我们使用n类型变量)

instance KnownNat n => Num (FieldElem n) where
  FieldElem x + FieldElem y = FieldElem (mod (x + y) n)
    where
      n = natVal (Proxy :: Proxy n)

等!

您可以使用智能构造函数将Integer引入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的值,因此您不必担心坏的字段订单加在一起。

请注意,遗憾的是,fromInteger :: KnownNat n => Integer -> FieldElem n必然是部分的。它必须拒绝坏订单。但是 base 中有大量实例,部分实现fromInteger无论如何:但是,fromInteger Num仍然是一个坏主意,Num是一个糟糕的类型类,所以Num的错误:)

编辑有一种潜在的方法可以使fromInteger不是部分/全部:我们可以创建一个Prime类型类,并且只有{{1}的实例}参数是素数:

Nat

然后你可以做:

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 将是一个总函数,因为唯一的实例将是素数字段!

但是,为了实现这一点,您需要以GHC可以理解的方式获取fromInteger的实例。从理论上讲,这可以使用GHC类型检查器扩展来完成 - 您可以编写自己的类型检查器扩展,以便Prime如果它在编译时处于初级状态,则会被赋予n个实例。时间。但是,这还没有完成......你可以做的下一个最好的事情是提供关于质量的运行时证明:

Prime

这是使用constraints库中的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的值的Dict构造函数进行了模式匹配,则实例Dict c在"范围内"在那个案例陈述中。

在我们的案例中,我们可以这样做:

c

或者我们可以在GHCi中运行它:

case witPrime (Proxy :: Proxy 11) of
  Just Dict -> ... -- in this branch, `Prime 11` is an instance we can use
  Nothing   -> ... -- here, it isn't

答案 1 :(得分:1)

使用此设计无法有效地使用Num。关于类型类的重要一点是调度是按类型而不是值来完成的。 Num的{​​{1}}实例无法知道它属于哪个FieldElement,因此其操作无法取决于您正在操作的字段。

您可以采取一些方法,这可以与FiniteField一起使用。

第一个是使Num表达式类型使用其FieldElement实例构建表达式树,然后可以在特定Num内进行评估。这具有使用非常简单的技术的优点。当计算变得复杂时,它的缺点是真的对内存和性能不利。

第二种是遵循像FiniteField这样的模式。您可以将Data.Fixed更改为一个类,并在代表各种特定字段的一些空类型上实现它,例如名称为FiniteField。然后使用类型参数对F17进行参数化,该参数用于标记它们所属的FieldElement。最后,FiniteField Num的实例要求其参数具有FiniteElement实例,该实例在其实现中使用。这种方法的优点是非常适合使用。缺点是需要为每个要使用的字段设置样板FiniteField

第三个选项与上面的选项非常相似,但是使用某种类型级别的自然替换自定义FiniteField类数据类型。 (手动或来自F17)。然后,您可以根据类型级自然实现-XDataKinds实例。这里的优点是您可以摆脱以前方法中的所有样板实例。缺点是它不要求类型级参数是素数,如果它不是素数,则几个计算是错误的。