Haskell中逻辑表达式的数据结构

时间:2015-02-02 21:03:18

标签: haskell data-structures recursive-datastructures

我尝试创建一个用于处理逻辑表达式的数据结构。乍一看,逻辑表达式看起来像Trees,因此从树中组成它似乎是合理的:

data Property a = And (Property a) (Property a) |
                 Or  (Property a) (Property a) |
                 Not (Property a) | Property a   deriving (Show,Eq)

但这不是一个好主意,因为我的树的左右分支之间确实没有区别,因为A && B == B && A

嗯,也许List甚至更好?

data Property a = Empty | And a (Property a) | Or a (Property a) | Not a (Property a)  deriving Show

但在这种情况下,我无法真正形成'逻辑树',只能形成'逻辑列表'。 所以我需要一个类似于Tree但没有左右“分支”的数据结构。

3 个答案:

答案 0 :(得分:10)

我们将遵循Daniel Wagner的优秀建议并使用“天真的代表”(您的第一个代表),以及选择一种着名的正常形式的功能"。我们将使用algebraic normal form有两个原因。主要原因是代数正规形式不需要枚举Property中保存的变量类型的所有可能值。代数范式也很简单。

代数范式

我们将使用newtype ANF a = ANF [[a]]表示不导出其构造函数的代数正规形式。每个内部列表都是其所有谓词的连接(和);如果内部列表为空,则始终为真。外部列表是其所有谓词的独占或排序;如果它是空的那就是假的。

module Logic (
    ANF (getANF),
    anf,
    ...
)

newtype ANF a = ANF {getANF :: [[a]]}
  deriving (Eq, Ord, Show)

我们将努力使我们构建的任何ANF都是规范的。我们将构建ANF s,以便内部列表始终排序且唯一。外部列表也将始终排序。如果外部列表中有两个(或偶数个)相同的项目,我们将删除它们。如果外部列表中存在奇数个相同的项目,我们将删除除其中一个之外的所有项目。

使用Data.List.Ordered包中data-ordlist的函数,我们可以清除表示连词xor-ing的列表列表到ANF,列表已排序并重复删除。

import qualified Data.List.Ordered as Ordered

anf :: Ord a => [[a]] -> ANF a
anf = ANF . nubPairs . map Ordered.nubSort

nubPairs :: Ord a => [a] -> [a]
nubPairs = removePairs . Ordered.sort
    where
        removePairs (x:y:zs)
            | x == y    = removePairs zs
            | otherwise = x:removePairs (y:zs)
        removePairs xs = xs

逻辑表达式形成boolean algebra,它是一个带有额外分配律和补充(否定)的有界点阵。利用BoundedLattice包中现有的lattices类,我们可以定义BooleanAlgebra s的类

import Algebra.Lattice

class (BoundedLattice a) => BooleanAlgebra a where
    complement :: a -> a
    -- Additional Laws:
    -- a `join` complement a == top
    -- a `meet` complement a == bottom
    -- a `join` (b `meet` c) == (a `join` b) `meet` (a `join` c)
    -- a `meet` (b `join` c) == (a `meet` b) `join` (a `meet` c)
每当ANF aBooleanAlgebra个实例时,

a形成Ord,以便我们可以将ANF保持为规范形式。

布尔代数的meet是逻辑and。逻辑and分布在xor上。我们将利用内部列表已经排序以快速将它们合并在一起的事实。

instance (Ord a) => MeetSemiLattice (ANF a) where
    ANF xs `meet` ANF ys = ANF (nubPairs [Ordered.union x y | x <- xs, y <- ys])

使用De Morgan's lawsjoin或逻辑or可以使用meet或逻辑and来撰写。

instance (Ord a) => JoinSemiLattice (ANF a) where
    xs `join` ys = complement (complement xs `meet` complement ys)

格子的top始终为真。

instance (Ord a) => BoundedMeetSemiLattice (ANF a) where
    top = ANF [[]]

格子的bottom始终为假。

instance (Ord a) => BoundedJoinSemiLattice (ANF a) where
    bottom = ANF []

逻辑and和逻辑or符合晶格吸收定律a join (a meet b) == a meet (a join b) == a

instance (Ord a) => Lattice (ANF a)
instance (Ord a) => BoundedLattice (ANF a)

最后,complement操作是否定的,可以通过xor来实现。

instance (Ord a) => BooleanAlgebra (ANF a) where
    complement (ANF ([]:xs)) = ANF xs
    complement (ANF xs)      = ANF ([]:xs)

Pointed一起,我们可以使用BooleanAlgebra来定义包含逻辑表达式的事物类Logical

import Data.Pointed

class Logical f where
    toLogic :: (Pointed g, BooleanAlgebra (g a)) => f a -> g a

代数范式包含逻辑表达式。

xor :: BooleanAlgebra a => a -> a -> a
p `xor` q = (p `join` q) `meet` complement (p `meet` q)

instance Logical ANF where
    toLogic = foldr xor bottom . map (foldr meet top . map point) . getANF

代数法线形式也是Pointed,因此可以转换为使用toLogic

instance Pointed ANF where
    point x = ANF [[x]]

toANF :: (Logical f, Ord a) => f a -> ANF a
toANF = toLogic

我们可以通过比较来判断是否有Logical在逻辑上是等价的,看它在转换为规范代数范式时是否在结构上是等价的。

equivalent :: (Logical f, Logical g, Ord a) => f a -> g a -> Bool
equivalent a b = toANF a == toANF b

将属性转换为ANF

