简体   繁体   English

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

[英]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. 我正在创建一个名为eval ,该函数为我部分评估数学表达式。 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) 例如, eval (Mult ((Const 4)) (Add (Cos (Const (0))) (Log (Const 1))))结果为(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. 每当我给变量添加两个常量时,就会出现我的问题: eval (Add (Const 4) (Add (Const 4) (Var 'x')))给我无限递归。 I have determined that the issue is because I call eval on eval (Add ab) = eval (Add (eval a) (eval b)) . 我确定问题是因为我在eval (Add ab) = eval (Add (eval a) (eval b))上调用了eval 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')) . 如果我做这行, eval (Add ab) = (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')) How do I get something like Add (Const 8.0) (Var 'x') instead? 如何获得类似Add (Const 8.0) (Var 'x')

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. 您一直调用eval直到两个操作数都是常量为止,如果这永远不会发生,就永远不会终止。 The most simple problematic input would be Add (Var "x") (Const 5) . 最简单的有问题的输入将是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

  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. 通常,首先避免此类问题的方法(即,在缺少基本案例的情况下使其显而易见)是按照以下方式构造函数:所有函数的递归案例仅使用sub来调用-参数表达式的表达式。

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. 在这里,唯一的递归调用是eval aeval b ,其中ab是原始表达式的子表达式。 This guarantees termination because if all cases follow this rule, you'll eventually reach expressions that have no subexpressions, meaning the recursion must terminate. 这样可以保证终止,因为如果所有情况都遵循此规则,则最终将到达没有子表达式的表达式,这意味着递归必须终止。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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