我编写了以下逻辑表达式求值程序。它适用于简单的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)) )
答案 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
确保你在组成上做事的一个有用的技巧是:
在这种情况下:
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'