Symbolic Math in Haskell -
import control.monad (liftm2) infixl 4 :+:, :-: infixl 5 :*:, :/: data expr = const | (expr a) :+: (expr a) | (expr a) :-: (expr a) | (expr a) :*: (expr a) | (expr a) :/: (expr a) deriving (show, eq) evalexpr (const a) = evalexpr (a :+: b) = liftm2 (+) (evalexpr a) (evalexpr b) evalexpr (a :-: b) = liftm2 (-) (evalexpr a) (evalexpr b) evalexpr (a :*: b) = liftm2 (*) (evalexpr a) (evalexpr b) evalexpr (a :/: b) = if (evalexpr b) == 0 nothing else liftm2 (/) (evalexpr a) (evalexpr b)
this symbolic representation of math , evaluation function. have problem limited knowledge of monads , maybe type problem arises me wanting return nothing value if there division 0 in evaluation function. many different reasons when try run evalexpr (const 3)
or more complicated fails on runtime. there missing?
the version think want is:
import control.monad (liftm2) infixl 4 :+:, :-: infixl 5 :*:, :/: data expr = const | (expr a) :+: (expr a) | (expr a) :-: (expr a) | (expr a) :*: (expr a) | (expr a) :/: (expr a) deriving (show, eq) evalexpr :: (eq a, num a, fractional a) => (expr a) -> maybe evalexpr (const a) = return evalexpr (a :+: b) = liftm2 (+) (evalexpr a) (evalexpr b) evalexpr (a :-: b) = liftm2 (-) (evalexpr a) (evalexpr b) evalexpr (a :*: b) = liftm2 (*) (evalexpr a) (evalexpr b) evalexpr (a :/: b) = if (evalexpr b) == return 0 nothing else liftm2 (/) (evalexpr a) (evalexpr b)
the changes using correct monadic (in case maybe
) type in const
case (return a
instead of a
) , when check 0 (if (evalexpr b) == return 0
rather if (evalexpr b) == 0
).
(not both have used just
rather return
because maybe monad using.)
by fixing type of evalexpr
able work out problems more easily. use of liftm2 makes haskell expressions quite general, hence many different versions of either compiled , didn't work expected or failed compile confusing messages. once type fixed compiler told me straight away const
case , ==
expression causing problems.
you may interested in using applicative functions <$>
, <*>
instead of liftm2
, these can used no matter how many arguments there are:
import control.applicative ((<$>), (<*>)) ... evalexpr :: (eq a, num a, fractional a) => (expr a) -> maybe evalexpr (const a) = return evalexpr (a :+: b) = (+) <$> (evalexpr a) <*> (evalexpr b) evalexpr (a :-: b) = (-) <$> (evalexpr a) <*> (evalexpr b) evalexpr (a :*: b) = (*) <$> (evalexpr a) <*> (evalexpr b) evalexpr (a :/: b) = if (evalexpr b) == return 0 nothing else (/) <$> (evalexpr a) <*> (evalexpr b)
if want lift function of 3 arguments, triad
say, is
triad <$> arg1 <*> arg2 <*> arg3
look applicative functors more info. there description here.
update
@gallais made point style of division case. wasn't going mention in interests of clarity, how it:
evalexpr :: (eq a, num a, fractional a) => (expr a) -> maybe evalexpr (const a) = return evalexpr (a :+: b) = (+) <$> (evalexpr a) <*> (evalexpr b) evalexpr (a :-: b) = (-) <$> (evalexpr a) <*> (evalexpr b) evalexpr (a :*: b) = (*) <$> (evalexpr a) <*> (evalexpr b) evalexpr (a :/: b) = (/) <$> (evalexpr a) <*> (failon 0 $ evalexpr b) failon x = case of x -> nothing _ ->
or more general version taking advantage of monadzero
class , taking function:
... evalexpr (a :/: b) = (/) <$> (evalexpr a) <*> (failon (==0) $ evalexpr b) failon f = b <- fmap f if b mzero else
there other monadic helper functions useful in cases this, such guard
, when
, unless
, see here.
Comments
Post a Comment