valuations-0.0.3: src/Data/Valuation/DomainLattice.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# OPTIONS_GHC -Wall -Werror #-}
-- | A lattice structure on domains, as required by the presheaf formulation
-- of valuation algebras (Shenoy & Shafer, Kohlas, Abramsky & Carù).
--
-- A domain lattice provides:
--
-- * Join (\/) — combining domains (supremum)
-- * Meet (/\) — intersecting domains (infimum)
-- * Partial order — domain inclusion, with incomparable elements
module Data.Valuation.DomainLattice
( DomainLattice (..),
DomainLattice',
HasDomainLattice (..),
AsDomainLattice (..),
runDomainJoin,
runDomainMeet,
runDomainCompare,
runDomainLeq,
setDomainLattice,
-- * laws
lawJoinAssociative,
lawMeetAssociative,
lawJoinCommutative,
lawMeetCommutative,
lawAbsorption1,
lawAbsorption2,
lawJoinIdempotent,
lawMeetIdempotent,
lawLeqFromJoin,
)
where
import Control.Lens (Lens', Prism', review, view)
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Valuation.PartialOrder
( HasPartialOrder (..),
PartialOrder,
fromLeq,
partialOrderLeq,
runPartialOrder,
)
import Data.Valuation.Semigroup
( Semigroup,
applySemigroup,
runSemigroup,
)
import Prelude hiding (Semigroup)
-- $setup
-- >>> :set -Wno-name-shadowing -Wno-type-defaults
-- |
-- >>> import qualified Data.Set as Set
-- >>> let lat = setDomainLattice :: DomainLattice (->) (Set Int) (Set Int)
-- >>> runDomainJoin lat (Set.fromList [1,2]) (Set.fromList [2,3])
-- fromList [1,2,3]
-- >>> runDomainMeet lat (Set.fromList [1,2]) (Set.fromList [2,3])
-- fromList [2]
-- >>> runDomainLeq lat (Set.fromList [1]) (Set.fromList [1,2])
-- True
-- >>> runDomainLeq lat (Set.fromList [1,3]) (Set.fromList [1,2])
-- False
-- >>> runDomainCompare lat (Set.fromList [1,2]) (Set.fromList [2,3])
-- Nothing
data DomainLattice p sg o
= DomainLattice
-- | join (\/ / supremum)
(Semigroup p sg)
-- | meet (/\ / infimum)
(Semigroup p sg)
-- | partial order
(PartialOrder o)
type DomainLattice' x =
DomainLattice (->) x x
-- | Classy lens for types that contain a 'DomainLattice'.
class HasDomainLattice c p sg o | c -> p sg o where
domainLattice :: Lens' c (DomainLattice p sg o)
domainLatticeJoin :: Lens' c (Semigroup p sg)
domainLatticeJoin = domainLattice . domainLatticeJoin
domainLatticeMeet :: Lens' c (Semigroup p sg)
domainLatticeMeet = domainLattice . domainLatticeMeet
instance HasDomainLattice (DomainLattice p sg o) p sg o where
domainLattice = id
domainLatticeJoin f (DomainLattice j m o) = fmap (\j' -> DomainLattice j' m o) (f j)
domainLatticeMeet f (DomainLattice j m o) = fmap (\m' -> DomainLattice j m' o) (f m)
-- | Classy prism for types that can be constructed from a 'DomainLattice'.
class AsDomainLattice c p sg o | c -> p sg o where
_DomainLattice :: Prism' c (DomainLattice p sg o)
instance AsDomainLattice (DomainLattice p sg o) p sg o where
_DomainLattice = id
instance HasPartialOrder (DomainLattice p sg o) o where
partialOrder f (DomainLattice j m o) = fmap (DomainLattice j m) (f o)
-- | Apply the domain join (\/): the supremum of two domains.
{-# SPECIALIZE runDomainJoin ::
DomainLattice (->) sg o -> sg -> sg -> sg
#-}
runDomainJoin :: (HasDomainLattice lat (->) sg o) => lat -> sg -> sg -> sg
runDomainJoin = runSemigroup . view domainLatticeJoin
-- | Apply the domain meet (/\): the infimum of two domains.
{-# SPECIALIZE runDomainMeet ::
DomainLattice (->) sg o -> sg -> sg -> sg
#-}
runDomainMeet :: (HasDomainLattice lat (->) sg o) => lat -> sg -> sg -> sg
runDomainMeet = runSemigroup . view domainLatticeMeet
-- | Compare two domains using the partial order.
-- Returns 'Nothing' for incomparable elements.
--
-- >>> import qualified Data.Set as Set
-- >>> runDomainCompare (setDomainLattice :: DomainLattice (->) (Set Int) (Set Int)) (Set.fromList [1]) (Set.fromList [1,2])
-- Just LT
-- >>> runDomainCompare (setDomainLattice :: DomainLattice (->) (Set Int) (Set Int)) (Set.fromList [1,2]) (Set.fromList [1,2])
-- Just EQ
-- >>> runDomainCompare (setDomainLattice :: DomainLattice (->) (Set Int) (Set Int)) (Set.fromList [1,2]) (Set.fromList [2,3])
-- Nothing
{-# SPECIALIZE runDomainCompare ::
DomainLattice p sg o -> o -> o -> Maybe Ordering
#-}
runDomainCompare :: (HasPartialOrder lat p) => lat -> p -> p -> Maybe Ordering
runDomainCompare = runPartialOrder . view partialOrder
-- | Test the domain partial order: @runDomainLeq lat d1 d2@ is 'True' iff @d1 <= d2@.
-- Returns 'False' for incomparable elements.
--
-- >>> import qualified Data.Set as Set
-- >>> runDomainLeq (setDomainLattice :: DomainLattice (->) (Set Int) (Set Int)) (Set.fromList [1]) (Set.fromList [1,2])
-- True
-- >>> runDomainLeq (setDomainLattice :: DomainLattice (->) (Set Int) (Set Int)) (Set.fromList [1,2]) (Set.fromList [2,3])
-- False
{-# SPECIALIZE runDomainLeq ::
DomainLattice p sg o -> o -> o -> Bool
#-}
runDomainLeq :: (HasPartialOrder lat p) => lat -> p -> p -> Bool
runDomainLeq = partialOrderLeq . view partialOrder
-- | The canonical 'DomainLattice' for 'Set', with union as join,
-- intersection as meet, and subset as the partial order.
--
-- >>> import qualified Data.Set as Set
-- >>> let lat = setDomainLattice :: DomainLattice (->) (Set String) (Set String)
-- >>> runDomainJoin lat (Set.fromList ["x","y"]) (Set.fromList ["y","z"])
-- fromList ["x","y","z"]
-- >>> runDomainMeet lat (Set.fromList ["x","y"]) (Set.fromList ["y","z"])
-- fromList ["y"]
-- >>> runDomainLeq lat (Set.fromList ["x"]) (Set.fromList ["x","y"])
-- True
-- >>> runDomainCompare lat (Set.fromList ["x","y"]) (Set.fromList ["y","z"])
-- Nothing
setDomainLattice :: (Ord a) => DomainLattice' (Set a)
setDomainLattice =
DomainLattice
(review applySemigroup Set.union)
(review applySemigroup Set.intersection)
(fromLeq Set.isSubsetOf)
-- |
-- >>> import qualified Data.Set as Set
-- >>> lawJoinAssociative (setDomainLattice :: DomainLattice (->) (Set Int) (Set Int)) (Set.fromList [1,2]) (Set.fromList [2,3]) (Set.fromList [3,4])
-- True
lawJoinAssociative :: (Eq sg) => DomainLattice (->) sg o -> sg -> sg -> sg -> Bool
lawJoinAssociative lat a b c =
let j = runDomainJoin lat
in j (j a b) c == j a (j b c)
-- |
-- >>> import qualified Data.Set as Set
-- >>> lawMeetAssociative (setDomainLattice :: DomainLattice (->) (Set Int) (Set Int)) (Set.fromList [1,2]) (Set.fromList [2,3]) (Set.fromList [3,4])
-- True
lawMeetAssociative :: (Eq sg) => DomainLattice (->) sg o -> sg -> sg -> sg -> Bool
lawMeetAssociative lat a b c =
let m = runDomainMeet lat
in m (m a b) c == m a (m b c)
-- |
-- >>> import qualified Data.Set as Set
-- >>> lawJoinCommutative (setDomainLattice :: DomainLattice (->) (Set Int) (Set Int)) (Set.fromList [1,2]) (Set.fromList [2,3])
-- True
lawJoinCommutative :: (Eq sg) => DomainLattice (->) sg o -> sg -> sg -> Bool
lawJoinCommutative lat a b =
runDomainJoin lat a b == runDomainJoin lat b a
-- |
-- >>> import qualified Data.Set as Set
-- >>> lawMeetCommutative (setDomainLattice :: DomainLattice (->) (Set Int) (Set Int)) (Set.fromList [1,2]) (Set.fromList [2,3])
-- True
lawMeetCommutative :: (Eq sg) => DomainLattice (->) sg o -> sg -> sg -> Bool
lawMeetCommutative lat a b =
runDomainMeet lat a b == runDomainMeet lat b a
-- | Absorption law 1: @a \/ (a /\ b) = a@.
--
-- >>> import qualified Data.Set as Set
-- >>> lawAbsorption1 (setDomainLattice :: DomainLattice (->) (Set Int) (Set Int)) (Set.fromList [1,2]) (Set.fromList [2,3])
-- True
lawAbsorption1 :: (Eq sg) => DomainLattice (->) sg o -> sg -> sg -> Bool
lawAbsorption1 lat a b =
runDomainJoin lat a (runDomainMeet lat a b) == a
-- | Absorption law 2: @a /\ (a \/ b) = a@.
--
-- >>> import qualified Data.Set as Set
-- >>> lawAbsorption2 (setDomainLattice :: DomainLattice (->) (Set Int) (Set Int)) (Set.fromList [1,2]) (Set.fromList [2,3])
-- True
lawAbsorption2 :: (Eq sg) => DomainLattice (->) sg o -> sg -> sg -> Bool
lawAbsorption2 lat a b =
runDomainMeet lat a (runDomainJoin lat a b) == a
-- | Join idempotence: @a \/ a = a@.
--
-- >>> import qualified Data.Set as Set
-- >>> lawJoinIdempotent (setDomainLattice :: DomainLattice (->) (Set Int) (Set Int)) (Set.fromList [1,2])
-- True
lawJoinIdempotent :: (Eq sg) => DomainLattice (->) sg o -> sg -> Bool
lawJoinIdempotent lat a =
runDomainJoin lat a a == a
-- | Meet idempotence: @a /\ a = a@.
--
-- >>> import qualified Data.Set as Set
-- >>> lawMeetIdempotent (setDomainLattice :: DomainLattice (->) (Set Int) (Set Int)) (Set.fromList [1,2])
-- True
lawMeetIdempotent :: (Eq sg) => DomainLattice (->) sg o -> sg -> Bool
lawMeetIdempotent lat a =
runDomainMeet lat a a == a
-- | Consistency of partial order with join: @a <= b@ iff @a \/ b = b@.
--
-- >>> import qualified Data.Set as Set
-- >>> lawLeqFromJoin (setDomainLattice :: DomainLattice (->) (Set Int) (Set Int)) (Set.fromList [1]) (Set.fromList [1,2])
-- True
-- >>> lawLeqFromJoin (setDomainLattice :: DomainLattice (->) (Set Int) (Set Int)) (Set.fromList [1,3]) (Set.fromList [1,2])
-- True
lawLeqFromJoin :: (Eq d) => DomainLattice' d -> d -> d -> Bool
lawLeqFromJoin lat a b =
runDomainLeq lat a b == (runDomainJoin lat a b == b)