【问题标题】:logical expression evaluator Haskell逻辑表达式求值器 Haskell
【发布时间】:2015-09-02 00:14:12
【问题描述】:

我写了下面的逻辑表达式求值器。它适用于简单的 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)) )

【问题讨论】:

  • 您应该在问题中说明“错误”。对于嵌套表达式(例如(Operation OR (Operation AND (Lit True) (Lit False)) (Lit False))),一个问题会立即跳出“模式匹配失败”。

标签: haskell


【解决方案1】:

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

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(不管输入如何,它总会产生一个结果)。您的代码并非如此。始终尝试执行全面检查

编辑

如果Operations 的数量会显着增加,您最好将关注点分成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 个参数的 NOT 怎么办?
【解决方案2】:

CommuSoft 的出色回答。我将对此进行反复讨论,并进行更高级/哲学的讨论。

在为它们设计语言和解释器时要遵循的一个关键准则是principle of compositionality

  • 复杂表达式的含义应该是其各部分含义及其组合方式的函数。

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

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

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

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

一个有用的技巧可以确保你以组合方式做事:

  1. 在语法树类型上编写通用折叠函数。 (有关此概念的一些指导,请参阅 this question。)
  2. 根据折叠功能编写您的评估器。

在这种情况下:

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'

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-12-08
    • 2010-10-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-06
    • 2015-03-04
    • 2015-04-01
    相关资源
    最近更新 更多