-- | Emulation of type-level functions.
module Data.TypeFun (

    -- * Type-level functions in general
    TypeFun (type Domain),
    App,

    -- * Construction of type-level functions
    Id (Id),
    Const (Const),
    (:->) ((:->)),
    FunMap,

    -- * Utilities
    WrappedApp (WrapApp),
    unwrapApp,
    Universal

) where

    import "kinds" Data.Kind as Kind

    {-NOTE:
        At the value level, type-level function representations contain other /representations/ but
        nothing else.
    -}

    -- * Type-level functions in general
    {-|
        Type-level functions are represented by types. @TypeFun@ is the class of all type-level
        function representations.
    -}
    class (Kind (Domain fun)) => TypeFun fun where

        {-|
            The domain of the type-level function as a subkind representation. Subkind handling is
            provided by the /kinds/ package.
        -}
        type Domain fun

    {-|
        Application of type-level functions. @App@ takes a function representation and an argument
        and returns the corresponding result.
    -}
    type family App fun arg

    -- * Construction of type-level functions
    infixr 1 :->

    {-|
        A type @Id /d/@ represents the type-level identity function whose domain is represented
        by @/d/@.
    -}
    data Id dom = Id dom

    instance (Kind dom) => TypeFun (Id dom) where

        type Domain (Id dom) = dom

    type instance App (Id dom) arg = arg

    {-|
        A type @Const /d/ /v/@ represents the constant type-level function whose domain is
        represented by @/d/@, and whose result is @/v/@.
    -}
    data Const dom val = Const dom

    instance (Kind dom) => TypeFun (Const dom val) where

        type Domain (Const dom val) = dom

    type instance App (Const dom val) arg = val

    {-|
        A type @/f/ :-> /f'/@ represents the type-level function @\\arg -> ('App' /f/ arg -> 'App'
        /f'/ arg)@.
    -}
    data fun :-> fun' = fun :-> fun'

    instance (TypeFun fun, TypeFun fun', Domain fun ~ Domain fun') => TypeFun (fun :-> fun') where

        type Domain (fun :-> fun') = Domain fun

    type instance App (fun :-> fun') arg = App fun arg -> App fun' arg

    {-|
        If @/t/@ is a type of kind @* -> *@, and @/f/@ is the representation of a
        type-level function, @FunMap /t/ /f/@ represents the function @\\arg -> /t/ (App /f/ arg)@.
    -}
    data FunMap (trans :: * -> *) fun = FunMap fun

    instance (TypeFun fun) => TypeFun (FunMap trans fun) where

        type Domain (FunMap trans fun) = Domain fun

    type instance App (FunMap trans fun) arg = trans (App fun arg)

    -- * Utilities
    -- |A data type that is isomorphic to the type synonym family 'App'.
    newtype WrappedApp fun arg = WrapApp (App fun arg)

    -- |The inverse of 'WrapApp'.
    unwrapApp :: WrappedApp fun arg -> App fun arg
    unwrapApp (WrapApp val) = val

    -- |Turns a type-level function into the intersection of all its results.
    type Universal fun = forall arg. (Inhabitant (Domain fun) arg) => WrappedApp fun arg