packages feed

decidable-0.3.1.1: src/Data/Type/Universe.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE EmptyCase #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilyDependencies #-}
{-# LANGUAGE TypeOperators #-}

-- |
-- Module      : Data.Type.Universe
-- Copyright   : (c) Justin Le 2018
-- License     : BSD3
--
-- Maintainer  : justin@jle.im
-- Stability   : experimental
-- Portability : non-portable
--
-- A type family for "containers", intended for allowing lifting of
-- predicates on @k@ to be predicates on containers @f k@.
module Data.Type.Universe (
  -- * Universe
  Elem,
  In,
  Universe (..),
  singAll,

  -- ** Instances
  Index (..),
  IJust (..),
  IRight (..),
  NEIndex (..),
  ISnd (..),
  IIdentity (..),

  -- ** Predicates
  All,
  WitAll (..),
  NotAll,
  Any,
  WitAny (..),
  None,
  Null,
  NotNull,

  -- *** Specialized
  IsJust,
  IsNothing,
  IsRight,
  IsLeft,

  -- * Decisions and manipulations
  decideAny,
  decideAll,
  genAll,
  igenAll,
  splitSing,
  pickElem,
) where

import Data.Either.Singletons hiding (IsLeft, IsRight)
import Data.Functor.Identity
import Data.Functor.Identity.Singletons
import Data.Kind
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty.Singletons as NE
import Data.List.Singletons hiding (
  All,
  Any,
  Elem,
  ElemSym0,
  ElemSym1,
  ElemSym2,
  Null,
 )
import Data.Maybe.Singletons hiding (IsJust, IsNothing)
import Data.Singletons
import Data.Singletons.Decide
import Data.Tuple.Singletons
import Data.Type.Functor.Product
import Data.Type.Predicate
import Data.Type.Predicate.Logic
import GHC.Generics ((:*:) (..))
import Prelude hiding (all, any)

-- | A @'WitAny' p as@ is a witness that, for at least one item @a@ in the
-- type-level collection @as@, the predicate @p a@ is true.
data WitAny f :: (k ~> Type) -> f k -> Type where
  WitAny :: Elem f as a -> p @@ a -> WitAny f p as

-- | An @'Any' f p@ is a predicate testing a collection @as :: f a@ for the
-- fact that at least one item in @as@ satisfies @p@.  Represents the
-- "exists" quantifier over a given universe.
--
-- This is mostly useful for its 'Decidable' and 'TFunctor' instances,
-- which lets you lift predicates on @p@ to predicates on @'Any' f p@.
data Any f :: Predicate k -> Predicate (f k)

type instance Apply (Any f p) as = WitAny f p as

-- | A @'WitAll' p as@ is a witness that the predicate @p a@ is true for all
-- items @a@ in the type-level collection @as@.
newtype WitAll f p (as :: f k) = WitAll {runWitAll :: forall a. Elem f as a -> p @@ a}

-- | An @'All' f p@ is a predicate testing a collection @as :: f a@ for the
-- fact that /all/ items in @as@ satisfy @p@.  Represents the "forall"
-- quantifier over a given universe.
--
-- This is mostly useful for its 'Decidable', 'Provable', and 'TFunctor'
-- instances, which lets you lift predicates on @p@ to predicates on @'All'
-- f p@.
data All f :: Predicate k -> Predicate (f k)

type instance Apply (All f p) as = WitAll f p as

instance (Universe f, Decidable p) => Decidable (Any f p) where
  decide = decideAny @f @_ @p $ decide @p

instance (Universe f, Decidable p) => Decidable (All f p) where
  decide = decideAll @f @_ @p $ decide @p

instance (Universe f, Provable p) => Decidable (NotNull f ==> Any f p)

instance Provable p => Provable (NotNull f ==> Any f p) where
  prove _ (WitAny i s) = WitAny i (prove @p s)

instance (Universe f, Provable p) => Provable (All f p) where
  prove xs = WitAll $ \i -> prove @p (indexSing i xs)

instance Universe f => TFunctor (Any f) where
  tmap f xs (WitAny i x) = WitAny i (f (indexSing i xs) x)

instance Universe f => TFunctor (All f) where
  tmap f xs a = WitAll $ \i -> f (indexSing i xs) (runWitAll a i)

instance Universe f => DFunctor (All f) where
  dmap f xs a = idecideAll (\i x -> f x (runWitAll a i)) xs

-- | Typeclass for a type-level container that you can quantify or lift
-- type-level predicates over.
class FProd f => Universe (f :: Type -> Type) where
  -- | 'decideAny', but providing an 'Elem'.
  idecideAny ::
    forall k (p :: k ~> Type) (as :: f k).
    () =>
    -- | predicate on value
    (forall a. Elem f as a -> Sing a -> Decision (p @@ a)) ->
    -- | predicate on collection
    (Sing as -> Decision (Any f p @@ as))

  -- | 'decideAll', but providing an 'Elem'.
  idecideAll ::
    forall k (p :: k ~> Type) (as :: f k).
    () =>
    -- | predicate on value
    (forall a. Elem f as a -> Sing a -> Decision (p @@ a)) ->
    -- | predicate on collection
    (Sing as -> Decision (All f p @@ as))

  allProd ::
    forall p g.
    () =>
    (forall a. Sing a -> p @@ a -> g a) ->
    All f p --> TyPred (Prod f g)

  prodAll ::
    forall p g as.
    () =>
    (forall a. g a -> p @@ a) ->
    Prod f g as ->
    All f p @@ as

-- | Predicate that a given @as :: f k@ is empty and has no items in it.
type Null f = (None f Evident :: Predicate (f k))

-- | Predicate that a given @as :: f k@ is not empty, and has at least one
-- item in it.
type NotNull f = (Any f Evident :: Predicate (f k))

-- | A @'None' f p@ is a predicate on a collection @as@ that no @a@ in @as@
-- satisfies predicate @p@.
type None f p = (Not (Any f p) :: Predicate (f k))

-- | A @'NotAll' f p@ is a predicate on a collection @as@ that at least one
-- @a@ in @as@ does not satisfy predicate @p@.
type NotAll f p = (Not (All f p) :: Predicate (f k))

-- | Lifts a predicate @p@ on an individual @a@ into a predicate that on
-- a collection @as@ that is true if and only if /any/ 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@.
--
-- Essentially tests existential quantification.
decideAny ::
  forall f k (p :: k ~> Type).
  Universe f =>
  -- | predicate on value
  Decide p ->
  -- | predicate on collection
  Decide (Any f p)
decideAny f = idecideAny (const f)

-- | Lifts a predicate @p@ on an individual @a@ into a predicate that on
-- a collection @as@ that is true if and only if /all/ items in @as@
-- satisfies the original predicate.
--
-- That is, it turns a predicate of kind @k ~> Type@ into a predicate
-- of kind @f k ~> Type@.
--
-- Essentially tests universal quantification.
decideAll ::
  forall f k (p :: k ~> Type).
  Universe f =>
  -- | predicate on value
  Decide p ->
  -- | predicate on collection
  Decide (All f p)
decideAll f = idecideAll (const f)

-- | Split a @'Sing' as@ into a proof that all @a@ in @as@ exist.
splitSing ::
  forall f k (as :: f k).
  Universe f =>
  Sing as ->
  All f (TyPred Sing) @@ as
splitSing = prodAll id . singProd

-- | Automatically generate a witness for a member, if possible
pickElem ::
  forall f k (as :: f k) a.
  (Universe f, SingI as, SingI a, SDecide k) =>
  Decision (Elem f as a)
pickElem =
  mapDecision
    (\case WitAny i Refl -> i)
    (\case i -> WitAny i Refl)
    . decide @(Any f (TyPred ((:~:) a)))
    $ sing

-- | 'genAll', but providing an 'Elem'.
igenAll ::
  forall f k (p :: k ~> Type) (as :: f k).
  Universe f =>
  -- | always-true predicate on value
  (forall a. Elem f as a -> Sing a -> p @@ a) ->
  -- | always-true predicate on collection
  (Sing as -> All f p @@ as)
igenAll f = prodAll (\(i :*: x) -> f i x) . imapProd (:*:) . singProd

-- | If @p a@ is true for all values @a@ in @as@, then we have @'All'
-- p as@.  Basically witnesses the definition of 'All'.
genAll ::
  forall f k (p :: k ~> Type).
  Universe f =>
  -- | always-true predicate on value
  Prove p ->
  -- | always-true predicate on collection
  Prove (All f p)
genAll f = prodAll f . singProd

-- | Split a @'Sing' as@ into a proof that all @a@ in @as@ exist.
singAll ::
  forall f k (as :: f k).
  Universe f =>
  Sing as ->
  All f Evident @@ as
singAll = prodAll id . singProd

-- | Test that a 'Maybe' is 'Just'.
--
-- @since 0.1.2.0
type IsJust = (NotNull Maybe :: Predicate (Maybe k))

-- | Test that a 'Maybe' is 'Nothing'.
--
-- @since 0.1.2.0
type IsNothing = (Null Maybe :: Predicate (Maybe k))

-- | Test that an 'Either' is 'Right'
--
-- @since 0.1.2.0
type IsRight = (NotNull (Either j) :: Predicate (Either j k))

-- | Test that an 'Either' is 'Left'
--
-- @since 0.1.2.0
type IsLeft = (Null (Either j) :: Predicate (Either j k))

instance Universe [] where
  idecideAny ::
    forall k (p :: k ~> Type) (as :: [k]).
    () =>
    (forall a. Elem [] as a -> Sing a -> Decision (p @@ a)) ->
    Sing as ->
    Decision (Any [] p @@ as)
  idecideAny f = \case
    SNil -> Disproved $ \case
      WitAny i _ -> case i of {}
    x `SCons` xs -> case f IZ x of
      Proved p -> Proved $ WitAny IZ p
      Disproved v -> case idecideAny @[] @_ @p (f . IS) xs of
        Proved (WitAny i p) -> Proved $ WitAny (IS i) p
        Disproved vs -> Disproved $ \case
          WitAny IZ p -> v p
          WitAny (IS i) p -> vs (WitAny i p)

  idecideAll ::
    forall k (p :: k ~> Type) (as :: [k]).
    () =>
    (forall a. Elem [] as a -> Sing a -> Decision (p @@ a)) ->
    Sing as ->
    Decision (All [] p @@ as)
  idecideAll f = \case
    SNil -> Proved $ WitAll $ \case {}
    x `SCons` xs -> case f IZ x of
      Proved p -> case idecideAll @[] @_ @p (f . IS) xs of
        Proved a -> Proved $ WitAll $ \case
          IZ -> p
          IS i -> runWitAll a i
        Disproved v -> Disproved $ \a -> v $ WitAll (runWitAll a . IS)
      Disproved v -> Disproved $ \a -> v $ runWitAll a IZ

  allProd ::
    forall p g.
    () =>
    (forall a. Sing a -> p @@ a -> g a) ->
    All [] p --> TyPred (Prod [] g)
  allProd f = go
    where
      go :: Sing as -> WitAll [] p as -> Prod [] g as
      go = \case
        SNil -> \_ -> RNil
        x `SCons` xs -> \a ->
          f x (runWitAll a IZ)
            :& go xs (WitAll (runWitAll a . IS))

  prodAll ::
    forall p g as.
    () =>
    (forall a. g a -> p @@ a) ->
    Prod [] g as ->
    All [] p @@ as
  prodAll f = go
    where
      go :: Prod [] g bs -> All [] p @@ bs
      go = \case
        RNil -> WitAll $ \case {}
        x :& xs -> WitAll $ \case
          IZ -> f x
          IS i -> runWitAll (go xs) i

instance Universe Maybe where
  idecideAny f = \case
    SNothing -> Disproved $ \case WitAny i _ -> case i of {}
    SJust x -> case f IJust x of
      Proved p -> Proved $ WitAny IJust p
      Disproved v -> Disproved $ \case
        WitAny IJust p -> v p
  idecideAll f = \case
    SNothing -> Proved $ WitAll $ \case {}
    SJust x -> case f IJust x of
      Proved p -> Proved $ WitAll $ \case IJust -> p
      Disproved v -> Disproved $ \a -> v $ runWitAll a IJust
  allProd f = \case
    SNothing -> \_ -> PNothing
    SJust x -> \a -> PJust (f x (runWitAll a IJust))
  prodAll f = \case
    PNothing -> WitAll $ \case {}
    PJust x -> WitAll $ \case IJust -> f x

instance Universe (Either j) where
  idecideAny f = \case
    SLeft _ -> Disproved $ \case WitAny i _ -> case i of {}
    SRight x -> case f IRight x of
      Proved p -> Proved $ WitAny IRight p
      Disproved v -> Disproved $ \case
        WitAny IRight p -> v p
  idecideAll f = \case
    SLeft _ -> Proved $ WitAll $ \case {}
    SRight x -> case f IRight x of
      Proved p -> Proved $ WitAll $ \case IRight -> p
      Disproved v -> Disproved $ \a -> v $ runWitAll a IRight
  allProd f = \case
    SLeft w -> \_ -> PLeft w
    SRight x -> \a -> PRight (f x (runWitAll a IRight))
  prodAll f = \case
    PLeft _ -> WitAll $ \case {}
    PRight x -> WitAll $ \case IRight -> f x

instance Universe NonEmpty where
  idecideAny ::
    forall k (p :: k ~> Type) (as :: NonEmpty k).
    () =>
    (forall a. Elem NonEmpty as a -> Sing a -> Decision (p @@ a)) ->
    Sing as ->
    Decision (Any NonEmpty p @@ as)
  idecideAny f (x NE.:%| xs) = case f NEHead x of
    Proved p -> Proved $ WitAny NEHead p
    Disproved v -> case idecideAny @[] @_ @p (f . NETail) xs of
      Proved (WitAny i p) -> Proved $ WitAny (NETail i) p
      Disproved vs -> Disproved $ \case
        WitAny i p -> case i of
          NEHead -> v p
          NETail i' -> vs (WitAny i' p)

  idecideAll ::
    forall k (p :: k ~> Type) (as :: NonEmpty k).
    () =>
    (forall a. Elem NonEmpty as a -> Sing a -> Decision (p @@ a)) ->
    Sing as ->
    Decision (All NonEmpty p @@ as)
  idecideAll f (x NE.:%| xs) = case f NEHead x of
    Proved p -> case idecideAll @[] @_ @p (f . NETail) xs of
      Proved ps -> Proved $ WitAll $ \case
        NEHead -> p
        NETail i -> runWitAll ps i
      Disproved v -> Disproved $ \a -> v $ WitAll (runWitAll a . NETail)
    Disproved v -> Disproved $ \a -> v $ runWitAll a NEHead

  allProd ::
    forall p g.
    () =>
    (forall a. Sing a -> p @@ a -> g a) ->
    All NonEmpty p --> TyPred (Prod NonEmpty g)
  allProd f (x NE.:%| xs) a =
    f x (runWitAll a NEHead)
      :&| allProd @[] @p f xs (WitAll (runWitAll a . NETail))
  prodAll ::
    forall p g as.
    () =>
    (forall a. g a -> p @@ a) ->
    Prod NonEmpty g as ->
    All NonEmpty p @@ as
  prodAll f (x :&| xs) = WitAll $ \case
    NEHead -> f x
    NETail i -> runWitAll (prodAll @[] @p f xs) i

instance Universe ((,) j) where
  idecideAny f (STuple2 _ x) = case f ISnd x of
    Proved p -> Proved $ WitAny ISnd p
    Disproved v -> Disproved $ \case WitAny ISnd p -> v p
  idecideAll f (STuple2 _ x) = case f ISnd x of
    Proved p -> Proved $ WitAll $ \case ISnd -> p
    Disproved v -> Disproved $ \a -> v $ runWitAll a ISnd
  allProd f (STuple2 w x) a = PTup w $ f x (runWitAll a ISnd)
  prodAll f (PTup _ x) = WitAll $ \case ISnd -> f x

-- | The single-pointed universe.
instance Universe Identity where
  idecideAny f (SIdentity x) =
    mapDecision
      (WitAny IId)
      (\case WitAny IId p -> p)
      $ f IId x
  idecideAll f (SIdentity x) =
    mapDecision
      (\p -> WitAll $ \case IId -> p)
      (\y -> runWitAll y IId)
      $ f IId x
  allProd f (SIdentity x) a = PIdentity $ f x (runWitAll a IId)
  prodAll f (PIdentity x) = WitAll $ \case IId -> f x