Default Behavior for Haskell Recursive Data Types

I am trying to write a propositional logic solver in Haskell. I represent logical expressions with a recursive data type called "Sentence", which has several subtypes for different operations - "AndSentence", "OrSentence", etc. Therefore, I assume that this is a tree with several node types, each of which has 0, 1, or 2 children.

It seems to work, but some of the code is repetitive, and I think it's best to express it. Basically, I have several functions in which the "default behavior" is to have the function recursively act on the child elements of the node, relying on certain types of node (usually these are "AtomicSentences", which are leaves). Therefore, I am writing a function such as:

imply_remove :: Sentence Symbol -> Sentence Symbol
imply_remove (ImplySentence s1 s2) = OrSentence (NotSentence (imply_remove s1)) (imply_remove s2)
imply_remove (AndSentence s1 s2) = AndSentence (imply_remove s1) (imply_remove s2)
imply_remove (OrSentence s1 s2) = OrSentence (imply_remove s1) (imply_remove s2)
imply_remove (NotSentence s1) = NotSentence (imply_remove s1)
imply_remove (AtomicSentence s1) = AtomicSentence s1

and I want a more concise way to write strings for "AndSentence", "OrSentence" and "NotSentence".

The functors seem to be similar to what I want, but it didn’t work ... I want to work on the subtrees and not on some values ​​contained in each node of the subtree.

Is there a proper way to do this? Or a more natural way to structure my data?

+4
3

, , , :

default_transformation :: (Sentence Symbol -> Sentence Symbol) -> Sentence Symbol -> Sentence Symbol
default_transformation f (ImplySentence s1 s2) = ImplySentence (f s1) (f s2)
default_transformation f (AndSentence s1 s2) = AndSentence (f s1) (f s2)
default_transformation f (OrSentence s1 s2) = OrSentence (f s1) (f s2)
default_transformation f (NotSentence s1) = NotSentence (f s1)
default_transformation f (AtomicSentence s1) = AtomicSentence s1

.

, , a , :

imply_remove :: Sentence Symbol -> Sentence Symbol
imply_remove (ImplySentence s1 s2) = OrSentence (NotSentence (imply_remove s1)) (imply_remove s2)
imply_remove s = default_transformation imply_remove s

, , , , - .

+3

recursion-schemes.

Sentence sym .

{-# LANGUAGE DeriveFunctor, LambdaCase #-}

import Data.Functor.Foldable  -- from the recursion-schemes package

-- The functor describing the recursive data type
data SentenceF sym r
   = AtomicSentence sym
   | ImplySentence r r
   | AndSentence r r
   | OrSentence r r
   | NotSentence r
   deriving (Functor, Show)

-- The original type recovered via a fixed point
type Sentence sym = Fix (SentenceF sym)

Sentence sym , , Fix. : (Constructor ...), Fix (Constructor ...). ,

type Symbol = String

-- A simple formula: not (p -> (p || q))
testSentence :: Sentence Symbol
testSentence = 
   Fix $ NotSentence $
      Fix $ ImplySentence
         (Fix $ AtomicSentence "p")
         (Fix $ OrSentence
            (Fix $ AtomicSentence "p")
            (Fix $ AtomicSentence "q"))

( Fix es).

-- The original code, adapted
imply_remove :: Sentence Symbol -> Sentence Symbol
imply_remove (Fix (ImplySentence s1 s2)) =
  Fix $ OrSentence (Fix $ NotSentence (imply_remove s1)) (imply_remove s2)
imply_remove (Fix (AndSentence s1 s2)) =
  Fix $ AndSentence (imply_remove s1) (imply_remove s2)
imply_remove (Fix (OrSentence s1 s2)) =
  Fix $ OrSentence (imply_remove s1) (imply_remove s2)
imply_remove (Fix (NotSentence s1)) =
  Fix $ NotSentence (imply_remove s1)
imply_remove (Fix (AtomicSentence s1)) =
  Fix $ AtomicSentence s1

, imply_remove testSentence: - , :

 -- Output: not ((not p) || (p || q))
 Fix (NotSentence
   (Fix (OrSentence
      (Fix (NotSentence (Fix (AtomicSentence "p"))))
      (Fix (OrSentence
         (Fix (AtomicSentence "p"))
         (Fix (AtomicSentence "q")))))))

, :

imply_remove2 :: Sentence Symbol -> Sentence Symbol
imply_remove2 = cata $ \case
   -- Rewrite ImplySentence as follows
   ImplySentence s1 s2 -> Fix $ OrSentence (Fix $ NotSentence s1) s2
   -- Keep everything else as it is (after it had been recursively processed)
   s -> Fix s

imply_remove2 testSentence, , .

cata? , , cata f, , .. ,

  • cata f
  • ( ) f,

- , . \case . cata ( Functor, ).

, , - recursion-schemes. cata , , (, , ).

+5

What you are looking for is called "universal programming" in Haskell: https://wiki.haskell.org/Generics ; the early form was called "Scrap Your Boilerplate", which you might also want on Google. I have not tested this, but I think if you use Uniplate Data.Generics.Uniplate and Data.Generics.Uniplate.Datamodules you can determine imply_removehow

imply_remove = transform w where
    w (ImplySentence s1 s2) = OrSentence (NotSentence s1) s2
    w s = s

transform performs recursion for you.

+3
source

All Articles