----------------------------------------------------------------------------- -- | -- Module: Barbies -- -- A common Haskell idiom is to parameterise a datatype by a functor or GADT -- (or any "indexed type" @k -> 'Data.Kind.Type'@), a pattern -- sometimes called <https://reasonablypolymorphic.com/blog/higher-kinded-data/ HKD>). -- This parameter acts like the outfit of a Barbie, turning it into a different -- doll. The canonical example would be: -- -- @ -- data Person f -- = Person -- { name :: f 'String' -- , age :: f 'Int' -- } -- @ -- -- Let's say that we are writing an application where @Person@ data -- will be read from a web form, validated, and stored in a database. Some -- possibles outfits that we could use along the way are: -- -- @ -- Person ('Data.Functor.Const.Const' 'String') -- for the raw input from the web-form, -- Person ('Either' 'String') -- for the result of parsing and validating, -- Person 'Data.Functor.Identity.Identity' -- for the actual data, -- Person DbColumn -- To describe how to read / write a @Person@ to the db -- -- data DbColumn a -- = DbColumn -- { colName :: 'String' -- , fromDb :: DbDataParser a -- , toDb :: a -> DbData -- } -- @ -- -- In such application it is likely that one will have lots of types like -- @Person@ so we will like to handle these transformations uniformly, -- without boilerplate or repetitions. This package provides classes to -- manipulate these types, using notions that are familiar to haskellers like -- 'Functor', 'Applicative' or 'Traversable'. For example, instead of writing -- an ad-hoc function that checks that all fields have a correct value, like -- -- @ -- checkPerson :: Person ('Either' 'String') -> 'Either' ['String'] (Person 'Data.Functor.Identity.Identity') -- @ -- -- we can write only one such function: -- -- @ -- check :: 'TraversableB' b => b ('Either' 'String') -> 'Either' ['String'] (b 'Data.Functor.Identity.Identity') -- check be -- = case 'btraverse' ('either' ('const' 'Nothing') ('Just' . 'Daa.Functor.Identity.Identity')) be of -- 'Just' bi -> 'Right' bi -- 'Nothing' -> 'Left' ('bfoldMap' ('either' (:[]) ('const' [])) be) -- @ -- -- Moreover, these classes come with default instances based on -- `GHC.Generics.Generic`, so using them is as easy as: -- -- @ -- data Person f -- = Person -- { name :: f 'String' -- , age :: f 'Int' -- } -- deriving -- ( 'GHC.Generics.Generic' -- , 'FunctorB', 'TraversableB', 'ApplicativeB', 'ConstraintsB' -- ) -- -- deriving instance 'AllBF' 'Show' f Person => 'Show' (Person f) -- deriving instance 'AllBF' 'Eq' f Person => 'Eq' (Person f) -- @ -- ----------------------------------------------------------------------------- module Barbies ( -- * Barbies are functors -- | Barbie-types are functors. That means that if one is familiar -- with standard classes like 'Functor', 'Applicative' or 'Traversable', -- one already knows how to work with barbie-types too. For instance, just -- like one would use: -- -- @ -- 'fmap' f (as :: [a]) -- @ -- -- to apply @f@ uniformly on every @a@ occurring -- in @as@, one could use the following to turn a 'Either'-outfit -- into 'Maybe'-outfit: -- -- @ -- 'bmap' ('either' ('const' 'Nothing') 'Just') (p :: Person ('Either' e)) -- @ -- -- In this case, the argument of 'bmap' will have to be applied on all -- fields of @p@: -- -- @ -- name p :: 'Either' e 'String' -- age p :: 'Either' e 'Int' -- @ -- -- So 'bmap' here demands a polymorphic function of type: -- -- @ -- forall a . 'Either' e a -> 'Maybe' a -- @ -- -- That is why `bmap` has a rank-2 type: -- -- @ -- 'bmap' :: 'FunctorB' b => (forall a. f a -> g a) -> b f -> b g -- @ -- -- Polymorphic functions with 'Applicative' effects can be applied -- using 'btraverse' and the effects will be accumulated: -- -- @ -- 'btraverse' :: ('TraversableB' b, 'Applicative' t) => (forall a. f a -> t (g a)) -> b f -> t (b g) -- @ -- -- Finally, some barbie-types (typically records like @Person@) have an -- 'Applicative' structure, and allow us to lift pure n-ary functions -- to functions on barbie-types. For example, 'bzipWith' gives us an analogous -- of 'Control.Applicative.liftA2': -- -- @ -- 'bzipWith' :: 'ApplicativeB' b => (forall a. f a -> g a -> h a) -> b f -> b g -> b h -- @ -- -- We can use this to combine barbies: -- -- @ -- addDefaults :: Person 'Maybe' -> Person 'Data.Functor.Identity' -> Person 'Data.Functor.Identity' -- addDefaults = 'bzipWith' (\\m d -> 'maybe' d 'pure' m) -- @ -- -- Why is there not a @MonadB@ class as well? As everyone knows, -- <https://james-iry.blogspot.com/2009/05/brief-incomplete-and-mostly-wrong.html a monad is just a monoid in the category of endofunctors>, -- which in this case is a problem, since barbie-types are not endofunctors: -- they map indexed-types to types, unlike the 'Functor' class, that -- captures endo-functors on 'Data.Kind.Type'. -- -- All these classes, and other convenient functions are found in: module Data.Functor.Barbie -- * Transformers are functors -- | Haskellers may be more used to playing with another family of dolls: -- <https://hackage.haskell.org/package/transformers transformers>. -- Consider for example the following functor-transformers: -- -- @ -- 'Data.Functor.Compose.Compose' g f a -- 'Control.Monad.Trans.Reader.ReaderT' r f a -- 'Control.Monad.Maybe.MaybeT' f a -- @ -- -- Like with barbies, we can think that different choices of @f@ will -- give us a different doll. And if we start thinking about how -- to change the outfit of a transformer, we notice that, just like -- barbie-types, transformer-types are functors too. -- -- @ -- 'tmap' :: 'FunctorT' t => (forall a. f a -> g a) -> t f x -> b g x -- @ -- -- Where 'FunctorB' captures functors from indexed-types to types, -- 'FunctorT' captures those between indexed-types. And again, we can -- identitfy familiar classes of functors: 'ApplicativeT' and 'TraversableT'. -- -- Now, transformers like the ones above, are actually endofunctors, e.g. -- they map @'Data.Kind.Type' -> 'Data.Kind.Type'@ to itself. So it makes -- sense to classify those that are actually monads: the 'MonadT' class -- gives us a notion similar to that of `Control.Monad.Trans.Class.MonadTrans', -- in that it lets us lift a value to its transformed version: -- -- @ -- 'tlift' :: 'MonadT' t => f a -> t f a -- -- -- E.g., using the instance for Compose: -- 'tlift' [1, 2, 3] = 'Data.Functor.Compose.Compose' ('Just' [1, 2, 3]) :: 'Data.Functor.Compose' 'Maybe' [] 'Int' -- @ -- -- Unlike all other classes in this package, 'MonadT' instances need to be written -- by hand. -- -- For further details, see: , module Data.Functor.Transformer -- * Bi-functors and nesting -- -- | A barbie-type that is parametric on an additional functor can be made an -- instance of both 'FunctorB' and 'FunctorT'. For example: -- -- @ -- data B f g = B (f Int) (g Bool) -- deriving (Generic) -- -- instance FunctorB (B f) -- instance FunctorT B -- @ -- -- This gives us a a bifunctor on indexed-types, as we can map -- simultaneously over both arguments using 'btmap': -- -- @ -- 'btmap' :: ('FunctorB' (b f), 'FunctorT' b) => (forall a . f a -> f' a) -> (forall a . g a -> g' a) -> b f g -> b f' g' -- @ -- -- When @f ~ g@, we can use a specialized version of 'btmap': -- -- @ -- 'btmap1' :: ('FunctorB' (b f), 'FunctorT' b) => (forall a . f a -> f' a) -> b f f -> b f' f' -- @ -- -- Functions like 'btmap1' can be useful to handle cases where we would like -- a barbie-type to occur under the functor-argument. Let's consider an example -- of this. Continuing the web form example above, one may want to find out -- about a person's dependants and model it as follows: -- -- @ -- newtype Dependants f -- = Dependants { getDependants :: f [Person f] } -- @ -- -- This has the appeal of letting us distinguish two states: -- -- @ -- Dependants { getDependants = Just [] } -- the user declared 0 dependants -- Dependants { getDependants = Nothing } -- the user didn't specify dependants yet -- @ -- -- Unfortunately, it is not possible to write a 'FunctorB' instance for such -- a type (before going on, try to write one yourself!). Intuitively, we would -- need to have @'Functor' f@, which we can't assume. However, such a type -- can be rewritten as follows: -- -- @ -- newtype Dependants f' f -- = Dependants { getDependants :: f' [Person f] } -- deriving (Generic) -- -- instance Functor f' => FunctorB (Dependants f') -- instance FunctorT Dependants -- -- type Dependants f = Dependants f f -- @ -- -- We can thus use 'btmap1' as a poor man's version of 'bmap' for 'Dependants'. -- -- For more details, see: , module Barbies.Bi -- * Container-barbies -- | Some clothes make barbies look like containers, and we can make those -- types behave like normal 'Functor's. , Containers.Container(..) , Containers.ErrorContainer(..) -- * Wrappers -- | This can be use with deriving via to automate derivation of instances -- for Barbie-types. , Wrappers.Barbie(..) -- * Trivial Barbies , Trivial.Void , Trivial.Unit (..) ) where import Barbies.Internal.Containers as Containers import Data.Functor.Barbie import Data.Functor.Transformer import Barbies.Bi import qualified Barbies.Internal.Trivial as Trivial import qualified Barbies.Internal.Wrappers as Wrappers