在Haskell中递归修改数据结构的各个部分

时间:2011-05-31 21:08:22

标签: data-structures haskell referential-transparency

大家好我是Haskell的新手,我想创建一个可以应用DeMorgan关于逻辑表达式的法则的Haskell程序。问题是我无法将给定表达式更改为新表达式(在应用DeMorgan定律后)

具体来说,这是我的数据结构

data LogicalExpression = Var Char
        | Neg LogicalExpression
        | Conj LogicalExpression LogicalExpression
        | Disj LogicalExpression LogicalExpression
        | Impli LogicalExpression LogicalExpression
        deriving(Show)

我想创建一个函数,它接受“LogicalExpression”并在应用DeMorgan定律后返回“LogicalExpression”。

例如,每当我在logicalExpression中找到这种模式:Neg(Conj(Var'a')(Var'b'))时,我需要将其转换为Conj(Neg(Var'a')Neg(Var') b'))。

这个想法很简单,但是在haskell中很难实现,就像试图创建一个函数(让我们称之为Z)来搜索x并将其转换为y,所以如果Z被赋予“vx”它会转换它仅仅“vy”而不是数据结构“logicalExpression”中的字符串而不是x它采用我提到的模式并再次吐出整个logicalExpression但模式已更改。

P.S:我希望该函数采用任何 复杂 逻辑表达式,并使用DeMorgan的定律简化它

任何提示?

提前致谢。

4 个答案:

答案 0 :(得分:7)

卢克(luqui)提出了可能是最优雅的方式来思考这个问题。但是,他的编码要求您手动为每个要创建的重写规则获取大量遍历。

来自A Pattern for Almost Composable Functions的Bjorn Bringert的作品可以使这更容易,特别是如果你需要编写多个这样的规范化传递。它通常用Applicatives或2级类型编写,但为了简单起见,我将推迟:

根据您的数据类型

data LogicalExpression
    = Var Char
    | Neg LogicalExpression
    | Conj LogicalExpression LogicalExpression
    | Disj LogicalExpression LogicalExpression
    | Impl LogicalExpression LogicalExpression
deriving (Show)

我们可以定义一个用于搜索非顶级子表达式的类:

class Compos a where
    compos' :: (a -> a) -> a -> a

instance Compos LogicalExpression where
    compos' f (Neg e)    = Neg (f e)
    compos' f (Conj a b) = Conj (f a) (f b)
    compos' f (Disj a b) = Disj (f a) (f b)
    compos' f (Impl a b) = Impl (f a) (f b)
    compos' _ t = t

例如,我们可以消除所有影响:

elimImpl :: LogicalExpression -> LogicalExpression
elimImpl (Impl a b) = Disj (Not (elimImpl a)) (elimImpl b)
elimImpl t = compos' elimImpl t -- search deeper

然后我们可以应用它,就像luqui在上面所做的那样,寻找否定的连词和析取。而且,正如卢克指出的那样,在一次通过中做你所有的否定分布可能更好,我们还将包括否定蕴涵和双否定消除的归一化,产生否定正规形式的公式(假设我们已经消除了暗示)

nnf :: LogicalExpression -> LogicalExpression
nnf (Neg (Conj a b)) = Disj (nnf (Neg a)) (nnf (Neg b))
nnf (Neg (Disj a b)) = Conj (nnf (Neg a)) (nnf (Neg b))
nnf (Neg (Neg a))    = nnf a
nnf t                = compos' nnf t -- search and replace

键是最后一行,表示如果上述其他情况都不匹配,请搜索可以应用此规则的子表达式。此外,由于我们将Neg推入到术语中,然后对这些术语进行规范化,因此您应该只在叶子处使用否定变量,因为Neg在另一个构造函数之前的所有其他情况将被标准化。

将使用更高级的版本

import Control.Applicative
import Control.Monad.Identity

class Compos a where
    compos :: Applicative f => (a -> f a) -> a -> f a

compos' :: Compos a => (a -> a) -> a -> a
compos' f = runIdentity . compos (Identity . f) 

instance Compos LogicalExpression where
    compos f (Neg e)    = Neg <$> f e
    compos f (Conj a b) = Conj <$> f a <*> f b
    compos f (Disj a b) = Disj <$> f a <*> f b
    compos f (Impl a b) = Impl <$> f a <*> f b
    compos _ t = pure t

这对您的特定情况没有帮助,但如果您需要返回多个重写结果,执行IO或以其他方式在重写规则中进行更复杂的活动,这将非常有用。

您可能需要使用此功能,例如,您希望尝试将deMorgan法律应用于其应用位置的任何子集,而不是追求正常形式。

请注意,无论您正在重写哪种函数,在遍历期间使用的是适用的,甚至是信息流的方向性,compos定义只需要为每种数据类型提供一次。

答案 1 :(得分:6)

如果我理解正确,你想要应用De Morgan的定律尽可能地将否定推入树中。你必须多次明确地递归树:

-- no need to call self on the top-level structure,
-- since deMorgan is never applicable to its own result
deMorgan (Neg (a `Conj` b))  =  (deMorgan $ Neg a) `Disj` (deMorgan $ Neg b)
deMorgan (Neg (a `Disj` b))  =  (deMorgan $ Neg a) `Conj` (deMorgan $ Neg b)
deMorgan (Neg a)             =  Neg $ deMorgan a
deMorgan (a `Conj` b)        =  (deMorgan a) `Conj` (deMorgan b)
-- ... etc.

term-rewriting system中所有这些都会容易得多,但这不是Haskell的原因。

(顺便说一下,如果你在公式解析器中将P -> Q翻译成not P or Q并删除Impli构造函数,生活会变得容易得多。公式中每个函数的个案数变为小。)

答案 2 :(得分:4)

其他人给予了很好的指导。但我会将其称为否定消除器,因此这意味着你有:

deMorgan (Neg (Var x)) = Neg (Var x)
deMorgan (Neg (Neg a)) = deMorgan a
deMorgan (Neg (Conj a b)) = Disj (deMorgan (Neg a)) (deMorgan (Neg b))
-- ... etc. match Neg against every constructor
deMorgan (Conj a b) = Conj (deMorgan a) (deMorgan b)
-- ... etc. just apply deMorgan to subterms not starting with Neg

我们可以通过归纳看到,在结果中,Neg只会应用于Var个字词,最多只能应用一次。

我喜欢将这样的转换视为消除器:即试图通过推卸它们来“摆脱”顶级某个构造函数的东西。将要消除的构造函数与每个内部构造函数(包括其自身)进行匹配,然后转发其余部分。例如,lambda演算评估器是Apply消除器。 SKI转换器是Lambda消除器。

答案 3 :(得分:2)

重点是deMorgan的递归应用程序。它与(例如)完全不同:

 deMorgan' z@(Var x) = z
 deMorgan' (Neg (Conj x y)) = (Disj (Neg x) (Neg y))
 deMorgan' (Neg (Disj x y)) = (Conj (Neg x) (Neg y))
 deMorgan' z@(Neg x) = z
 deMorgan' (Conj x y) = Conj x y
 deMorgan' (Disj x y) = Disj x y

不起作用:

 let var <- (Conj (Disj (Var 'A') (Var 'B')) (Neg (Disj (Var 'D') (Var 'E'))))
 *Main> deMorgan' var
 Conj (Disj (Var 'A') (Var 'B')) (Neg (Disj (Var 'D') (Var 'E')))

这里的问题是你在subexpressiosn(x和ys)中应用转换。