How to create a custom made function instance of Eq and Ord

62 views Asked by At

I've implemented a normalisation function normaliseRat :: Rat -> Rat for rational numbers so that all of Rat 2 4, Rat (-1) (-2) and Rat 1 2 are transformed into the same internal representation. Furthermore, implemented a function createRat :: Integer -> Integer -> Rat that, given two Integers, returns a normalised Rat. Note: I used the Prelude that contains a function gcd to compute the greatest common divisor of two integers.

gcd' :: Integer -> Integer -> Integer
gcd' a b = if b == 0 then a else gcd' b (a `mod` b)

data Rat = Rat Integer Integer

normaliseRat :: Rat -> Rat
normaliseRat (Rat num den) =
    let commonDivisor = gcd' (abs num) (abs den)
        num' = num `div` commonDivisor
        den' = den `div` commonDivisor
    in Rat (if den < 0 then (-num') else num') (abs den')

createRat :: Integer -> Integer -> Rat
createRat num den = normaliseRat (Rat num den)

-- Adding the Show instance for Rat
instance Show Rat where
    show (Rat num den) = show num ++ "/" ++ show den

This program is giving the expected result. Like:

ghci>createRat 2 4
ghci> rat1
1/2

Now I want to make Rat an instance of Eq and Ord. Of course, Rat 2 4 == Rat 1 2 should evaluate to True.

2

There are 2 answers

0
willeM_ Van Onsem On

We know that two fractions a/b and c/d are equal if a×d = c×b (given the denominators are not zero), so we can implement the Eq instance as:

instance Eq Rat where
    Rat a b == Rat c d = a * d == b * c

I leave the instance of Ord as an exercise. This will be more complicated, since the denominators can be negative as well.

0
Daniel Wagner On

One option is to ensure that Rat 2 4 is not a value that can be constructed. Do this by only exposing the part of the API that preserves normalization. For example:

module Rat (
    Rat, -- expose the type, but not the data constructor
         -- (compare an export of Rat(Rat) or Rat(..) which would export both)
    createRat,
    )

-- because rationals are always created already-reduced, the
-- derived Eq instance is good enough
data Rat = Rat Integer Integer deriving Eq

createRat :: Integer -> Integer -> Rat
createRat = undefined

-- reduction isn't enough if you want the usual ordering on
-- rationals, so we need to implement this one ourselves
instance Ord Rat where compare (Rat n d) (Rat n' d') = undefined

This is a fairly standard technique, and you can find more resources using "smart constructor" as a search term.

As an aside, I would recommend deriving your Show instance. If you'd like a way to show rationals that is prettier, export a function specifically for that, named showRat or similar. This, too, is a common practice.