Data structure for Propositional Logic formulas

Hi, I wrote this program to work with formulas in propositional logic: data Formula = | Variable Char | Not Formula | And Formula Formula | Or Formula Formula | Imply Formula Formula | Equivalent Formula Formula deriving (Eq) instance Show Formula where show (Variable v) = [v] show (Not f) = "~" ++ show f show (And f g) = "(" ++ show f ++ " & " ++ show g ++ ")" show (Or f g) = "(" ++ show f ++ " v " ++ show g ++ ")" show (Imply f g ) = "(" ++ show f ++ " -> " ++ show g ++ ")" show (Equivalent f g ) = "(" ++ show f ++ " <-> " ++ show g ++ ")" To make a formula you can do something like: ghci> let f = Or (Variable 'C') (And (Variable 'A') (Not (Variable 'C'))) ghci> f (C v (A & ~C)) So, my questions are: 1. Do you think this is an elegant solution or would you define custom operators (or, and, ->, <->) instead? Or something completely different? 2. If I pack this code into a module how would add a new logic connective like xor or nand to Formula? Meaning, can I add another type constructor to it? 3. Is there a way to "nest" data definitions, e.g. I would like to replace the 'Variable Char' type constructor by 'Literal Char', whereas a Literal is either a Variable or a negated Variable. Literals can be negated, too. Thanks a lot. Alex

On Wed, 12 Oct 2011 17:38:51 +0200, Alexander Raasch
Hi,
I wrote this program to work with formulas in propositional logic:
data Formula = | Variable Char | Not Formula | And Formula Formula | Or Formula Formula | Imply Formula Formula | Equivalent Formula Formula deriving (Eq)
It would be better to have Formula only include a few basic primitives (And, Or and Not is a popular choice), and define the rest in terms of those. More on that in the answer to your second question.
instance Show Formula where show (Variable v) = [v] show (Not f) = "~" ++ show f show (And f g) = "(" ++ show f ++ " & " ++ show g ++ ")" show (Or f g) = "(" ++ show f ++ " v " ++ show g ++ ")" show (Imply f g ) = "(" ++ show f ++ " -> " ++ show g ++ ")" show (Equivalent f g ) = "(" ++ show f ++ " <-> " ++ show g ++ ")"
To make a formula you can do something like:
ghci> let f = Or (Variable 'C') (And (Variable 'A') (Not (Variable 'C'))) ghci> f (C v (A & ~C))
So, my questions are:
1. Do you think this is an elegant solution or would you define custom operators (or, and, ->, <->) instead? Or something completely different?
You can still define such operators afterwards, and I think it would make formulas easier to read and write, for example: (<&&>) :: Formula -> Formula a <&&> b = And a b
2. If I pack this code into a module how would add a new logic connective like xor or nand to Formula? Meaning, can I add another type constructor to it?
If you chose a set of "primitives" capable of expressing every other formula, you can define xor, nand, etc. later as normal functions: nand :: Formula -> Formula -> Formula nand a b = Not (And a b)
3. Is there a way to "nest" data definitions, e.g. I would like to replace the 'Variable Char' type constructor by 'Literal Char', whereas a Literal is either a Variable or a negated Variable. Literals can be negated, too.
The parameters to constructors are not limited to built-in types and the type you are defining. So for your example, something like this would work: data Literal = Variable Char | NegVar Char data Formula = Literal | And ....... That would of course introduce some redundancy, since `Not (Variable c)' and `NegVar c' would represent the same formula. Cheers, Daniel

Hi Alex, since Daniel has done a nice job of actually answering your questions, I shall just note that I have a module similar to this available on Hackage as part of my hatt package, which generates truth tables for formulae in classical propositional logic. http://hackage.haskell.org/package/hatt http://hackage.haskell.org/packages/archive/hatt/1.3.0/doc/html/Data-Logic-P... Benedict

Am 12.10.2011 23:24, schrieb Benedict Eastaugh:
Hi Alex,
since Daniel has done a nice job of actually answering your questions, I shall just note that I have a module similar to this available on Hackage as part of my hatt package, which generates truth tables for formulae in classical propositional logic.
http://hackage.haskell.org/package/hatt http://hackage.haskell.org/packages/archive/hatt/1.3.0/doc/html/Data-Logic-P...
Why do most people like duplicate or repeated code? Instead of data Expr = Variable String | Negation Expr | Conjunction Expr Expr | Disjunction Expr Expr | Conditional Expr Expr | Biconditional Expr Expr you are usually better served by: data Expr = Variable String | Negation Expr | Junct BinOp Expr Expr data BinOp = Conjunction | Disjunction | Conditional | Biconditional Cheers Christian
Benedict

