exhaustive-1.0.0: src/Control/Exhaustive/Internal.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
module Control.Exhaustive.Internal
(Constructor, Producer, FunctorStack, produceM, produceFirst, produceAll, construct, Apply, apply )
where
import Data.Maybe
import Data.Traversable
import Control.Applicative
import Data.Foldable
import Generics.SOP
import Generics.SOP.NP
import Data.Functor.Compose
import Generics.SOP.Constraint
-- | Internal machinery to build n-ary functions. As a user of `exhaustive`, you
-- generally shouldn't need to worry about this type class, other than knowing
-- that the type variable @b@ will correspond to a function of all types
-- mentioned in @a@.
class Functor f => Apply f a b | f a -> b where
apply :: f a -> b
instance Apply ((->) a) b (a -> b) where
apply = id
instance Apply I b b where
apply (I a) = a
instance (Apply g a b,Apply f b c) => Apply (Compose f g) a c where
apply (Compose x) = apply (fmap apply x)
-- | A 'Producer' is a lifted function (in the terms of @generics-sop@) that
-- instantiates a particular constructor of a data type, possibly using
-- the side-effects provided by @f@.
--
-- Most users will want to create 'Producer's using the smart constructor
-- 'construct'.
type Producer f code = Injection (NP I) code -.-> K (f (NS (NP I) code))
-- | A 'Constructor' is an n-ary function from all fields of a specific
-- constructor in a data type, to a generic representation.
type Constructor fields = forall r. Apply (FunctorStack fields) (NP I fields) r => r
-- | 'construct' builds a 'Producer' for a single constructor of a data type.
-- As you can see, the type is a little scary - but there are a few main parts
-- that will interest you, while the rest are unfortunate implementation
-- details.
--
-- * @f@ is the type of functor who's side effects you can use. For example,
-- you can choose @f@ to be 'IO', @(MyEnv ->)@, or even more complex
-- monad transformer stacks.
--
-- * @fields@ is a list of types that are used in the constructor.
--
-- As an example, given the data type
--
-- @data User = User { name :: 'String', age :: 'Int'}@
--
-- then @fields@ will correspond to @['String', 'Int']@.
--
-- The 'Constructor' argument is what you use to actually create your data type.
-- A 'Constructor' is an n-ary function from all field types. Continuing the
-- example with @User@ above, we would have
--
-- @Constructor fields@ == @Text -> Int -> out@
--
-- Thus a complete call to 'construct' would be
--
-- @'construct' (\\f -> f '<$>' parseName '<*>' parseAge)@
--
-- For a complete example of how this all fits together, user's are pointed
-- to the example at the top of this page.
construct :: forall fields code f.
(Applicative f,SingI fields)
=> (Constructor fields -> f (NP I fields)) -> Producer f code fields
construct applyCtor =
Fn (\(Fn f) ->
(K (fmap (unK . f)
(applyCtor (apply (buildF (shape :: Shape fields)))))))
-- | This type family is internal, but provides the building block for building
-- n-ary functions. Most users will probably not need to work with this
-- directly.
type family FunctorStack (args :: [*]) :: * -> * where
FunctorStack '[] = I
FunctorStack (a ': as) = Compose ((->) a) (FunctorStack as)
data Dict (k :: Constraint) where
Dict :: k => Dict k
-- | Prove that a 'FunctorStack' really is a 'Functor'.
isAFunctor :: Shape xs -> Dict (Functor (FunctorStack xs))
isAFunctor ShapeNil = Dict
isAFunctor (ShapeCons s) = case isAFunctor s of Dict -> Dict
-- | Given a list of types, build a stack of reader functors that represents
-- an n-ary function of all of those types.
buildF :: Functor (FunctorStack xs) => Shape xs -> FunctorStack xs (NP I xs)
buildF ShapeNil = I Nil
buildF (ShapeCons s) = Compose (\x -> case isAFunctor s of Dict -> fmap (I x :*) (buildF s))
-- | Keep attempting to construct a data type until a constructor succeeds. The
-- first constructor to successfully be constructed (in the order defined in the
-- original data type) will be returned, or 'empty' if all constructions fail.
produceFirst
:: (code ~ Code a, SingI code, Generic a, Alternative f)
=> NP (Producer f code) code -> f a
produceFirst = asum . produceM
-- | Produce all successful constructions of a data-type. If any constructors
-- fail, they will not be included in the resulting list. If all constructors
-- fail, this will return 'pure' '[]'.
produceAll
:: (code ~ Code a, SingI code, Generic a, Alternative f)
=> NP (Producer f code) code -> f [a]
produceAll = fmap catMaybes . sequenceA . map optional . produceM
-- | Build a list of computations, one for each constructor in a data type.
produceM
:: (code ~ Code a, SingI code, Generic a, Applicative f)
=> NP (Producer f code) code
-> [f a]
produceM fs =
map (fmap (to . SOP))
(collapse_NP (fs `hap` injections))