0

I am dealing with first order logic propositions expressions as objects:

data Prop 
    = Atom String
    | Var String
    | Pred String [Prop]
    | Not Prop
    | And [Prop]
    | Or [Prop]
    | Nil
    deriving (Show,Eq)

I take an expression as input in the form of a list of conjunctions or disjunctions in prefix form as such:

And [Or [Var "x",Var "y"],Or [Var "x",Or [Var "z",Nil]]]

What I should return in this case is a flattened expression that merges nested Ands or Ors together and eliminate any Nils so that it becomes in this form:

And [Or [Var "x",Var "y"],Or [Var "x",Var "z"]]

What I did is:

flatten :: Prop -> Prop
flatten (And[]) = Nil
flatten (And (v:(And w):x:(And y):z)) = And ([flatten(And (w ++ y))] ++ [flatten (And (v:x:z))])
flatten (And (x:(And y):z)) = And ([flatten(And (x:z))] ++ [flatten (And y)])
flatten (And (x:Nil:[])) = x

flatten (Or[]) = Nil
flatten (Or (v:(Or w):x:(Or y):z)) = Or ([flatten(Or (w ++ y))] ++ [flatten (Or (v:x:z))])
flatten (Or (x:(Or y):z)) = Or ([flatten(Or (x:z))] ++ [flatten (Or y)])
flatten (Or (x:Nil:[])) = Or([flatten(Or [x])])

flatten (And [x]) = And [flatten x]
flatten (Or [x]) = Or [flatten x]
flatten x = x

The code logic seems fine to me but it returns the expression in its original form and I'm not sure why exactly.

SalmaFG
  • 2,022
  • 3
  • 23
  • 36
  • I think you'd be better served by sorting these lists by constructor rather than complex pattern matching. You're changing the order anyway, it'd just make things simpler. – bheklilr Dec 04 '14 at 14:36
  • @bheklilr Could you please elaborate more? I'm not sure what you mean. – SalmaFG Dec 04 '14 at 14:49
  • `let x = Var "x"; y = Var "y"; z = Var "z"`; `And [And [x, y], Or [x, y], And [x, z]]` to `And [And [x, y], And [x, z], Or [x, y]]`. Then you wouldn't need all those pattern matches, you'd just need `flatten (And (And xs:And ys:zs)) = flatten $ And (And (xs ++ ys):zs)` if it's pre-sorted. – bheklilr Dec 04 '14 at 14:56

2 Answers2

2

I'd go about this differently.

flatten (And []) = Nil
flatten (And (h:t)) = case (flatten h, flatten $ And t) of
                        (Nil, t) -> t -- t is already flatten And t
                        (h, Nil) -> h -- And [h] == h
                        (And h, And t) -> And $ h++t
                        (And h, t) -> And $ h++[t]
                        (h, And t) -> And $ h:t
                        (h, t) -> And [h,t]
... the cases for And are covered; etc for Or

(flatten h, flatten $ And t) is the implementation of the assertion that And (h:t) == h && (And t). Using the tuple and case ... of permits to pattern-match to test all combinations of results of flattening h and And t.

The only combinations that need taking care of are the combinations involving Nil - which are based on my understanding of how you intended to interpret Nil - a discardable proposition, neutral for any operator - and the cases where one level of And can be eliminated.

Sassa NF
  • 5,306
  • 15
  • 22
  • I would probably put the last alternative as `And (t:[])`, just to be explicit. – MathematicalOrchid Dec 04 '14 at 15:13
  • The thing is, t could be an expression of its own so you have to flatten everything inside the main expression. This code doesn't do it. There are many possibilities and I am trying to cover them all but it's hard to find one or two expressions that do that. – SalmaFG Dec 04 '14 at 15:40
  • @MathematicalOrchid yes, I hasted. I'll add some more. – Sassa NF Dec 04 '14 at 15:45
  • @SalmaFG yes, I thought I'd miss something :) Now I am more sure: the flattening boils down to the rules for `Nil` and `And` - anything else is kept verbatim. – Sassa NF Dec 04 '14 at 15:53
  • The code is actually working and I'm not really sure how :D Because there aren't any recursive calls inside the case however it manages to go through the different cases. Why is that? – SalmaFG Dec 04 '14 at 16:09
  • 1
    @SalmaFG I don't know what you mean. There are recursive calls in `case` - see the tuple `(flatten h, flatten $ And t)`, which is pattern-matched to work through the combinations of results of flattening `h` and `t`. – Sassa NF Dec 04 '14 at 16:11
