简体   繁体   中英

Haskell: How to define my custom math data type recursively (stop infinite recursion)

I'm defining a custom data type to help me with my calculus for a project. I have defined this data type as follows:

data Math a =       
            Add (Math a) (Math a)
        |   Mult (Math a) (Math a)
        |   Cos (Math a)
        |   Sin (Math a)
        |   Log (Math a)
        |   Exp (Math a)
        |   Const a
        |   Var Char

    deriving Show

I am creating a function called eval that partially evaluates a mathematical expression for me. This is what I have:

eval (Const a) = (Const a)
eval (Var a) = (Var a)

eval (Add (Const a) (Const b)) = eval (Const (a+b))
eval (Add (Var a) b) = eval (Add (Var a) (eval b))
eval (Add a (Var b)) = eval (Add (eval a) (Var b))
eval (Add a b) = eval (Add (eval a) (eval b))

eval (Mult (Const a) (Const b)) = (Const (a*b))
eval (Mult a (Var b)) = (Mult (eval a) (Var b))
eval (Mult (Var a) b) = (Mult (Var a) (eval b))
eval (Mult a b) = eval (Mult (eval a) (eval b))

eval (Cos (Const a)) = (Const (cos(a)))
eval (Cos (Var a)) = (Cos (Var a))
eval (Cos a) = eval (Cos (eval a))

eval (Sin (Const a)) = (Const (sin(a)))
eval (Sin (Var a)) = (Sin (Var a))
eval (Sin a) = eval (Sin (eval a))

eval (Log (Const a)) = (Const (log(a)))
eval (Log (Var a)) = (Log (Var a))
eval (Log a) = eval (Log (eval a))

eval (Exp (Const a)) = (Const (exp(a)))
eval (Exp (Var a)) = (Exp (Var a))

This works fine for the most part. For instance, eval (Mult ((Const 4)) (Add (Cos (Const (0))) (Log (Const 1)))) results in (Const 4.0)

My problem arises whenever I have a variable added with two constants: eval (Add (Const 4) (Add (Const 4) (Var 'x'))) gives me infinite recursion. I have determined that the issue is because I call eval on eval (Add ab) = eval (Add (eval a) (eval b)) . If I make this line, eval (Add ab) = (Add (eval a) (eval b)) , I stop the infinite recursion, but I am no longer simplifying my answers: Add (Const 4.0) (Add (Const 4.0) (Var 'x')) results in the exact same Add (Const 4.0) (Add (Const 4.0) (Var 'x')) . How do I get something like Add (Const 8.0) (Var 'x') instead?

Any help would be much appreciated!

Your problem is that you don't treat expressions that are already simplified any differently than those that aren't. You keep calling eval until both operands are constants and if that never happens, you never terminate. The most simple problematic input would be Add (Var "x") (Const 5) . Such an input should end the recursion and just return itself. But instead it will keep calling eval on the same input:

  eval (Add (Var "x") (Const 5))
= eval (Add (Var "x") (eval (Const 5)))
= eval (Add (Var "x") (Const 5))
= eval (Add (Var "x") (eval (Const 5)))
= ... ad infinitum

In general the way to avoid this kind of problem in the first place, ie to make it obvious when you're missing a base case, is to structure your function in such a way that all recursive cases of your function call itself only with sub-expressions of the argument expression.

In this case that could be achieved by evaluating the operands first and then constant-folding the result without another recursive call. That would look like this:

eval (Add a b) =
  case (eval a, eval b) of
    (Const x, Const y) => Const (x+y)
    (x, y) => Add x y

Here the only recursive calls are eval a and eval b where a and b are subexpressions of the original expression. This guarantees termination because if all cases follow this rule, you'll eventually reach expressions that have no subexpressions, meaning the recursion must terminate.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM