0

I would like to model a field in Haskell. A field is a set of elements plus two operations, which can be seen as multiplication and addition. I proceeded as follows:

data Field a = Field [a] (a -> a -> a) (a -> a -> a)

elements :: Field a -> [a]
elements (Field els _ _) = els

add :: Field a -> a -> a -> a
add (Field _ addop _) = addop

mul :: Field a -> a -> a -> a
mul (Field _ _ mulop) = mulop

However, I would now like to model polynomials over fields, i.e., something like

data Poly = Poly f [(a, Int)]

where f should be a field and a should be the type of the field. The list [(a, Int)] would represent the polynomial, e.g.,

[(4, 2), (1, 1)] = 4 * x^2 + 1 * x^1

Yet I cannot think of how to achieve this in Haskell. I think maybe somehow I can declare Field as a class instead and then somehow constrain Poly to only work with Fields, but as far as I know, I cannot constrain data constructors.

fabian789
  • 8,348
  • 4
  • 45
  • 91

1 Answers1

1

Another way to do this is to define a Field typeclass:

class Field a where
    rplus :: a -> a -> a
    rmult :: a -> a -> a
    rnegate :: a -> a
    rinverse :: a -> a
    runit :: a
    rzero :: a

For each field, define a type that represents the underlying set of the field, then define an appropriate instance for that type. You are responsible for ensuring that your implementation obeys the various field laws (multiplication distributes over addition, any element times its inverse is 1, etc).

See the last example for how to define a Field instance for a polynomial over a field.

Examples

Integers mod 7

-- Haskell doesn't have dependent types yet, so use the
-- next best thing: a smart constructor
newtype Z7 = Z7 Integer
makeZ7 :: Integer -> Z7
makeZ7 x = Z7 $ (x `mod` 7)

instance Field Z7 where
    rplus (Z7 a) (Z7 b) = makeZ7 (a + b)
    rmult (Z7 a) (Z7 b) = makeZ7 (a * b)
    rzero = makeZ7 0
    runit = makeZ7 1
    rnegate (Z7 a) = makeZ7 (7 - a)
    -- Necessarily partial, since 0 doesn't have an inverse
    rinverse (Z7 a) | a == 1 = makeZ7 1
                    | a == 2 = makeZ7 4
                    | a == 3 = makeZ7 5
                    | a == 4 = makeZ7 2
                    | a == 5 = makeZ7 3
                    | a == 6 = makeZ7 6

Rationals

This one is easier because Integral a => Ratio a is already an instance of Num, so we get most of the implementation for free.

import Data.Ratio
instance Integeral a => Field (Ratio a) where
    rplus = (+)
    rmult = (*)
    rnegate = negate
    rinverse q = denominator q % numerator q
    rzero = 0
    runit = 1

Polynomials over a field

The coefficients of a polynomial can be anything you want.

data Poly f = Poly [(f, Int)]

However, polynomials only form a field if the coefficients themselves are from a field, so this is where you declare that constraint.

-- A type Poly f is a field if f is a field.
-- Fields: Poly Z7, Integral a => Poly (Ratio a)
-- Not a field: Poly Integer
instance Field f => Field (Poly f) where
    runit = Poly [(runit, 0)] -- Not Poly [(1,0)]
    rzero = Poly [(rzero, 0)] -- Not Poly [(0,0)]
    -- Not Poly [(-c, e) | (c,e) <- p]
    rnegate (Poly p) = Poly [(rnegate c, e) | (c,e) <- p]
    -- Left as an exercise for the reader
    -- Remember to use runit instead of 1, rzero instead of 0
    -- and rnegate instead of - where appropriate.
    rinverse p = ...
    rplus p q = ...
    rmult p q = ...

Some very quick examples that don't require me to finish implementing the Poly instance. (You can derive Show for all the types involved.)

> runit :: Z7
Z7 1
> runit :: Rational  -- type Rational = Ratio Integer
1 % 1
> runit :: Poly Z7
Poly [(Z7 1,0)]
> runit :: Poly Rational
Poly [(1 % 1,0)]
chepner
  • 497,756
  • 71
  • 530
  • 681