On 13 October 2011 10:12, Christian Maeder
Why do most people like duplicate or repeated code?
Hi Christian, I can't speak for most people, but I like my data types to encode the semantics of the domain. In this case, the connectives are logical devices that allow the formation of new well-formed formulae from old ones. I also think it's a mistake to consider them, in their guise as operators, to be any different from negation save in their arity. In my code they are on a par, and any extension to include new n-ary connectives would be straightforward and natural. (There are other unary and binary logical operators which happen not to be listed; I simply included the most commonly used ones, rather than add NAND, XOR, etc. As Daniel pointed out, merely including negation and disjunction allows the formation of a truth-functionally complete system. One can also do this with just NAND or NOR.) The duplication is, ultimately, fairly minor, so doing what you suggest also seems like overkill: it's not as though I'm going to be adding another five or ten connectives any time soon. Your version, while admittedly shorter, makes the code more complex and less readable, for no gain in expressiveness and a very minor improvement in brevity. Benedict

Am 13.10.2011 13:24, schrieb Benedict Eastaugh:
On 13 October 2011 10:12, Christian Maeder
wrote: Why do most people like duplicate or repeated code?
Hi Christian,
I can't speak for most people, but I like my data types to encode the semantics of the domain. In this case, the connectives are logical devices that allow the formation of new well-formed formulae from old ones. I also think it's a mistake to consider them, in their guise as operators, to be any different from negation save in their arity. In my code they are on a par, and any extension to include new n-ary connectives would be straightforward and natural.
(There are other unary and binary logical operators which happen not to be listed; I simply included the most commonly used ones, rather than add NAND, XOR, etc. As Daniel pointed out, merely including negation and disjunction allows the formation of a truth-functionally complete system. One can also do this with just NAND or NOR.)
The duplication is, ultimately, fairly minor, so doing what you suggest also seems like overkill: it's not as though I'm going to be adding another five or ten connectives any time soon. Your version, while admittedly shorter, makes the code more complex and less readable, for no gain in expressiveness and a very minor improvement in brevity.
I did (and do) not mean to offend you, but reading duplicated code is rather hard, because you'll have to take care if it is really the same code or if there are minor differences introduced on purpose or by (copy-paste) accident. Cheers Christian
Benedict

On Thu, Oct 13, 2011 at 5:39 PM, Christian Maeder
Am 13.10.2011 13:24, schrieb Benedict Eastaugh:
On 13 October 2011 10:12, Christian Maeder
> wrote:
Why do most people like duplicate or repeated code?
Hi Christian,
I can't speak for most people, but I like my data types to encode the semantics of the domain. In this case, the connectives are logical devices that allow the formation of new well-formed formulae from old ones. I also think it's a mistake to consider them, in their guise as operators, to be any different from negation save in their arity. In my code they are on a par, and any extension to include new n-ary connectives would be straightforward and natural.
(There are other unary and binary logical operators which happen not to be listed; I simply included the most commonly used ones, rather than add NAND, XOR, etc. As Daniel pointed out, merely including negation and disjunction allows the formation of a truth-functionally complete system. One can also do this with just NAND or NOR.)
The duplication is, ultimately, fairly minor, so doing what you suggest also seems like overkill: it's not as though I'm going to be adding another five or ten connectives any time soon. Your version, while admittedly shorter, makes the code more complex and less readable, for no gain in expressiveness and a very minor improvement in brevity.
I did (and do) not mean to offend you, but reading duplicated code is rather hard, because you'll have to take care if it is really the same code or if there are minor differences introduced on purpose or by (copy-paste) accident.
Cheers Christian
I was going to write saying that the problem is really with ghc not allowing 'bunched' GADT style defs like so: data Expr where Var :: String -> Expr Neg :: Expr -> Expr Conj, Disj, Cond, BiCond :: Expr -> Expr -> Expr But now I find it works. Is this something new in haskell 7?? Of course that does not change the fact that the OP's Expr will need 6 patterns for (total) functions of type Expr -> a. Christian's will need only 3 The 6 will of course not reduce for the 'bunched' newly supported type syntax [AFAIK]
participants (5)
-
Alexander Raasch
-
Benedict Eastaugh
-
Christian Maeder
-
Daniel Schoepe
-
Rustom Mody