Muhammad Ali
Muhammad Ali

Reputation: 599

Division in Haskell for Numeric class

I am trying to define a function toVal :: (Num a) => (Fraction a) -> a. The function takes a fraction and evaluates its numeric value. But since the function uses division, I can do the following since division is defined by different functions on the subclasses of Num a:

data Fraction a = Constant a
    |Rational{numerator :: (Fraction a), denominator :: (Fraction a)}


toVal1 :: (Integral a) => (Fraction a) -> a
toVal1 (Constant a) = a
toVal1 (Rational num den) = (toVal1 num) `div` (toVal1 den)

toVal2 :: (Fractional a) => (Fraction a) -> a
toVal2 (Constant a) = a
toVal2 (Rational num den) = (toVal2 num) / (toVal2 den)

Is there a way I can combine the two functions so that I can have a generic function toVal :: (Num a) => (Fraction a) -> a?

Upvotes: 4

Views: 1014

Answers (1)

Zeta
Zeta

Reputation: 105876

No, since Num has no notion of division, or, to say it in C++ terms, because there's no dynamic_cast<...> in Haskell.

You could introduce your own typeclass:

class HasDivOp a where
  divOp :: a -> a -> a

instance HasDivOp Int     where divOp = div
instance HasDivOp Integer where divOp = div
instance HasDivOp Double  where divOp = (/)
instance HasDivOp Float   where divOp = (/)

And then have a single function that takes the correct divOp:

toVal :: (Num a, HasDivOp a) => (Fraction a) -> a
toVal (Constant a)   = a
toVal (Rational a b) = toVal a `divOp` toVal b

An alternative way to reduce the code duplication is to add an additional function:

divG :: (a -> b) -> (a -> a -> b) -> Fraction a -> b
divG p _ (Constant x)       = p x
divG p f (Rational num den) = f (divG p f num) (divF p f den)

That is, for fixed as and bs tell divF how to combine two as into a b, or how to transform an a to a b, you can reduce two Fraction as into a single b. In all your cases a = b, so we can define another helper:

divF :: (a -> a -> a) -> Fraction a -> a
divF = divG id

Now we can define both toVal1 and toVal2 in terms of divF:

toVal1 :: Integral n => Fraction n -> Fraction n -> n
toVal1 = divF div

toVal2 :: Fractional n => Fraction n -> Fraction n -> n
toVal2 = divF (/)

That being said, both toVal and toVal1 lead to interesting behaviour on integral numbers:

toVal1 (Rational (Rational 2 3) (Rational 2 3)) = 0 :: Int

But x div x should be 1 for any x /= 0. If you preprocess your integral Fractions this problem won't occur:

rationalDiv :: Integral n => Fraction a -> Fraction a -> Fraction a
rationalDiv (Constant a  ) (Constant c  ) = Rational a       c
rationalDiv (Constant a  ) (Rational c d) = Rational (a * d) c
rationalDiv (Rational a b) (Constant c  ) = Rational a       (b * c)
rationalDiv (Rational a b) (Rational c d) = Rational (a * d) (b * c)

Note that this needs a Num instance for Fraction due to *. That way you have the most control about the actual division and just need to transform the elements at the end:

toVal3 :: Integral n => Fraction n -> n
toVal3 = divF div . divF rationalDiv

Which correctly leads to 1 in our upper example. But to get back to the topic: no, you cannot use only Num as a constraint, you need to use another one that actually has a notion of division.

Upvotes: 3

Related Questions