decidable-0.3.1.1: src/Data/Type/Predicate/Quantification.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
-- |
-- Module : Data.Type.Predicate.Quantification
-- Copyright : (c) Justin Le 2018
-- License : BSD3
--
-- Maintainer : justin@jle.im
-- Stability : experimental
-- Portability : non-portable
--
-- Higher-level predicates for quantifying predicates over universes and
-- sets.
module Data.Type.Predicate.Quantification (
-- * Any
Any,
WitAny (..),
None,
anyImpossible,
-- ** Decision
decideAny,
idecideAny,
decideNone,
idecideNone,
-- ** Entailment
entailAny,
ientailAny,
entailAnyF,
ientailAnyF,
-- * All
All,
WitAll (..),
NotAll,
-- ** Decision
decideAll,
idecideAll,
-- ** Entailment
entailAll,
ientailAll,
entailAllF,
ientailAllF,
decideEntailAll,
idecideEntailAll,
-- * Logical interplay
allToAny,
allNotNone,
noneAllNot,
anyNotNotAll,
notAllAnyNot,
) where
import Data.Kind
import Data.Singletons
import Data.Singletons.Decide
import Data.Type.Functor.Product
import Data.Type.Predicate
import Data.Type.Predicate.Logic
import Data.Type.Universe
-- | 'decideNone', but providing an 'Elem'.
idecideNone ::
forall f k (p :: k ~> Type) (as :: f k).
Universe f =>
-- | predicate on value
(forall a. Elem f as a -> Sing a -> Decision (p @@ a)) ->
-- | predicate on collection
(Sing as -> Decision (None f p @@ as))
idecideNone f xs = decideNot @(Any f p) $ idecideAny f xs
-- | Lifts a predicate @p@ on an individual @a@ into a predicate that on
-- a collection @as@ that is true if and only if /no/ item in @as@
-- satisfies the original predicate.
--
-- That is, it turns a predicate of kind @k ~> Type@ into a predicate
-- of kind @f k ~> Type@.
decideNone ::
forall f k (p :: k ~> Type).
Universe f =>
-- | predicate on value
Decide p ->
-- | predicate on collection
Decide (None f p)
decideNone f = idecideNone (const f)
-- | 'entailAny', but providing an 'Elem'.
ientailAny ::
forall f p q as.
(Universe f, SingI as) =>
-- | implication
(forall a. Elem f as a -> Sing a -> p @@ a -> q @@ a) ->
Any f p @@ as ->
Any f q @@ as
ientailAny f (WitAny i x) = WitAny i (f i (indexSing i sing) x)
-- | If there exists an @a@ s.t. @p a@, and if @p@ implies @q@, then there
-- must exist an @a@ s.t. @q a@.
entailAny ::
forall f p q.
Universe f =>
(p --> q) ->
(Any f p --> Any f q)
entailAny = tmap @(Any f)
-- | 'entailAll', but providing an 'Elem'.
ientailAll ::
forall f p q as.
(Universe f, SingI as) =>
-- | implication
(forall a. Elem f as a -> Sing a -> p @@ a -> q @@ a) ->
All f p @@ as ->
All f q @@ as
ientailAll f a = WitAll $ \i -> f i (indexSing i sing) (runWitAll a i)
-- | If for all @a@ we have @p a@, and if @p@ implies @q@, then for all @a@
-- we must also have @p a@.
entailAll ::
forall f p q.
Universe f =>
(p --> q) ->
(All f p --> All f q)
entailAll = tmap @(All f)
-- | 'entailAnyF', but providing an 'Elem'.
ientailAnyF ::
forall f p q as h.
Functor h =>
-- | implication in context
(forall a. Elem f as a -> p @@ a -> h (q @@ a)) ->
Any f p @@ as ->
h (Any f q @@ as)
ientailAnyF f = \case WitAny i x -> WitAny i <$> f i x
-- | If @p@ implies @q@ under some context @h@, and if there exists some
-- @a@ such that @p a@, then there must exist some @a@ such that @p q@
-- under that context @h@.
--
-- @h@ might be something like, say, 'Maybe', to give predicate that is
-- either provably true or unprovably false.
--
-- Note that it is not possible to do this with @p a -> 'Decision' (q a)@.
-- This is if the @p a -> 'Decision' (q a)@ implication is false, there
-- it doesn't mean that there is /no/ @a@ such that @q a@, necessarily.
-- There could have been an @a@ where @p@ does not hold, but @q@ does.
entailAnyF ::
forall f p q h.
(Universe f, Functor h) =>
-- | implication in context
(p --># q) h ->
(Any f p --># Any f q) h
entailAnyF f x a =
withSingI x $
ientailAnyF @f @p @q (\i -> f (indexSing i x)) a
-- | 'entailAllF', but providing an 'Elem'.
ientailAllF ::
forall f p q as h.
(Universe f, Applicative h, SingI as) =>
-- | implication in context
(forall a. Elem f as a -> p @@ a -> h (q @@ a)) ->
All f p @@ as ->
h (All f q @@ as)
ientailAllF f a =
fmap (prodAll getWit)
. itraverseProd (\i _ -> Wit @q <$> f i (runWitAll a i))
$ singProd (sing @as)
-- | If @p@ implies @q@ under some context @h@, and if we have @p a@ for
-- all @a@, then we must have @q a@ for all @a@ under context @h@.
entailAllF ::
forall f p q h.
(Universe f, Applicative h) =>
-- | implication in context
(p --># q) h ->
(All f p --># All f q) h
entailAllF f x a =
withSingI x $
ientailAllF @f @p @q (\i -> f (indexSing i x)) a
-- | 'entailAllF', but providing an 'Elem'.
idecideEntailAll ::
forall f p q as.
(Universe f, SingI as) =>
-- | decidable implication
(forall a. Elem f as a -> p @@ a -> Decision (q @@ a)) ->
All f p @@ as ->
Decision (All f q @@ as)
idecideEntailAll f a = idecideAll (\i _ -> f i (runWitAll a i)) sing
-- | If we have @p a@ for all @a@, and @p a@ can be used to test for @q a@,
-- then we can test all @a@s for @q a@.
decideEntailAll ::
forall f p q.
Universe f =>
p -?> q ->
All f p -?> All f q
decideEntailAll = dmap @(All f)
-- | It is impossible for any value in a collection to be 'Impossible'.
--
-- @since 0.1.2.0
anyImpossible :: Universe f => Any f Impossible --> Impossible
anyImpossible _ (WitAny i p) = p . indexSing i
-- | If any @a@ in @as@ does not satisfy @p@, then not all @a@ in @as@
-- satisfy @p@.
--
-- @since 0.1.2.0
anyNotNotAll :: Any f (Not p) --> NotAll f p
anyNotNotAll _ (WitAny i v) a = v $ runWitAll a i
-- | If not all @a@ in @as@ satisfy @p@, then there must be at least one
-- @a@ in @as@ that does not satisfy @p@. Requires @'Decidable' p@ in
-- order to locate that specific @a@.
--
-- @since 0.1.2.0
notAllAnyNot ::
forall f p.
(Universe f, Decidable p) =>
NotAll f p --> Any f (Not p)
notAllAnyNot xs vAll = elimDisproof (decide @(Any f (Not p)) xs) $ \vAny ->
vAll $ WitAll $ \i ->
elimDisproof (decide @p (indexSing i xs)) $ \vP ->
vAny $ WitAny i vP
-- | If @p@ is false for all @a@ in @as@, then no @a@ in @as@ satisfies
-- @p@.
--
-- @since 0.1.2.0
allNotNone :: All f (Not p) --> None f p
allNotNone _ a (WitAny i v) = runWitAll a i v
-- | If no @a@ in @as@ satisfies @p@, then @p@ is false for all @a@ in
-- @as@. Requires @'Decidable' p@ to interrogate the input disproof.
--
-- @since 0.1.2.0
noneAllNot ::
forall f p.
(Universe f, Decidable p) =>
None f p --> All f (Not p)
noneAllNot xs vAny = elimDisproof (decide @(All f (Not p)) xs) $ \vAll ->
vAll $ WitAll $ \i p -> vAny $ WitAny i p
-- | If something is true for all xs, then it must be true for at least one
-- x in xs, provided that xs is not empty.
--
-- @since 0.1.5.0
allToAny :: (All f p &&& NotNull f) --> Any f p
allToAny _ (a, WitAny i _) = WitAny i $ runWitAll a i