逻辑表达式应该形成一个布尔代数,它是一个带有附加分配律和补充(否定)的有界点。为了使Property更接近于布尔代数,我们需要为格子的topbottom边界添加两个元素。 top始终为Truebottom始终为False。对于始终为Trivial的属性,我将为TrueImpossible的属性调用这些False

data Property a
    = And (Property a) (Property a)
    | Or  (Property a) (Property a)
    | Not (Property a)
    | Property a
    | Trivial
    | Impossible
  deriving (Eq, Ord, Read, Show)

Property是属性的抽象语法树。它派生的EqOrd实例只是结构上的相等。

PropertyLogical,我们可以将其转换为Pointed BooleanAlgebra

instance Logical Property where
    toLogic (And a b)    = (toLogic a) `meet` (toLogic b)
    toLogic (Or  a b)    = (toLogic a) `join` (toLogic b)
    toLogic (Not a)      = complement (toLogic a)
    toLogic (Property a) = point a
    toLogic Trivial      = top
    toLogic Impossible   = bottom

使用上一节中的equivalent和我们的Logical实例,我们可以检查两个属性是否与某些示例相同。

runExample :: (Ord a, Show a) => Property a -> Property a -> IO ()
runExample p q = 
    if p `equivalent` q
    then putStrLn (show p ++ " == " ++ show q)
    else putStrLn (show p ++ " /= " ++ show q)

main = do
    runExample (point 'a' `meet` point 'b') (point 'b' `meet` point 'a')
    runExample (point 'a' `meet` point 'b') (point 'b' `meet` point 'a' `meet` point 'a')
    runExample (point 'a' `meet` point 'b') (point 'b' `meet` point 'a' `join` point 'a')
    runExample (point 'a' `join` complement (point 'a')) (top)
    runExample (point 'a' `meet` complement (point 'a')) (bottom)

这会产生以下输出。

And (Property 'a') (Property 'b') == And (Property 'b') (Property 'a')
And (Property 'a') (Property 'b') == And (And (Property 'b') (Property 'a')) (Pr
operty 'a')
And (Property 'a') (Property 'b') /= Or (And (Property 'b') (Property 'a')) (Pro
perty 'a')
Or (Property 'a') (Not (Property 'a')) == Trivial
And (Property 'a') (Not (Property 'a')) == Impossible

要使用这些示例,我们还需要BooleanAlgebra的{​​{1}}和Pointed个实例。 Property定律的等价关系是等价的解释,而不是BooleanAlgebra的结构平等。

Property

证明

每个布尔函数,以及每个有限instance MeetSemiLattice (Property a) where meet = And instance BoundedMeetSemiLattice (Property a) where top = Trivial instance JoinSemiLattice (Property a) where join = Or instance BoundedJoinSemiLattice (Property a) where bottom = Impossible instance Lattice (Property a) instance BoundedLattice (Property a) instance BooleanAlgebra (Property a) where complement = Not instance Pointed Property where point = Property ,在Property中都有唯一表示。 ANF的{​​{1}}和BooleanAlgebra个实例证明,所有Pointed,以及ANF索引的每个有限Logical a和布尔函数都有Property a中的表示。设aANF a的居民数量。 k布尔变量有a个可能的布尔函数。 2^(2^k)的每个内部列表都是一组k s;有ANF个可能的a个集合,因此有2^k个可能的内部列表。 a的外部列表是一组内部列表;每个内部列表最多可以在外部列表中出现一次。可能有2^k ANF个。由于每个布尔函数都在2^(2^k)中有一个表示,并且只有ANF a的可能居民与布尔函数一样多,因此每个布尔函数在{{{}中都有唯一表示。 1}}。

声明

ANF的{​​{1}}实例是一个结构顺序,除了相等之外,与晶格结构引起的偏序无关。

代数法线形式可以指数大于其输入。 ANF变量列表的分离大小为ANF。例如,OrdANFn包含.5*n*2^nlength . getANF . foldr join bottom . map point $ ['a'..'g']个不同变量的出现次数。

答案 1 :(得分:1)

我建议使用SAT / SMT解算器进行等效性检查。通常,这些类型的检查可能非常昂贵(NP完全),并且任何类型的正常形式的转换都可能导致表示的指数爆炸。 SAT / SMT求解器具有用于处理此类问题的自定义算法,并且最好使用这样的求解器。翻译Property的两个实例并询问它们在所有变量赋值下是否相同将是微不足道的。 SBV库(https://hackage.haskell.org/package/sbv)可用于从高级Haskell进行转换。以下问题的答案有关于如何执行此操作的一些详细信息:SAT solving with haskell SBV library: how to generate a predicate from a parsed string?

答案 2 :(得分:0)

如果要反映逻辑连接词&&)和的关联性,请使用关联的数据结构,如list:< / p>

data Property a = And [Property a] | ...

如果您还想要交换性(A && B == B && A),请使用Data.Set

我记得这个方便的图表,如果你想要某些属性,可以使用哪些类型:

+-----------+-----------+----------+----------
|Associative|Commutative|Idempotent|   type
+-----------+-----------+----------+----------
|(a+b)+c=   |  a+b=b+a  |  a+a=a   |
|    a+(b+c)|           |          |
+-----------+-----------+----------+----------
|    no     |    no     |    no    |binary trees
|    no     |   yes     |    no    | “mobiles”
|   yes     |    no     |    no    |lists (arrays)
|   yes     |   yes     |    no    |multisets (bags)
|   yes     |   yes     |   yes    |sets
+-----------+-----------+----------+----------

Guy Steele's Lecture的第51页。