rest-rewrite-0.4.5: src/Language/REST/WQOConstraints/Strict.hs
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- | This module defines an implemenation for representing constraints on a 'WQO';
-- in this case represented by a set of "extendable" WQOs each satisfying the constraints.
-- For more details see 'StrictOC'
module Language.REST.WQOConstraints.Strict (
strictOC
, strictOC'
, difference
, isUnsatisfiable
, noConstraints
, permits
, StrictOC
) where
import Control.Monad.Identity
import GHC.Generics (Generic)
import Data.Hashable
import Data.Maybe
import qualified Data.List as L
import qualified Data.Set as S
import qualified Language.REST.WQOConstraints as OC
import qualified Language.REST.Internal.WQO as WQO
type WQO = WQO.WQO
-- Represents a set of constraints on a WQO on type `a`
-- The constraints are represented as a set ws of WQOs
-- The constraints permit any WQO w that is a valid extension of some (w' in wqos)
-- | @StrictOC ws@ represents constraints on a WQO. Each element of @ws@ is a WQO
-- that satisfies the constraints. @StrictOC ws@ permits a WQO @w@ if there exists
-- a @w'@ in @ws@ such that @w'@ can be extended to yield @w@.
--
-- This implementation is similar to disjunctive normal form representation of
-- logical formulas; except in this case each "conjunction" is a valid WQO, and thus
-- "satisfiable". Therefore @StrictOC ws@ satisfies /some/ WQO iff @ws@ is not empty.
--
-- Two potential downsides to this implementation are:
-- 1. The size of @ws@ can grow quickly; an inherent issue of DNF
-- 2. Related, calculating the entire set @ws@ is computationally expensive,
-- and often unnecessary for RESTs use-case, where continuing the path only
-- requires knowing if /any/ WQO is permitted.
newtype StrictOC a = StrictOC (S.Set (WQO a))
deriving (Eq, Ord, Generic, Hashable)
instance (Show a, Eq a, Ord a, Hashable a) => Show (StrictOC a) where
show (StrictOC cs) | S.null cs = "unsatisfiable"
show (StrictOC cs) | S.member WQO.empty cs = "no constraints"
show (StrictOC cs) = L.intercalate " ∨ \n" (map show (S.toList cs))
getOrdering :: StrictOC a -> Maybe (WQO a)
getOrdering (StrictOC o) =
listToMaybe (S.toList o)
-- | Constraints that permit any 'WQO'. In this case implemented by
-- a singleton set containing an empty WQO.
noConstraints :: forall a. (Eq a, Ord a, Hashable a) => StrictOC a
noConstraints = StrictOC (S.singleton WQO.empty)
unsatisfiable :: StrictOC a
unsatisfiable = StrictOC S.empty
-- | Returns @true@ iff @strictOC ws@ does not permit any WQOs; i.e., if @ws@ is empty.
isUnsatisfiable :: Eq a => StrictOC a -> Bool
isUnsatisfiable c = c == unsatisfiable
isSatisfiable :: Eq a => StrictOC a -> Bool
isSatisfiable c = c /= unsatisfiable
notStrongerThan :: forall m a. (Monad m, Eq a, Ord a, Hashable a) => StrictOC a -> StrictOC a -> m Bool
notStrongerThan (StrictOC _lhs) (StrictOC _rhs) = return False
-- The difference of two constraints `a` and `b` is new constraints such that
-- intersect (diff a b) b = a
difference :: (Eq a, Ord a, Hashable a) => StrictOC a -> StrictOC a -> StrictOC a
difference (StrictOC lhs) (StrictOC rhs) =
StrictOC (S.difference lhs rhs)
-- The union of two constraints `a` and `b` is new constraints that only
-- permits an ordering if permitted by either `a` or `b`
union :: (Eq a, Ord a, Hashable a) => StrictOC a -> StrictOC a -> StrictOC a
union (StrictOC lhs) (StrictOC rhs) =
fromSet $ S.union lhs rhs
fromSet :: (Eq a, Ord a, Hashable a) => S.Set (WQO a) -> StrictOC a
fromSet oc = -- StrictOC oc
StrictOC $ go [] (L.sortOn (length . WQO.elems) $ S.toList oc)
where
go include [] = S.fromList include
go include (x : xs) =
if any (`WQO.notStrongerThan` x) (include ++ xs)
then go include xs
else go (x : include) xs
-- | The intersection of two constraints `a` and `b` is new constraints that only
-- permits the orderings permitted by both `a` and `b`
intersect :: (Show a, Eq a, Ord a, Hashable a) => StrictOC a -> StrictOC a -> StrictOC a
intersect (StrictOC lhs) (StrictOC rhs) = result
-- trace (printf "%s intersect %s yields %s" (show lhs) (show rhs) (show result)) result
where
result = fromSet $ S.fromList $
do
lhs' <- S.toList lhs
rhs' <- S.toList rhs
maybeToList (WQO.merge lhs' rhs')
addConstraint :: (Eq a, Ord a, Hashable a) => WQO a -> StrictOC a -> StrictOC a
addConstraint c (StrictOC oc) = StrictOC $ S.fromList $ do
c' <- S.toList oc
maybeToList $ WQO.merge c c'
-- | @StrictOC ws@ permits a 'WQO' @w@ if there exists a @w'@ in @ws@
-- that can be extended to equal @w@
permits :: (Eq a, Ord a, Hashable a) => StrictOC a -> WQO a -> Bool
permits (StrictOC permitted) desired =
any (`WQO.notStrongerThan` desired) (S.toList permitted)
-- | An implementation of 'StrictOC'; for any computational context
strictOC :: Monad m => OC.WQOConstraints StrictOC m
strictOC = OC.OC
addConstraint
intersect
(return . isSatisfiable)
notStrongerThan
noConstraints
permits
union
unsatisfiable
getOrdering
-- | An implementation of 'StrictOC' in the 'Identity' monad; usable in pure
-- computations.
strictOC' :: OC.WQOConstraints StrictOC Identity
strictOC' = strictOC