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?