Taking inspiration from SICP Exercise 2.1, I decided to implement a module in Haskell for rational numbers.
Some of my concerns include:
- Is this idiomatic Haskell?
- Besides
Num
,Eq
,Ord
,Show
, andRead
, are there other typeclasses I should be implementing? I've got "free-floating" functionstoInteger'
andrationalDiv
. Is there some typeclass that would accommodate them? - Is this a good way to handle division by zero?
- How is the code layout and organization?
module Rational'
( toInteger'
, rationalDiv
) where
data Rational' = Rational' { numer :: Integer
, denom :: Integer
}
instance Num Rational' where
(+) = rationalAdd
(-) = rationalSub
(*) = rationalMul
abs = rationalAbs
signum = rationalSignum
fromInteger = rationalFromInteger
instance Eq Rational' where
(==) = rationalEq
instance Ord Rational' where
compare = rationalCompare
instance Show Rational' where
show = rationalShow
instance Read Rational' where
readsPrec = rationalRead
divByZero n = error ("Division by zero: " ++ (show n) ++ "/0")
rationalAdd :: Rational' -> Rational' -> Rational'
a `rationalAdd` b = reduce $ Rational' (an * bd + bn * ad) (ad * bd)
where (Rational' an ad) = a
(Rational' bn bd) = b
rationalSub :: Rational' -> Rational' -> Rational'
a `rationalSub` b = a + (Rational' (-bn) bd)
where bn = numer b
bd = denom b
rationalMul :: Rational' -> Rational' -> Rational'
a `rationalMul` b = reduce $ Rational' (an * bn) (ad * bd)
where (Rational' an ad) = a
(Rational' bn bd) = b
rationalDiv :: Rational' -> Rational' -> Rational'
a `rationalDiv` b = a * (Rational' bd bn)
where bn = numer b
bd = denom b
rationalAbs :: Rational' -> Rational'
rationalAbs (Rational' n d) = reduce $ Rational' (abs n) (abs d)
{- You might expect the result of signum to be an Int (namely -1 | 0 | 1), but
- the Num typeclass requires it to be the same type as the input. -}
rationalSignum :: Rational' -> Rational'
rationalSignum (Rational' n d)
| n == 0 = 0
| (n > 0) == (d > 0) = 1
| otherwise = Rational' (-1) 1
rationalFromInteger :: Integer -> Rational'
rationalFromInteger n = Rational' n 1
{- Truncates towards 0 -}
toInteger' :: Rational' -> Integer
toInteger' (Rational' n d) = n `div` d
rationalEq :: Rational' -> Rational' -> Bool
a `rationalEq` b = (a `rationalCompare` b) == EQ
rationalCompare :: Rational' -> Rational' -> Ordering
a `rationalCompare` b
| (ad == 0) = divByZero a
| (bd == 0) = divByZero b
| (signum ad) == (signum bd) = (an * bd) `compare` (bn * ad)
| otherwise = (bn * ad) `compare` (an * bd)
where (Rational' an ad) = a
(Rational' bn bd) = b
rationalShow :: Rational' -> [Char]
rationalShow a = (show $ numer a) ++ "/" ++ (show $ denom a)
rationalRead :: Int -> [Char] -> [(Rational', [Char])]
rationalRead _ r = [(Rational' (read ns) (read ds), "")]
where (ns, (_:ds)) = break (=='/') r
reduce :: Rational' -> Rational'
reduce a = Rational' (n `div` common) (d' `div` common)
where (Rational' n d) = a
d'
| (d == 0) = divByZero n
| otherwise = d
common = (gcd n d') * (signum d')