packages feed

compaREST-0.1.0.0: src/Data/OpenApi/Compare/Validate/Schema/DNF.hs

module Data.OpenApi.Compare.Validate.Schema.DNF
  ( DNF (..),
    Disjunct (..),
    pattern SingleDisjunct,
    pattern TopDNF,
    pattern BottomDNF,
    pattern LiteralDNF,
    foldDNF,
    forDNF,
  )
where

import Algebra.Lattice
import Control.Applicative
import Data.Foldable
import qualified Data.Set as S

-- | A boolean formula (without "not") represented as a Disjunctive Normal Form:
-- the formula is a disjunction of a set of clauses, each of which is a
-- conjunction of a set of some elementary formulas.
-- Invariant: no two disjuncts imply eachother
newtype DNF a = DNF (S.Set (Disjunct a))
  deriving stock (Eq, Ord, Show)

-- A disjunct is a thing that is to be disjuncted. Itself it is a conjunction of some literals.
newtype Disjunct a = Disjunct (S.Set a)
  deriving stock (Eq, Ord, Show)

disjImplies :: Ord a => Disjunct a -> Disjunct a -> Bool
disjImplies (Disjunct xs) (Disjunct ys) = ys `S.isSubsetOf` xs

disjConjunction :: Ord a => Disjunct a -> Disjunct a -> Disjunct a
disjConjunction (Disjunct xs) (Disjunct ys) = Disjunct $ xs `S.union` ys

disjAdd :: Ord a => DNF a -> Disjunct a -> DNF a
disjAdd (DNF yss) xs
  | any (xs `disjImplies`) yss = DNF yss
  | otherwise = DNF $ S.insert xs $ S.filter (not . (`disjImplies` xs)) yss

instance Ord a => Lattice (DNF a) where
  xss \/ DNF yss = S.foldl' disjAdd xss yss
  DNF xss /\ DNF yss =
    foldl' disjAdd bottom $
      liftA2 disjConjunction (S.toList xss) (S.toList yss)

pattern BottomDNF :: DNF a
pattern BottomDNF <-
  DNF (S.null -> True)
  where
    BottomDNF = DNF S.empty

isSingleton :: S.Set a -> Maybe a
isSingleton s
  | S.size s == 1 = S.lookupMin s
  | otherwise = Nothing

pattern SingleDisjunct :: Ord a => Disjunct a -> DNF a
pattern SingleDisjunct xs <-
  DNF (isSingleton -> Just xs)
  where
    SingleDisjunct xs = DNF $ S.singleton xs

pattern TopDNF :: DNF a
pattern TopDNF <-
  DNF (isSingleton -> Just (Disjunct (S.null -> True)))
  where
    TopDNF = DNF $ S.singleton $ Disjunct S.empty

pattern LiteralDNF :: Ord a => a -> DNF a
pattern LiteralDNF x <-
  SingleDisjunct (Disjunct (isSingleton -> Just x))
  where
    LiteralDNF x = SingleDisjunct $ Disjunct $ S.singleton x

instance Ord a => BoundedJoinSemiLattice (DNF a) where
  bottom = BottomDNF

instance Ord a => BoundedMeetSemiLattice (DNF a) where
  top = TopDNF

foldDisjunct :: BoundedMeetSemiLattice l => (a -> l) -> Disjunct a -> l
foldDisjunct f (Disjunct xs) = S.foldl' (\y l -> y /\ f l) top xs

foldDNF :: BoundedLattice l => (a -> l) -> DNF a -> l
foldDNF f (DNF xss) = S.foldl' (\y xs -> y \/ foldDisjunct f xs) bottom xss

newtype LiftA f a = LiftA {getLiftA :: f a}
  deriving newtype (Functor, Applicative)

instance (Lattice a, Applicative f) => Lattice (LiftA f a) where
  (/\) = liftA2 (/\)
  (\/) = liftA2 (\/)

instance (BoundedJoinSemiLattice a, Applicative f) => BoundedJoinSemiLattice (LiftA f a) where
  bottom = pure bottom

instance (BoundedMeetSemiLattice a, Applicative f) => BoundedMeetSemiLattice (LiftA f a) where
  top = pure top

forDNF :: (BoundedLattice l, Applicative f) => (a -> f l) -> DNF a -> f l
forDNF f = getLiftA . foldDNF (LiftA . f)