Haskell:如何递归地定义我的自定义数学数据类型(停止无限递归)

时间:2018-03-31 17:20:24

标签: haskell recursion types functional-programming pattern-matching

我正在定义一个自定义数据类型来帮助我完成项目的微积分。我已经按如下方式定义了这种数据类型:

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

我正在创建一个名为eval的函数,它为我部分评估数学表达式。这就是我所拥有的:

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))

这在大多数情况下都可以正常工作。例如,eval (Mult ((Const 4)) (Add (Cos (Const (0))) (Log (Const 1))))会产生(Const 4.0)

每当我添加一个带有两个常量的变量时,就会出现问题: eval (Add (Const 4) (Add (Const 4) (Var 'x')))给了我无限的递归。我已确定问题是因为我在eval上致电eval (Add a b) = eval (Add (eval a) (eval b))。如果我创建此行eval (Add a b) = (Add (eval a) (eval b)),我会停止无限递归,但我不再简化我的答案:Add (Const 4.0) (Add (Const 4.0) (Var 'x'))会产生完全相同的Add (Const 4.0) (Add (Const 4.0) (Var 'x'))。我如何获得Add (Const 8.0) (Var 'x')之类的内容?

非常感谢任何帮助!

1 个答案:

答案 0 :(得分:4)

您的问题是,您不会将已经简化的表达式与那些不简化的表达式区别对待。你继续调用eval,直到两个操作数都是常量,如果从未发生过,你永远不会终止。最简单的问题输入是Add (Var "x") (Const 5)。这样的输入应该结束递归并且只返回它自己。但相反,它将继续在同一输入上调用eval

  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

一般来说,首先避免这种问题的方法,即在你缺少一个基本案例时使其显而易见的方法是以你的函数的所有递归情况调用自身的方式构造你的函数。只有参数表达式的子表达式。

在这种情况下,可以通过首先计算操作数然后在不进行另一次递归调用的情况下对结果进行常量折叠来实现。这看起来像这样:

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

此处唯一的递归调用是eval aeval b,其中ab是原始表达式的子表达式。这可以保证终止,因为如果所有情况都遵循此规则,那么最终将达到没有子表达式的表达式,这意味着递归必须终止。