Reputation: 599
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
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 a
s and b
s tell divF
how to combine two a
s into a b
, or how to transform an a
to a b
, you can reduce two Fraction a
s 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
should be 1 for any div
xx /= 0
. If you preprocess your integral Fraction
s 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