Safe Haskell | None |
---|---|
Language | Haskell2010 |
The most commonly used names in this module are intended to be imported unqualified:
import Flay (Flay, Flayable(flay), gflay, Dict(Dict))
The rest of the names, qualified:
import qualified Flay
- type Flay c s t f g = forall m. Applicative m => (forall a. Dict (c a) -> f a -> m (g a)) -> s -> m t
- inner :: Flay Trivial s s f f -> s -> s
- class Flayable c s t f g | s -> f, t -> g, s g -> t, t f -> s where
- class Flayable1 c r where
- gflay :: GFlay c s t f g => Flay (c :: k -> Constraint) s t (f :: k -> *) (g :: k -> *)
- class (Generic s, Generic t, GFlay' c (Rep s) (Rep t) f g) => GFlay c s t f g
- class GFlay' c s t f g where
- class All' cs x => All cs x
- class Trivial a
- trivial :: (Applicative m, Flayable Trivial s t f g) => (forall a. Trivial a => f a -> m (g a)) -> s -> m t
- trivial1 :: (Applicative m, Flayable1 Trivial r) => (forall a. Trivial a => f a -> m (g a)) -> r f -> m (r g)
- trivial' :: forall m s t f g. Applicative m => Flay Trivial s t f g -> (forall a. Trivial a => f a -> m (g a)) -> s -> m t
- collect :: (Monoid b, Flayable c s t f (Const ())) => (forall a. Dict (c a) -> f a -> b) -> s -> b
- collect1 :: (Monoid b, Flayable1 c r) => (forall a. Dict (c a) -> f a -> b) -> r f -> b
- collect' :: Monoid b => Flay c s t f (Const ()) -> (forall a. Dict (c a) -> f a -> b) -> s -> b
- zip :: (Record s0, Flayable c s0 t0 f (Const ()), Flayable c s1 t1 g h) => (forall x. Dict (c x) -> f x -> g x -> h x) -> s0 -> s1 -> t1
- zip1 :: (Record (s f), Flayable1 c s) => (forall x. Dict (c x) -> f x -> g x -> h x) -> s f -> s g -> s h
- unsafeZip :: forall c s0 s1 t0 t1 f g h. Record s0 => Flay c s0 t0 f (Const ()) -> Flay c s1 t1 g h -> (forall x. Dict (c x) -> f x -> g x -> h x) -> s0 -> s1 -> t1
- class Record a
- class GRecord a
- terminal :: Terminal a => a
- class Terminal a where
- class GTerminal f where
- data Dict a :: Constraint -> * where
Documentation
type Flay c s t f g = forall m. Applicative m => (forall a. Dict (c a) -> f a -> m (g a)) -> s -> m t Source #
allows converting Flay
c s t f gs
to t
by replacing
ocurrences of f
with g
by applicatively applying a function
(forall a. c a => f a -> m (g a))
to targeted occurences of f a
inside
s
.
A Flay
must obey the inner
identity law.
When defining Flay
values, you should leave c
, f
, and g
fully
polymorphic, as these are the most useful types of Flay
s.
We use
instead of Dict
(c a) ->c a =>
because the latter is often not
enough to satisfy the type checker. With this approach, one must explicitly
pattern match on the
constructor in order to bring the Dict
(c a)c a
instance to scope. Also, it's necessary that c
is explicitly given a type
at the Flay'
s call site, as otherwise the type checker won't be able to
infer c
on its own.
to flay: tr. v., to strip off the skin or surface of.
Mnemonic for c s t f g
: Common STandard FoG.
Example 1: Removing uncertainty
Consider the following types and values:
data Foo f = Foo (fInt
) (fBool
) deriving instance (Show
(fInt
),Show
(fBool
)) =>Show
(Foo f)
flayFoo :: (Applicative
m, cInt
, cBool
) =>Flay
c (Foo f) (Foo g) f g flayFoo h (Foo a b) = Foo <$> hDict
a <*> hDict
b
foo1 :: FooMaybe
foo1 = Foo (Just
1)Nothing
foo2 :: FooMaybe
foo2 = Foo (Just
2) (Just
True
)
It is possible to remove the uncertainty of the fields in Foo
perhaps
being empty (Nothing
) by converting Foo
to Maybe
Foo
.
However, we can't just write a function of type Identity
Foo
because we have the possiblity of some of the fields being
Maybe
-> Foo
Identity
Nothing
, like in foo1
. Instead, we are looking for a function Foo
which will result on Maybe
-> Maybe
(Foo Identity
)Just
only as long
as all of the fields in Foo
is Just
, like in foo2
. This is exactly what
Applicative
enables us to do:
fooMaybeToIdentity :: FooMaybe
->Maybe
(FooIdentity
) fooMaybeToIdentity (Foo a b) = Foo <$>fmap
pure
a <*>fmap
pure
b
Example using this in GHCi:
> fooMaybeToIdentity foo1
Nothing
> fooMaybeToIdentity foo2Just
(Foo (Identity
2) (Identity
True
))
In fact, notice that we are not really working with Just
, Nothing
, nor
Identity
directly, so we might as well just leave Maybe
and Identity
polymorphic. All we need is that they both are Applicative
s:
fooMToG :: (Applicative
m,Applicative
g) => Foo m -> m (Foo g) fooMToG (Foo a b) = Foo <$>fmap
pure
a <*>fmap
pure
b
fooMToG
behaves the same as fooMaybeToIdentity
, but more importantly, it
is much more flexible:
> fooMToG foo2 ::Maybe
(Foo [])Just
(Foo [2] [True
])
> fooMToG foo2 ::Maybe
(Foo (Either
String
))Just
(Foo (Right
2) (Right
True
))
Flay
, among other things, is intended to generalize this pattern, so that
whatever choice of Foo
, Maybe
or Identity
you make, you can use
Applicative
this way. The easiest way to use Flay
is through trivial'
,
which is sufficient unless we need to enforce some constraint in the target
elements wrapped in m
inside foo (we don't need this now). With trivial'
,
we could have defined fooMToG
this way:
fooMToG :: (Applicative
m,Applicative
g) => Foo m -> m (Foo g) fooMToG =trivial'
flayFoo (fmap
pure
)
Some important things to notice here are that we are reusing flayFoo'
s
knowledge of Foo'
s structure, and that the construction of g
using pure
applies to any value wrapped in m
(Int
and Bool
in our case). Compare
this last fact to traverse
, where the types of the targets must be the
same, and known beforehand.
Also, notice that we inlined flayFoo
for convenience in this example, but
we could as well have taken it as an argument, illustrating even more how
Flay
decouples the shape and targets from their processing:
flayMToG :: (Applicative
m,Applicative
g) =>Flay
Trivial
m s t m g -> s -> m s flayMToG fl =trivial'
fl (fmap
pure
)
This is the esscence of Flay
: We can work operating on the contents of a
datatype s
targeted by a given Flay
without knowing anything about s
,
nor about the forall x. f x
targets of the Flay
. And we do this using an
principled approach relying on Applicative
and Functor
.
We can use a Flay
to repurpose a datatype while maintaining its "shape".
For example, given Foo
: Foo
represents the presence of two
values Identity
Int
and Char
, Foo
represents their potential absence,
Maybe
Foo []
represents the potential for zero or more Int
s and Char
s,
Foo (
represent the presence of two values of type Const
x)x
, and Foo
represents two IO
IO
actions necessary to obtain values of type Int
and Char
. We can use flayFoo
to convert between these representations. In
all these cases, the shape of Foo
is preserved, meaning we can continue to
pattern match or project on it. Notice that even though in this example the
f
argument to Foo
happens to always be a Functor
, this is not necessary
at all.
Example 2: Standalone m
In the previous example, flayFoo
took the type Flay
when it was used in Trivial
(Foo m) (Foo
g) m gflayMToG
. That is, m
and f
were unified by
our use of fmap
. However, keeping these different opens interesting
possibilities. For example, let's try and convert a Foo
to a Maybe
Foo
(
, prompting the user for the Either
String
)Left
side of that Either
whenever the original target value is missing.
prompt ::IO
String
prompt = doputStr
"Missing value! Error message? "getLine
fooMaybeToEitherIO :: FooMaybe
->IO
(Foo (Either
String
)) fooMaybeToEitherIO =trivial'
flayFoo $ \caseNothing
->fmap
Left
prompt
Just
x ->pure
(Right
x)
Using this in GHCi:
> fooMaybeToEitherIO foo1 Missing value! Error message? Nooooo!!!!! Foo (Right
1) (Left
"Nooooo!!!!!")
> fooMaybeToEitherIO foo2 Foo (Right
2) (Right
True
)
Example 3: Contexts
Extending the previous example we "replaced" the missing values with a
String
, but wouldn't it be nice if we could somehow prompt a replacement
value of the original type instead? That's what the c
argument to Flay
is
for. Let's replace prompt
so that it can construct a type other than
String
:
prompt ::Read
x =>IO
x prompt = doputStr
"Missing value! Replacement? "readLn
Notice how prompt
now has a
constraint. In order to be able to
use the result of Read
xprompt
as a replacement for our missing values in Foo
, we will have to mention Maybe
Read
as the c
argument to Flay
,
which implies that Read
will have to be a constraint satisfied by all of
the targets of our Flay
(as seen in the constraints in flayFoo
). We can't
use trivial'
anymore, we need to use flayFoo
directly:
fooMaybeToIdentityIO :: FooMaybe
->IO
(FooIdentity
) fooMaybeToIdentityIO = flayFoo h where h ::Dict
(Read
a) ->Maybe
a ->IO
(Identity
a) hDict
= \caseNothing
->fmap
pure
promptJust
a ->pure
(pure
a)
Notice how we had to give an explicit type to our function h
: This is
because can't infer our
constraint. You will always need to
explicitly type the received Read
a
unless the Dict
c
argument to Flay
has
been explicitly by other means (like in the definition of trivial'
, where
we don't have to explicitly type Dict
because c ~
according to
the top level signature of Trivial
trivial'
).
Example using this in GHCi:
> fooMaybeToIdentityIO foo1 Missing value! Replacement?True
Foo (Identity
1) (Identity
True
)
> fooMaybeToIdentityIO foo2 Foo (Identity
2) (Identity
True
)
Of course, as in our previous examples, Identity
here could have
generalized to any Applicative
. We just fixed it to Identity
as an
example.
You can mention as many constraints as you need in c
as long as c
has
kind k ->
(where Constraint
k
is the kind of f
's argument). You can
always group together many constraints as a single new one in order to
achieve this. For example, if you want to require both Show
and Read
on
your target types, then you can introduce the following ShowAndRead
class,
and use that as your c
.
class (Show
a,Read
a) => ShowAndRead a instance (Show
a,Read
a) => ShowAndRead a
This is such a common scenario that the Flay module exports All
, a
Constraint
you can use to apply many Constraint
s at once. For example,
instead of introducing ShowAndRead
, we could use
as our All
'[Show
, Read
]c
argument to Flay
, and the net result would be the same.
Example 4: collect'
See the documentation for collect'
. To sum up: for any given Flay
, we can
collect all of the Flay'
s targets into a Monoid
, without knowing anything
about the targets themselves beyond the fact that they satisfy a particular
constraint.
inner :: Flay Trivial s s f f -> s -> s Source #
Inner identity law:
(\fl ->runIdentity
.trivial'
flpure
) =id
Flayable
class Flayable c s t f g | s -> f, t -> g, s g -> t, t f -> s where Source #
Default Flay
implementation for s
and t
.
When defining Flayable
instances, you should leave c
, f
, and g
fully polymomrphic, as these are the most useful types of Flayables
s.
flay :: Flay c s t f g Source #
If s
and t
are instances of Generic
, then gflay
can be used as
default implementation for flay
. For example, provided the Foo
datatype
shown in the documentation for Flay
had a Generic
instance, then the
following Flayable
instance would get a default implementation for
flay
:
instance (cInt
, cBool
) =>Flayable
c (Foo f) (Foo g) f g whereflay
=gflay
Notice that flay
can be defined in terms of flay1
as well.
Implementors note: Unfortunately, due to some strange bug in GHC, we
can't use DefaultSignatures
to say
, because when doing
that the kind of flay
= gflay
c
infers incorrectly.
class Flayable1 c r where Source #
Flayable1
is Flayable
specialized for the common case of s ~ r f
and
t ~ r g
. The rationale for introducing this seemingly redundant class is
that the Flayable1
constraint is less verbose than Flayable
.
Unfortunately, we can't readily existentialize the arguments to Flayable
,
which is why you'll need to specify both Flayable1
and Flayable
instances. Notice, however, that flay1
can be defined in terms of
flay
and vice-versa, so this should be very mechanical.
flay1 :: Flay c (r f) (r g) f g Source #
If r f
and r g
are instances of Generic
, then flay1
gets a
default implementation. For example, provided the Foo
datatype shown in
the documentation for Flay
had a Generic
instance, then the following
Flayable
instance would get a default implementation for flay1
:
instance (cInt
, cBool
) =>Flayable1
c Foo
flay1 :: GFlay c (r f) (r g) f g => Flay c (r f) (r g) f g Source #
If r f
and r g
are instances of Generic
, then flay1
gets a
default implementation. For example, provided the Foo
datatype shown in
the documentation for Flay
had a Generic
instance, then the following
Flayable
instance would get a default implementation for flay1
:
instance (cInt
, cBool
) =>Flayable1
c Foo
Generics
class (Generic s, Generic t, GFlay' c (Rep s) (Rep t) f g) => GFlay c s t f g Source #
Convenience Constraint
for satisfying basic GFlay'
needs for s
and t
.
class GFlay' c s t f g where Source #
GFlay' * k c V1 V1 f g Source # | |
(GFlay' * k c sl tl f g, GFlay' * k c sr tr f g) => GFlay' * k c ((:+:) sl sr) ((:+:) tl tr) f g Source # | |
(GFlay' * k c sl tl f g, GFlay' * k c sr tr f g) => GFlay' * k c ((:*:) sl sr) ((:*:) tl tr) f g Source # | |
c a => GFlay' * k c (K1 r (f a)) (K1 r (g a)) f g Source # | |
GFlay' * k c s t f g => GFlay' * k c (M1 i j s) (M1 i j t) f g Source # | |
Utils
Constraint
trivially satisfied by every type.
This can be used as the c
parameter to Flay
or Flayable
in case you are
not interested in observing the values inside f
.
:: (Applicative m, Flayable Trivial s t f g) | |
=> (forall a. Trivial a => f a -> m (g a)) | |
-> s | |
-> m t |
:: (Applicative m, Flayable1 Trivial r) | |
=> (forall a. Trivial a => f a -> m (g a)) | |
-> r f | |
-> m (r g) |
:: Applicative m | |
=> Flay Trivial s t f g | |
-> (forall a. Trivial a => f a -> m (g a)) | |
-> s | |
-> m t |
:: (Record (s f), Flayable1 c s) | |
=> (forall x. Dict (c x) -> f x -> g x -> h x) | |
-> s f | |
-> s g | |
-> s h |
Zip two Flayable1
s together.
Example pairing two of the Foo
values seen elsewhere in this file.
> let foo1 =Foo
(Identity
0) (Identity
False
) > ::Foo
Identity
> let foo2 =Foo
(Just
1)Nothing
> ::Foo
Maybe
>zip1
((Dict
::Dict
(Trivial
x)) a b ->Pair
a b) foo1 foo2 > ::Foo
(Product
Identity
Maybe
)Foo
(Pair
(Identity
0) (Just
1)) (Pair
(Identity
False
)Nothing
) >zip1
((Dict
::Dict
(Show
x)) (Identity
a) yb -> case yb of >Nothing
->Const
(show
a) >Just
b ->Const
(show
(a, b)) ) > foo1 foo2 > :: Foo (Const
String
) Foo (Const
"(0,1)") (Const
"False")
Handwavy class of which only product or record types are supposed to be instances.
class Terminal a where Source #
Witness that a
is a terminal object. That is, that a
can always be
constructed out of thin air.
Re-exports
data Dict a :: Constraint -> * where #
Values of type
capture a dictionary for a constraint of type Dict
pp
.
e.g.
Dict
::Dict
(Eq
Int
)
captures a dictionary that proves we have an:
instance Eq
'Int
Pattern matching on the Dict
constructor will bring this instance into scope.
a :=> (Read (Dict a)) | |
a :=> (Monoid (Dict a)) | |
a :=> (Enum (Dict a)) | |
a :=> (Bounded (Dict a)) | |
() :=> (Eq (Dict a)) | |
() :=> (Ord (Dict a)) | |
() :=> (Show (Dict a)) | |
a => Bounded (Dict a) | |
a => Enum (Dict a) | |
Eq (Dict a) | |
(Typeable Constraint p, p) => Data (Dict p) | |
Ord (Dict a) | |
a => Read (Dict a) | |
Show (Dict a) | |
a => Monoid (Dict a) | |