barbies-2.0.0.0: src/Barbies.hs
-----------------------------------------------------------------------------
-- |
-- 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