1

Are you sure, you need the Nil constructor? That's how flatten looks without it:

cutAndAppend :: Prop -> [Prop] -> [Prop]
cutAndAppend (And ps) qs = ps ++ qs
cutAndAppend  p       qs = p : qs

cutOrAppend :: Prop -> [Prop] -> [Prop]
cutOrAppend  (Or  ps) qs = ps ++ qs
cutOrAppend   p       qs = p : qs

flatten (Var v)  = Var v
flatten (Not p)  = Not (flatten p)
flatten (And ps) = And (foldr (cutAndAppend . flatten) [] ps)
flatten (Or  ps) = Or  (foldr (cutOrAppend  . flatten) [] ps)

Equivalently you can write

flatten (And ps) = And (foldr cutAndAppend [] $ map flatten ps)
flatten (Or  ps) = Or  (foldr cutOrAppend  [] $ map flatten ps)

i.e. flatten every p in ps, and, for example, in the Or case transform the list

[Var "x", Or [Var "y", And [Var "z"]], And [Var "x"]]

into

[Var "x", Var "y", And [Var "z"], And [Var "x"]]

But if you really want Nil, it's better to treat Nil as Nothing, because it's more idiomatic and there are some useful functions, acting on Maybes, in the standard library.

nonEmpty :: [a] -> Maybe [a]
nonEmpty [] = Nothing
nonEmpty xs = Just xs

flatten'  :: Prop -> Maybe Prop
flatten'  Nil     = Nothing
flatten' (Var v)  = Just (Var v)
flatten' (Not p)  = Not <$> flatten' p
flatten' (And ps) = And <$> nonEmpty (foldr cutAndAppend [] $ mapMaybe flatten' ps)
flatten' (Or  ps) = Or  <$> nonEmpty (foldr cutOrAppend  [] $ mapMaybe flatten' ps)

Nils are filtered by mapMaybe flatten'. Other stuff is the same. flatten then is simply

flatten  :: Prop -> Prop
flatten = fromMaybe Nil . flatten'

i.e. Nothing back to Nil and Just p to p.

Some examples:

vx = Var "x"; vy = Var "y"; vz = Var "z"  

main = mapM_ (print . flatten)
    [ And [Or [Nil], Nil, And [Nil]]
        -- Nil

    , And [Or [vx, vy], Or [vx, Or [vz, Nil]]]
        -- And [Or [Var "x",Var "y"],Or [Var "x",Var "z"]]

    , Or  [Or [vx, vy], Or [vx, Or [vz, Nil]]]
        -- Or [Var "x",Var "y",Var "x",Var "z"]

    , Not (And [And [Or [Or [Nil], Or [Not vx]], Nil, And [vy]]])
        -- Not (And [Or [Not (Var "x")],Var "y"])
    ]

EDIT

The first code doesn't remove expressions like Or [], and both the codes do not replace expressions like And [p] with p. It's not hard to recover this behavior, though. Relevant cases are:

smart :: ([Prop] -> Prop) -> [Prop] -> Maybe Prop
smart cons []  = Nothing
smart cons [x] = Just x
smart cons xs  = Just (cons xs)

flatten' (And ps) = smart And $ foldr cutAndAppend [] $ mapMaybe flatten' ps
flatten' (Or  ps) = smart Or  $ foldr cutOrAppend  [] $ mapMaybe flatten' ps

Some tests:

main = mapM_ (print . flatten)
    [ Not (And [And [Or [Or [Nil], Or [Not vx]], Nil, And [vy]]])
        -- Not (And [Not (Var "x"),Var "y"])

    , And []
        -- Nil 

    , Or [And [vx]]
        -- Var "x"

    , And [vx, Or []]
        -- Var "x"

    , And [Or [And [vx, Or [], vy, Nil]], And [vz, Or [vx]]]
        -- And [Var "x",Var "y",Var "z",Var "x"]
    ]

The whole code: http://ideone.com/0DwEae

A slightly modified version without Maybes: http://ideone.com/1yRDXo

effectfully
  • 12,325
  • 2
  • 17
  • 40
  • It doesn't look like your flattening (the first implementation) caters for degenerate cases, like `Or [And x] == And x` and `And [x, Or []] == And [x]`, etc – Sassa NF Dec 04 '14 at 22:01
  • @Sassa NF, thanks, I hadn't realized, that this is desired. Edited a bit. – effectfully Dec 04 '14 at 23:02