packages feed

rest-rewrite-0.4.5: src/Language/REST/WQOConstraints.hs

{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE FlexibleContexts #-}

-- | This module includes a typeclass for implementations of constraints on 'WQO's
module Language.REST.WQOConstraints
  (
    WQOConstraints(..)
  , ConstraintGen
  , liftC
  , cmapConstraints
  , isUnsatisfiable
  , intersectAll
  , unionAll
  , intersectRelation
  , runStateConstraints
  , singleton
  )  where

import Control.Monad.Identity
import Control.Monad.State.Strict
import qualified Data.List as L
import Data.Hashable

import Prelude hiding (GT, EQ)

import qualified Language.REST.Internal.WQO as WQO
import Language.REST.Types
import Language.REST.SMT (ToSMTVar)

type WQO = WQO.WQO

-- | @WQOConstraints impl m@ defines an implementation for tracking and checking
--   satisfiability of constraints on arbitrary type @a@. Namely, instances of
--   @impl a@ are used to keep track of constraints. Satisfiability checking and
--   other computations are embedded in a computational context @m@.
data WQOConstraints impl m = OC
  {
    -- | @addConstraint wqo c@ adds constraints to @c@ to also permit the WQO @w@.
    addConstraint       :: forall a. (Eq a, Ord a, Hashable a) => WQO a -> impl a -> impl a

    -- | @intersect c1 c2@ returns constraints to permit only WQOs permitted by both @c1@ and
    --   @c2@. Therefore the resulting constraints are stronger (less likely to be
    --   satisifiable).
  , intersect           :: forall a. (Show a, Eq a, Ord a, Hashable a) => impl a -> impl a -> impl a
    -- | @isSatisfiable c@ returns true iff @c@ permits any WQO
  , isSatisfiable       :: forall a. (ToSMTVar a Int, Show a, Eq a, Ord a, Hashable a) => impl a -> m Bool

    -- | @c1 `notStrongerThan` c2@ iff any ordering permitted by @c1@ is also permitted
    --   by @c2@
  , notStrongerThan     :: forall a. (ToSMTVar a Int, Eq a, Ord a, Hashable a) => impl a -> impl a -> m Bool
    -- | @noConstraints@ returns an instance of constraints that permits any WQO
  , noConstraints       :: forall a. (Eq a, Ord a, Hashable a) => impl a

  , permits             :: forall a. (Show a, Eq a, Ord a, Hashable a) => impl a -> WQO a -> Bool
    -- | @c1 `union` c2@ returns constraints that permit WQOs permitted by /either/
    --  @c1@ or @c2@. The resulting constraints are therefore weaker (more likely to
    --  be satisfiable)
  , union               :: forall a. (Eq a, Ord a, Hashable a) => impl a -> impl a -> impl a

    -- | @unsatisfiable@ returns an instance of constraints that does not permit any WQO
  , unsatisfiable       :: forall a. impl a

    -- | @getOrdering c@ returns a concrete ordering satisfying the constraints, if one exists
  , getOrdering         :: forall a. impl a -> Maybe (WQO a)
  }

-- | Returns true iff the constraints do not permit any WQO.
isUnsatisfiable :: (Functor m, ToSMTVar a Int, Show a, Eq a, Ord a, Hashable a) => WQOConstraints oc m -> oc a -> m Bool
isUnsatisfiable OC{isSatisfiable} c = not <$> isSatisfiable c

-- | Returns the constraints that permit a given WQO
singleton :: (Eq a, Ord a, Hashable a) => WQOConstraints oc m -> WQO a -> oc a
singleton OC{addConstraint, noConstraints} c = addConstraint c noConstraints

-- | Given a list of constraints @ocs@, returns constraints that permit only the WQOs
--   permitted by each @oc@ in @ocs@
intersectAll :: (Eq a, Ord a, Hashable a, Show a, Show (oc a)) => WQOConstraints oc m -> [oc a] -> oc a
intersectAll OC{noConstraints} []     = noConstraints
intersectAll OC{intersect}     (x:xs) = L.foldl' intersect x xs

-- | Given a list of constraints @ocs@, returns constraints that permit the WQOs
--   permitted by any @oc@ in @ocs@
unionAll :: (Eq a, Ord a, Hashable a, Show a, Show (oc a)) => WQOConstraints oc m -> [oc a] -> oc a
unionAll OC{unsatisfiable} []     = unsatisfiable
unionAll OC{union}         (x:xs) = L.foldl' union x xs

-- | @intersectRelation oc impl (f, g, r)@ strengthens constraints represented by @impl@
--   to also ensure that @f@ and @g@ are related via relation @r@ in permitted WQOs.
intersectRelation ::
  (Ord a, Eq a, Ord a, Hashable a, Show a) =>
  WQOConstraints oc m -> oc a -> (a, a, Relation) -> oc a
intersectRelation oc impl (f, g, r) =
  case nc r of
    Just impl' -> intersect oc impl impl'
    Nothing    -> unsatisfiable oc
  where
    nc GT  = fmap (singleton oc) (WQO.singleton (f, g, WQO.QGT))
    nc EQ  = fmap (singleton oc) (WQO.singleton (f, g, WQO.QEQ))
    nc GTE = do
      wqo1 <- WQO.singleton (f, g, WQO.QGT)
      wqo2 <- WQO.singleton (f, g, WQO.QEQ)
      return $ union oc (singleton oc wqo1) (singleton oc wqo2)

-- | ConstraintGen impl R >= t u returns the constraints on >= that guarantee
-- the resulting relation >=', we have:
--   1. x >= y implies x >=' y
--   2. t lift(R(>=')) u
-- Where R generates { == , >=, > } from the underlying ordering
-- R is used to enable optimizations
type ConstraintGen oc base lifted m =
  forall m' . (WQOConstraints oc m' -> Relation -> oc base -> lifted -> lifted -> m (oc base))

-- | @cmapConstraints@ takes a transformation @f@ from @lifted' to lifted@, and transforms
-- a constraint generator on terms of types @lifted@ into one on terms of types @lifted'@
cmapConstraints :: (lifted' -> lifted) -> ConstraintGen oc base lifted m -> ConstraintGen oc base lifted' m
cmapConstraints f cgen impl r oc t u = cgen impl r oc (f t) (f u)

-- | @liftc f imp@ lifts the computations of @imp@ from context @m@ to context @m'@
liftC :: (m Bool  -> m' Bool) -> WQOConstraints impl m -> WQOConstraints impl m'
liftC f oc = oc{
    isSatisfiable   = isSatisfiable'
  , notStrongerThan = notStrongerThan'
  }
  where
    isSatisfiable'   c1    = f (isSatisfiable oc c1)
    notStrongerThan' c1 c2 = f (notStrongerThan oc c1 c2)

-- @runStateConstriants initState cgen@ transforms a constraint generator in the 'State'
-- monad to one in the 'Identity' monad by using initial state @initState@'
runStateConstraints :: ConstraintGen oc base lifted (State a) -> a -> ConstraintGen oc base lifted Identity
runStateConstraints cgen initState impl r oc t u = Identity $ evalState (cgen impl r oc t u) initState