Safe Haskell | Safe-Inferred |
---|---|
Language | Haskell2010 |
A common Haskell idiom is to parameterise a datatype by a functor or GADT
(or any "indexed type" k ->
), a pattern
sometimes called HKD).
This parameter acts like the outfit of a Barbie, turning it into a different
doll. The canonical example would be:Type
data Person f = Person { name :: fString
, age :: fInt
}
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 (Const
String
) -- for the raw input from the web-form, Person (Either
String
) -- for the result of parsing and validating, PersonIdentity
-- for the actual data, Person DbColumn -- To describe how to read / write aPerson
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
] (PersonIdentity
)
we can write only one such function:
check ::TraversableB
b => b (Either
String
) ->Either
[String
] (bIdentity
) check be = casebtraverse
(either
(const
Nothing
) (Just
.Identity
)) be ofJust
bi ->Right
biNothing
->Left
(bfoldMap
(either
(:[]) (const
[])) be)
Moreover, these classes come with default instances based on
Generic
, so using them is as easy as:
data Person f = Person { name :: fString
, age :: fInt
} deriving (Generic
,FunctorB
,TraversableB
,ApplicativeB
,ConstraintsB
) deriving instanceAllBF
Show
f Person =>Show
(Person f) deriving instanceAllBF
Eq
f Person =>Eq
(Person f)
Synopsis
- module Data.Functor.Barbie
- module Data.Functor.Transformer
- module Barbies.Bi
- newtype Container b a = Container {
- getContainer :: b (Const a)
- newtype ErrorContainer b e = ErrorContainer {
- getErrorContainer :: b (Either e)
- newtype Barbie (b :: (k -> Type) -> Type) f = Barbie {
- getBarbie :: b f
- data Void (f :: k -> Type)
- data Unit (f :: k -> Type) = Unit
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
eString
age p ::Either
eInt
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 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 :: PersonMaybe
-> PersonIdentity
-> PersonIdentity
addDefaults =bzipWith
(\m d ->maybe
dpure
m)
Why is there not a MonadB
class as well? As everyone knows,
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 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: transformers. Consider for example the following functor-transformers:
Compose
g f aReaderT
r f aMaybeT
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
to itself. So it makes
sense to classify those that are actually monads: the Type
-> Type
MonadT
class
gives us a notion similar to that of 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] =Compose
(Just
[1, 2, 3]) ::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
, which we can't assume. However, such a type
can be rewritten as follows:Functor
f
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.
newtype Container b a Source #
Wrapper for barbies that act as containers of a
by wearing (
.Const
a)
Container | |
|
Instances
newtype ErrorContainer b e Source #
Wrapper for barbies that act as containers of e
by wearing
.Either
e
ErrorContainer | |
|
Instances
Wrappers
This can be use with deriving via to automate derivation of instances for Barbie-types.
newtype Barbie (b :: (k -> Type) -> Type) f Source #
A wrapper for Barbie-types, providing useful instances.
Instances
ApplicativeB b => ApplicativeB (Barbie b :: (k -> Type) -> TYPE LiftedRep) Source # | |
ConstraintsB b => ConstraintsB (Barbie b :: (k -> Type) -> TYPE LiftedRep) Source # | |
FunctorB b => FunctorB (Barbie b :: (k -> Type) -> TYPE LiftedRep) Source # | |
TraversableB b => TraversableB (Barbie b :: (k -> Type) -> TYPE LiftedRep) Source # | |
Defined in Barbies.Internal.Wrappers | |
ProductB b => ProductB (Barbie b :: (k -> Type) -> TYPE LiftedRep) Source # | |
ProductBC b => ProductBC (Barbie b :: (k -> Type) -> TYPE LiftedRep) Source # | |
Defined in Data.Barbie.Internal.ProductC | |
(ConstraintsB b, ApplicativeB b, AllBF Semigroup f b, AllBF Monoid f b) => Monoid (Barbie b f) Source # | |
(ConstraintsB b, ApplicativeB b, AllBF Semigroup f b) => Semigroup (Barbie b f) Source # | |
type AllB (c :: k -> Constraint) (Barbie b :: (k -> Type) -> TYPE LiftedRep) Source # | |
Defined in Barbies.Internal.Wrappers |
Trivial Barbies
data Void (f :: k -> Type) Source #
Uninhabited barbie type.
Instances
ConstraintsB (Void :: (k -> Type) -> Type) Source # | |
FunctorB (Void :: (k -> Type) -> Type) Source # | |
TraversableB (Void :: (k -> Type) -> Type) Source # | |
Defined in Barbies.Internal.Trivial | |
Semigroup (Void f) Source # | |
Generic (Void f) Source # | |
Show (Void f) Source # | |
Eq (Void f) Source # | |
Ord (Void f) Source # | |
type AllB (c :: k -> Constraint) (Void :: (k -> Type) -> Type) Source # | |
type Rep (Void f) Source # | |
data Unit (f :: k -> Type) Source #
A barbie type without structure.
Instances
ApplicativeB (Unit :: (k -> Type) -> Type) Source # | |
ConstraintsB (Unit :: (k -> Type) -> Type) Source # | |
DistributiveB (Unit :: (k -> Type) -> Type) Source # | |
Defined in Barbies.Internal.Trivial | |
FunctorB (Unit :: (k -> Type) -> Type) Source # | |
TraversableB (Unit :: (k -> Type) -> Type) Source # | |
Defined in Barbies.Internal.Trivial | |
ProductB (Unit :: (k -> Type) -> Type) Source # | |
ProductBC (Unit :: (k -> Type) -> Type) Source # | |
Defined in Data.Barbie.Internal.ProductC | |
(Typeable f, Typeable k) => Data (Unit f) Source # | |
Defined in Barbies.Internal.Trivial gfoldl :: (forall d b. Data d => c (d -> b) -> d -> c b) -> (forall g. g -> c g) -> Unit f -> c (Unit f) # gunfold :: (forall b r. Data b => c (b -> r) -> c r) -> (forall r. r -> c r) -> Constr -> c (Unit f) # toConstr :: Unit f -> Constr # dataTypeOf :: Unit f -> DataType # dataCast1 :: Typeable t => (forall d. Data d => c (t d)) -> Maybe (c (Unit f)) # dataCast2 :: Typeable t => (forall d e. (Data d, Data e) => c (t d e)) -> Maybe (c (Unit f)) # gmapT :: (forall b. Data b => b -> b) -> Unit f -> Unit f # gmapQl :: (r -> r' -> r) -> r -> (forall d. Data d => d -> r') -> Unit f -> r # gmapQr :: forall r r'. (r' -> r -> r) -> r -> (forall d. Data d => d -> r') -> Unit f -> r # gmapQ :: (forall d. Data d => d -> u) -> Unit f -> [u] # gmapQi :: Int -> (forall d. Data d => d -> u) -> Unit f -> u # gmapM :: Monad m => (forall d. Data d => d -> m d) -> Unit f -> m (Unit f) # gmapMp :: MonadPlus m => (forall d. Data d => d -> m d) -> Unit f -> m (Unit f) # gmapMo :: MonadPlus m => (forall d. Data d => d -> m d) -> Unit f -> m (Unit f) # | |
Monoid (Unit f) Source # | |
Semigroup (Unit f) Source # | |
Generic (Unit f) Source # | |
Read (Unit f) Source # | |
Show (Unit f) Source # | |
Eq (Unit f) Source # | |
Ord (Unit f) Source # | |
type AllB (c :: k -> Constraint) (Unit :: (k -> Type) -> Type) Source # | |
type Rep (Unit f) Source # | |