逻辑表达式评估者Haskell

时间:2015-06-16 23:45:42

标签: haskell

我编写了以下逻辑表达式求值程序。它适用于简单的2成员表达式,并且它会运行,但会为包含其他表达式的表达式产生错误,作为第二个/第一个成员。这是我的代码。

data Expression = Literal Bool | Operation Operator Expression Expression
data Operator = AND | OR

eval :: Expression -> Bool
eval (Literal x)                    = x
eval (Operation AND (Literal x) (Literal y))
 | x == True && y == True           = True
 | otherwise                        = False
eval (Operation OR (Literal x) (Literal y))
 | x == False && y == False         = False
 | otherwise                        = True

使用此输入调用时,它可以正常工作:

main = do
print $ eval (Operation OR (Literal False) (Literal False))

但是使用此输入调用时会产生错误:

main = do
print $ eval( Operation OR (Literal True) (Operation AND (Literal True) (Literal False)) )

2 个答案:

答案 0 :(得分:5)

你让eval有点过低。通过在签名中包含Literal s。更好的方法是使用递归:

eval :: Expression -> Bool
eval (Literal x) = x
eval (Operation AND x y) = (eval x) && (eval y)
eval (Operation OR x y) = (eval x) || (eval y)

换句话说,在右侧上调用eval。如果它是Literal,它将立即解析为正确的值,如果它是级联表达式,它也将解析Operation _ _ _

一般情况下,不建议启动级联模式匹配(好吧,有时它很有用)。在这种情况下,你至少应该问自己是否没有更优雅的解决方案。

这段代码很容易显示该函数是 total (无论输入如何,它总是会产生一个结果)。你的代码不是这样的。始终尝试执行整体检查

修改

如果Operation的数量会显着增加,您最好将问题分为handler :: Operation -> Bool -> Bool -> Bool函数和eval函数。类似的东西:

data Expression = Literal Bool | Operation Operator Expression Expression
data Operator = AND | OR | XOR

handler :: Operation -> Bool -> Bool -> Bool
handler AND = (&&)
handler OR = (||)
handler XOR = xor
    where xor True False = True
          xor False True = True
          xor _ _ = False

eval :: Expression -> Bool
eval (Literal x) = x
eval (Operation o x y) = (handler o) (eval x) (eval y)

如果你需要处理NOT,那就是另一种表达方式:

data Expression = Literal Bool | Operation Operator Expression Expression | OperationU OperatorU Expression

OperatorU这里是一元运算符。例如:

data OperatorU = ID | NOT

ID身份。现在,在这种情况下,您可以定义第二个处理程序:

handlerU :: OperatorU -> Bool -> Bool
handlerU ID = id
handlerU NOT = not

然后eval读取:

eval :: Expression -> Bool
eval (Literal x) = x
eval (Operation o x y) = (handler o) (eval x) (eval y)
eval (OperationU o x) = (handlerU o) (eval x)

答案 1 :(得分:1)

CommuSoft的优秀答案。我要对此进行重复,并采取更高层次/哲学的方式。

principle of compositionality

设计语言和口译员时应遵循的一个关键准则
  • 复杂表达的含义应该是其各部分的含义及其组合方式的函数。

在这种情况下,“含义”是表达式评估的Bool。适用于您的评估者,其组合性意味着:

  • 您的评估者只应查看子表达式的含义Bool评估结果),而不是语法

这个eval的等式违反了该规则,因为它在子表达式中“窥视”以检查Literal构造函数是否存在:

eval (Operation AND (Literal x) (Literal y))
 | x == True && y == True           = True
 | otherwise                        = False

确保你在组成上做事的一个有用的技巧是:

  1. 在语法树类型上编写通用折叠函数。 (有关此概念的一些指导,请参阅this question。)
  2. 根据折叠函数编写评估者。
  3. 在这种情况下:

    data Expression = Literal Bool | Operation Operator Expression Expression
    data Operator = AND | OR
    
    foldExpression :: (Bool -> r) -> (Operator -> r -> r -> r) -> r
    foldExpression f _ (Literal x) = f x
    foldExpression f g (Operation op l r) = g op (subfold l) (subfold r)
        where subfold = foldExpression f g 
    

    诀窍是当你使用foldExpression时,它会阻止你看到当前表达式下面的节点的构造函数,所以你被迫在结果类型r方面工作。所以这里:

    eval :: Expression -> Bool
    eval = foldExpression evalLiteral evalOperation
        where 
          -- A literal just evals to the `Bool` it carries.
          evalLiteral :: Bool -> Bool
          evalLiteral b = b
    
          evalOperation :: Operator -> Bool -> Bool -> Bool
    
          -- An `AND` operation evaluates to the `&&` of its subexpressions' values
          evalOperation (AND l r) = l && r
    
          -- An `OR` operation evaluates to the `||` of its subexpressions' values
          evalOperation (OR l r) = l || r
    

    并注意evalOperation与CommuSoft handler函数的相同之处。这只是将解决方案写成折叠而自然而然地消失了。

    稍微复杂一点,包括一元操作和大规模可理解性:

    data Unary = Not
    data Binary = And | Or | If
    data Expr = Lit Bool | Expr1 Unary Expr | Expr2 Binary Expr Expr
    
    -- Package all the functions used in a fold into a record
    -- so that we don't need to remember the argument order.
    -- For complex tree types with many types of nodes you will 
    -- want this!
    data ExprFold r 
        = ExprFold { literal :: Bool -> r
                   , unary :: Unary -> r -> r
                   , binary :: Binary -> r -> r -> r
                   }
    
    foldExpr :: ExprFold r -> Expr -> r
    foldExpr f (Lit b) = literal f b
    foldExpr f (Expr1 e) = unary f (foldExpr f e)
    foldExpr f (Expr2 e e') = binary f (foldExpr f e) (foldExpr f e')
    
    evaluator :: ExprFold Bool
    evaluator = ExprFold { literal = evalLit
                         , unary = evalExpr1
                         , binary = evalExpr2 }
        where 
          evalLit b = b
          evalExpr1 (Not b) = not b
          evalExpr2 (And b b') = b && b'
          evalExpr2 (Or b b') = b || b'
          evalExpr2 (If b b') = not b || b'