Functional fun with logical expressions

Here's a quickly thrown-together version of "Fun with logical expressions", written in Haskell:

{-# LANGUAGE LambdaCase #-}

module Main (main) where
import Control.Applicative (Applicative (..), (<$>))
import Control.Arrow       (first)
import Data.Ix             (inRange)
import Control.Monad       (guard, mplus, msum)
import Data.Maybe          (fromMaybe, isJust, listToMaybe)

data RawExpr
    = VarR Char
    | N RawExpr
    | D RawExpr RawExpr
    | C RawExpr RawExpr
    | I RawExpr RawExpr
    | E RawExpr RawExpr
    deriving (Eq, Ord, Read, Show)

newtype Parser a = Parser{ unParser :: String -> (a, String) }

instance Functor Parser where
    fmap f (Parser p) = Parser (first f . p)

instance Applicative Parser where
    pure x = Parser (\s -> (x, s))
    Parser f <*> Parser x = Parser (\s ->
        let (f', s')  = f s
            (x', s'') = x s'
        in (f' x', s''))

instance Monad Parser where
    return = pure
    Parser x >>= f = Parser (\s ->
        let (x', s') = x s
        in unParser (f x') s')

anyChar :: Parser Char
anyChar = Parser (\(c : cs) -> (c, cs))

parseRaw :: String -> RawExpr
parseRaw s = case unParser expr s of
    (x, "")   -> x
    (_, rest) -> error ("parseRaw: trailing garbage: " ++ show rest)
    where
    expr = anyChar >>= \case
        c | inRange ('a', 'z') c -> pure (VarR c)
        'N' -> N <$> expr
        'D' -> D <$> expr <*> expr
        'C' -> C <$> expr <*> expr
        'I' -> I <$> expr <*> expr
        'E' -> E <$> expr <*> expr

data ModExpr
    = VarM Char
    | T
    | F
    | Not ModExpr
    | Or [ModExpr]
    deriving (Eq, Ord, Read, Show)

desugar :: RawExpr -> ModExpr
desugar = \case
    VarR v -> VarM v
    N x    -> not (desugar x)
    D x y  -> or  (desugar x) (desugar y)
    C x y  -> and (desugar x) (desugar y)
    I x y  -> or (not (desugar x)) (desugar y)
    E x y  -> or (and x' y') (and (not x') (not y'))
        where
        x' = desugar x
        y' = desugar y
    where
    not     = Not
    or  x y = Or [x, y]
    and x y = not (or (not x) (not y))

render :: ModExpr -> String
render e = go e "" where
    go = \case
        VarM c -> (c :)
        T      -> ('1' :)
        F      -> ('0' :)
        Not x  -> ('~' :) . go x
        Or xs  -> ('V' :) . foldr (.) id (map go xs) . (';' :)

type Rule = ModExpr -> Maybe ModExpr

mkRule :: (Maybe ModExpr -> Maybe ModExpr) -> Rule
mkRule t = rule
    where
    rule e = t (Just e) `mplus` rec
        where
        rec = case e of
            Not x -> Not <$> rule x
            Or xs -> do
                let ys = map rule xs
                guard (any isJust ys)
                return (Or (zipWith fromMaybe xs ys))
            _ -> Nothing

not_not, not_true, not_false :: Rule
not_not = mkRule $ \e -> do
    Not (Not x) <- e
    pure x
not_true = mkRule $ \e -> do
    Not T <- e
    pure F
not_false = mkRule $ \e -> do
    Not F <- e
    pure T

or_true, or_false :: Rule
or_true = mkRule $ \e -> do
    Or xs <- e
    guard (T `elem` xs)
    pure T
or_false = mkRule $ \e -> do
    Or xs <- e
    let ys = filter (F /=) xs
    guard (length ys < length xs)
    pure $ Or ys

first3 :: (a -> r) -> (a, b, c) -> (r, b, c)
first3 f (x, y, z) = (f x, y, z)

second3 :: (b -> r) -> (a, b, c) -> (a, r, c)
second3 f (x, y, z) = (x, f y, z)

selectSplit :: [a] -> [([a], a, [a])]
selectSplit []       = []
selectSplit (x : xs) = ([], x, xs) : map (first3 (x :)) (selectSplit xs)

extract :: (a -> Maybe b) -> [a] -> Maybe ([a], b, [a])
extract f xs = msum (map (evert . second3 f) (selectSplit xs))
    where
    evert (x, Just y, z) = Just (x, y, z)
    evert _              = Nothing

or_none, or_one, or_assoc :: Rule
or_none = mkRule $ \e -> do
    Or [] <- e
    pure F
or_one = mkRule $ \e -> do
    Or [x] <- e
    pure x
or_assoc = mkRule $ \e -> do
    Or xs <- e
    (pre, ys, post) <- extractOr xs
    pure $ Or (pre ++ ys ++ post)
    where
    extractOr = extract (\case Or xs -> Just xs; _ -> Nothing)

isSubExpr :: ModExpr -> ModExpr -> Bool
isSubExpr e1 e2 =
    e1 == e2 || case e2 of
        Not x -> e1 `isSubExpr` x
        Or xs -> any (e1 `isSubExpr`) xs
        _ -> False

substitute :: ModExpr -> ModExpr -> ModExpr -> ModExpr
substitute x y = go
    where
    go = \case
        e | e == x -> y
        Not e      -> Not (go e)
        Or es      -> Or (map go es)
        e          -> e

or_spec :: Rule
or_spec = mkRule $ \e -> do
    Or xs <- e
    listToMaybe $ do
        (pre, x_orig, post) <- selectSplit xs
        (Not x, r) <- [(x_orig, T), (Not x_orig, F)]
        guard $ any (x `isSubExpr`) (pre ++ post)
        let z = Or (map (substitute x r) pre ++ [x_orig] ++ map (substitute x r) post)
        pure z

rules :: [Rule]
rules =
    [
        not_not, not_true, not_false,
        or_true, or_false,
        or_none, or_one, or_assoc,
        or_spec
    ]

simplify1 :: ModExpr -> Maybe ModExpr
simplify1 e = msum (map ($ e) rules)

simplify :: ModExpr -> (ModExpr, [String])
simplify e = case simplify1 e of
    Nothing -> (e, [])
    Just e' -> (render e' :) <$> simplify e'

process :: String -> String
process s = unlines $
    ("ORIG: " ++ s) :
    ("MOD: " ++ render e) :
    foldr (\x z -> ("...> " ++ x) : z) [v, ""] ts
    where
    e = desugar (parseRaw s)
    (r, ts) = simplify e
    v | r == T    = "Tautology"
      | otherwise = "Not a tautology"

main :: IO ()
main = interact (unlines . map process . lines)

Leave a comment

About mauke

user-pic I blog about Perl.