{-# LANGUAGE BangPatterns #-} module Algebra.Heyting.CounterExample where import Data.Set (Set) import qualified Data.Set as Set import Text.Printf (printf) import Algebra.Lattice (top) import Algebra.Lattice.Lifted (Lifted) import qualified Algebra.Lattice.Lifted as Lifted import Algebra.Lattice.Levitated (Levitated) import qualified Algebra.Lattice.Levitated as Levitated import Algebra.Lattice.Op (Op (..)) -- | A counter example type is a Heyting algebra; it useful for tests and -- properties. It records all failures. The truth value is represented by an -- empty set. Since @'CounterExample' e@ is a Heyting algebra, it is useful in -- expressing properties that require assumptions. -- type CounterExample a = Lifted (Op (Set a)) fmapCounterExample :: (Ord a, Ord b) => (a -> b) -> CounterExample a -> CounterExample b fmapCounterExample = fmap . fmap . Set.map -- | A bijection from @e@ to atoms of the @'CounterExample' e@ lattice. -- counterExample :: e -> CounterExample e counterExample e = Lifted.Lift (Op (Set.singleton e)) -- | A lattice homomorphism from @'Bool'@ to @'CounterExample'@, which lifts -- @'False'@ to an atom of @'CounterExample'@ (uniquely determined by @e@) and -- which preserves the top element. -- fromBool :: Ord e => e -> Bool -> CounterExample e fromBool e False = counterExample e fromBool _ True = top -- | -- A homomorphism of /Heyting algebras/. -- toBool :: CounterExample e -> Bool toBool (Lifted.Lift (Op s)) | Set.null s = True toBool _ = False -- | Note that this map is not a lattice homomorphism (it does not preserve -- meet nor join). It is also not a poset map in general. Nevertheless, it preserves -- @top@ and @bottom@. -- foldMapCounterExample :: (Ord e, Monoid m) => (e -> m) -> CounterExample e -> Levitated m foldMapCounterExample f (Lifted.Lift (Op s)) | Set.null s = Levitated.Top | otherwise = Levitated.Levitate (foldMap f s) foldMapCounterExample _ Lifted.Bottom = Levitated.Bottom -- | -- Map a CounterExample to @'Levitated' String@. Each set of counter example -- is concatenated into a single comma separated string. This is useful for -- printing counter examples in tests. See @'fromCounterExample''@. -- fromCounterExample :: Show a => CounterExample a -> Levitated String fromCounterExample Lifted.Bottom = Levitated.Bottom fromCounterExample (Lifted.Lift (Op s)) | Set.null s = Levitated.Top | otherwise = Levitated.Levitate (Set.foldl' go "" s) where go "" b = show b go !a b = printf "%s, %s" a (show b) -- | Map `CounterExample` to a Maybe, representing @'Levitated.Top'@ as -- @'Nothing'@ and mapping both @'Levitated.Levitate'@ and @'Levitate.Bottom'@ to -- a @Just String@. -- fromCounterExample' :: Show a => CounterExample a -> Maybe String fromCounterExample' ce = case fromCounterExample ce of Levitated.Top -> Nothing Levitated.Levitate s -> Just s Levitated.Bottom -> Just "" -- | Add a counter example. This is simply lifts the bottom to an atom given -- by @e@, otherwise it preserves the @'CounterExample' e@. -- -- Note that take join of @\e es = counterExample e \\/ es@ will return @top@ if -- @e@ if e is not in the set @es@; thus this map is not defined with using @\\/@. -- annotate :: Ord e => e -> CounterExample e -> CounterExample e annotate e Lifted.Bottom = counterExample e annotate _ es = es (===) :: (Ord e, Eq a) => a -> a -> CounterExample e a === b = if a == b then Lifted.Lift (Op Set.empty) else Lifted.Bottom infixr 4 ===