2
\$\begingroup\$

I'm writing a program to differentiate functions (perform basic calculus) in Haskell. The program works, but there's one function that looks very ugly to me:

--some prerequisite definitions
--a mathematical value is either...
data Value =
 Literal Double --a number, such as 4, -17.5, or 2.33e27 
 | Variable Symbol (Maybe Double) --a variable, which may or may not have a known value
 | FCall --a function call, consisting of a function and two arguments
 {
 func :: Function,
 arg1 :: Value,
 arg2 :: Value
 }
 deriving (Eq)
--simplifies a mathematical value into a simpler form.
reduce :: Value -> Value
reduce val@(Literal _) = val --literals cannot be simplified
reduce (Variable _ (Just val)) = Literal val --bound variables can be simplified to literals
reduce var@(Variable _ Nothing) = var --unbound variables cannot be simplified
reduce fc@(FCall f a b) = --simplifying functions is messy...
 if isDefined f ma mb
 then matchLiterals ma mb
 else retval
 where ra = reduce a --reduce simplifies a Value to a Value
 rb = reduce b
 ma = eval ra --eval converts known values to Doubles and unknown Values to Nothing
 mb = eval rb
 retval = FCall f ra rb --returned in case of failure
 matchLiterals (Just da) (Just db) = Literal $ (impl f) da db
 matchLiterals _ _ = matchCase $ findCase (cases f) ra rb
 matchCase (Just sc) = evalCase sc ra rb
 matchCase Nothing = retval

The multitude of local functions defined in the where clause seems excessive, but I can't find a better way to handle the flow of execution. I've tried to break this up into several smaller functions, but since most of these can fail (evaluate to Nothing), I still have to use pattern matching to handle the results of these functions.

The flow of logic goes like this:

if the function is defined for the given arguments:
 if the arguments have definite known values:
 evaluate and return the function call
 else:
 look for special cases (adding 0, multiplying/diving by 1, etc.)
 if a special case was found:
 evaluate and return the result of applying the special case to the arguments
 else:
 return another FCall with possibly simplified arguments
else:
 return another FCall with possibly simplified arguments

It's possible I'm fretting over nothing (hah) and this code is perfectly fine, but it seems that this is not the optimal way to handle these conditions. Can anyone offer any advice about how I might better structure this code?

200_success
146k22 gold badges190 silver badges479 bronze badges
asked Apr 20, 2014 at 22:25
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

It is a matter of taste but I think reduce will be a bit more cleaner with case.

reduce exp = case exp of
 Literal{} -> exp
 Variable _ (Just var) -> Literal var
 Variable _ Nothing -> exp
 FCall f a b
 | isDefined f ma mb -> matchLiterals ma mb
 | otherwise -> FCall f ra rb
 where ra = reduce a
 rb = reduce b
 ma = eval ra
 mb = eval rb

Literal{} is special pattern syntax that will still work even if you add more arguments to Literal.

To reduce duplication (just a bit) in where, you can use on function from Data.Function

on :: (b -> b -> c) -> (a -> b) -> a -> a -> c

it is like (.) but allows to apply function to both arguments

(ra,rb) = on (,) reduce a b
(ma,mb) = on (,) eval ra rb
answered Apr 21, 2014 at 8:14
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Also, on is often used infix, e.g f `on` g rather than on f g \$\endgroup\$ Commented May 2, 2014 at 21:36

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.