Cleaner Alternative to Extensive Pattern Matching in Haskell

后端 未结 5 1418
旧巷少年郎
旧巷少年郎 2021-02-07 01:13

Right now, I have some code that essentially works like this:

data Expression 
    = Literal Bool 
    | Variable String
    | Not Expression 
    | Or Expressio         


        
5条回答
  •  清歌不尽
    2021-02-07 01:53

    Basically the problem is that you have to write out simplify of the subexpressions in each clause, over and over again. It would be better to first get all the subexpressions done before even considering laws involving the top-level operator. One simple way is to add an auxiliary version of simplify, that doesn't recurse down:

    simplify :: Expression -> Expression
    simplify (Literal b) = Literal b
    simplify (Variable s) = Variable s
    simplify (Not e) = simplify' . Not $ simplify e
    simplify (And a b) = simplify' $ And (simplify a) (simplify b)
    simplify (Or a b) = simplify' $ Or (simplify a) (simplify b)
    
    simplify' :: Expression -> Expression
    simplify' (Not (Literal b)) = Literal $ not b
    simplify' (And (Literal False) _) = Literal False
    ...
    

    With the only small amount of operations you have in booleans, this is probably the most sensible approach. However with more operations, the duplication in simplify might still be worth to avoid. To that end, you can conflate the unary and binary operations to a common constructor:

    data Expression 
        = Literal Bool 
        | Variable String
        | BoolPrefix BoolPrefix Expression 
        | BoolInfix BoolInfix Expression Expression 
        deriving Eq
    
    data BoolPrefix = Negation
    data BoolInfix  = AndOp | OrOp
    

    and then you have just

    simplify (Literal b) = Literal b
    simplify (Variable s) = Variable s
    simplify (BoolPrefix bpf e) = simplify' . BoolPrefix bpf $ simplify e
    simplify (BoolInfix bifx a b) = simplify' $ BoolInfix bifx (simplify a) (simplify b)
    

    Obviously this makes simplify' more awkward though, so perhaps not such a good idea. You can however get around this syntactical overhead by defining specialised pattern synonyms:

    {-# LANGUAGE PatternSynonyms #-}
    
    pattern Not :: Expression -> Expression
    pattern Not x = BoolPrefix Negation x
    
    infixr 3 :∧
    pattern (:∧) :: Expression -> Expression -> Expression
    pattern a:∧b = BoolInfix AndOp a b
    
    infixr 2 :∨
    pattern (:∨) :: Expression -> Expression -> Expression
    pattern a:∨b = BoolInfix OrOp a b
    

    For that matter, perhaps also

    pattern F, T :: Expression
    pattern F = Literal False
    pattern T = Literal True
    

    With that, you can then write

    simplify' :: Expression -> Expression
    simplify' (Not (Literal b)) = Literal $ not b
    simplify' (F :∧ _) = F
    simplify' (_ :∧ F) = F
    simplify' (T :∨ _) = T
    simplify' (a :∧ Not b) | a==b  = T
    ...
    

    I should add a caveat though: when I tried something similar to those pattern synonyms, not for booleans but affine mappings, it made the compiler extremely slow. (Also, GHC-7.10 didn't yet support polymorphic pattern synonyms yet; this has changed quite a bit as of now.)


    Note also that all this will not generally yield the simplest possible form – for that, you'd need to find the fixed point of simplify.

提交回复
热议问题