| Safe Haskell | Safe |
|---|---|
| Language | Haskell2010 |
Flay
Contents
Description
The most commonly used names in this module are intended to be imported unqualified, as necessary:
import Flay (Flay, Flayable(flay), Flayable1(flay1))
The rest of the names, qualified:
import qualified Flay
- type Flay (c :: k -> Constraint) s t (f :: k -> *) (g :: k -> *) = forall m. Applicative m => (forall (a :: k). Dict (c a) -> f a -> m (g a)) -> s -> m t
- inner :: Flay Trivial s s f f -> s -> s
- class Flayable (c :: k -> Constraint) s t (f :: k -> *) (g :: k -> *) | s -> f, t -> g, s g -> t, t f -> s where
- class Flayable1 (c :: k -> Constraint) (r :: (k -> *) -> *) 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 :: k -> Constraint) s t (f :: k -> *) (g :: k -> *)
- class GFlay' (c :: k -> Constraint) s t (f :: k -> *) (g :: k -> *) where
- class All' cs x => All (cs :: [k -> Constraint]) (x :: k)
- class Trivial (a :: k)
- trivialize :: forall c s t f g. Flay c s t f g -> Flay Trivial s t f g
- trivial :: forall m s t f g. (Applicative m, Flayable Trivial s t f g) => (forall a. Trivial a => f a -> m (g a)) -> s -> m t
- trivial1 :: forall m f g r. (Applicative m, Flayable1 Trivial r) => (forall a. Trivial a => f a -> m (g a)) -> r f -> m (r g)
- trivial' :: forall m c s t f g. Applicative m => Flay c 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 :: (Monad m, Typeable f, Flayable Typeable s1 t1 f (Const ()), Flayable Typeable s2 t2 g (Product f g), Flayable c t2 t3 (Product f g) h) => (forall x. Dict (c x) -> f x -> g x -> m (h x)) -> s1 -> s2 -> m (Maybe t3)
- zip1 :: (Monad m, Typeable f, Flayable1 c s, Flayable1 Typeable s) => (forall x. Dict (c x) -> f x -> g x -> m (h x)) -> s f -> s g -> m (Maybe (s h))
- unsafeZip :: forall c s1 s2 t1 t2 t3 f g h m. (Monad m, Typeable f) => Flay Typeable s1 t1 f (Const ()) -> Flay Typeable s2 t2 g (Product f g) -> Flay c t2 t3 (Product f g) h -> (forall x. Dict (c x) -> f x -> g x -> m (h x)) -> s1 -> s2 -> m (Maybe t3)
- terminal :: Terminal a => a
- class Terminal a where
- class GTerminal (f :: * -> *) where
- data Dict a :: Constraint -> * where
Documentation
type Flay (c :: k -> Constraint) s t (f :: k -> *) (g :: k -> *) = forall m. Applicative m => (forall (a :: k). 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 Flays.
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 :: (Applicativem, cInt, cBool) =>Flayc (Foo f) (Foo g) f g flayFoo h (Foo a b) = Foo <$> hDicta <*> hDictb
foo1 :: FooMaybefoo1 = Foo (Just1)Nothing
foo2 :: FooMaybefoo2 = Foo (Just2) (JustTrue)
It is possible to remove the uncertainty of the fields in Foo perhaps
being empty (Nothing) by converting Foo to MaybeFoo .
However, we can't just write a function of type IdentityFoo because we have the possiblity of some of the fields being
Maybe -> Foo
IdentityNothing, 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 <$>fmappurea <*>fmappureb
Example using this in GHCi:
> fooMaybeToIdentity foo1
Nothing
> fooMaybeToIdentity foo2Just(Foo (Identity2) (IdentityTrue))
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 Applicatives:
fooMToG :: (Applicativem,Applicativeg) => Foo m -> m (Foo g) fooMToG (Foo a b) = Foo <$>fmappurea <*>fmappureb
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 (EitherString))Just(Foo (Right2) (RightTrue))
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 :: (Applicativem,Applicativeg) => Foo m -> m (Foo g) fooMToG =trivial'flayFoo (fmappure)
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 :: (Applicativem,Applicativeg) =>FlayTrivialm s t m g -> s -> m s flayMToG fl =trivial'fl (fmappure)
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 IdentityInt and Char, Foo represents their potential absence,
MaybeFoo [] represents the potential for zero or more Ints and Chars,
Foo ( represent the presence of two values of type Const x)x, and Foo
represents two IOIO 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 MaybeFoo
(, prompting the user for the Either String)Left side of that Either
whenever the original target value is missing.
prompt ::IOStringprompt = doputStr"Missing value! Error message? "getLine
fooMaybeToEitherIO :: FooMaybe->IO(Foo (EitherString)) fooMaybeToEitherIO =trivial'flayFoo $ \caseNothing->fmapLeftpromptJustx ->pure(Rightx)
Using this in GHCi:
> fooMaybeToEitherIO foo1 Missing value! Error message? Nooooo!!!!! Foo (Right1) (Left"Nooooo!!!!!")
> fooMaybeToEitherIO foo2 Foo (Right2) (RightTrue)
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 ::Readx =>IOx 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 MaybeRead 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(Reada) ->Maybea ->IO(Identitya) hDict= \caseNothing->fmappurepromptJusta ->pure(purea)
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 Dictc 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 Trivialtrivial').
Example using this in GHCi:
> fooMaybeToIdentityIO foo1 Missing value! Replacement?TrueFoo (Identity1) (IdentityTrue)
> fooMaybeToIdentityIO foo2 Foo (Identity2) (IdentityTrue)
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 Constraintk 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 (Showa,Reada) => ShowAndRead a instance (Showa,Reada) => ShowAndRead a
This is such a common scenario that the Flay module exports All, a
Constraint you can use to apply many Constraints 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 :: k -> Constraint) s t (f :: k -> *) (g :: k -> *) | 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 Flayabless.
Minimal complete definition
Methods
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) =>Flayablec (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 = gflayc infers incorrectly.
class Flayable1 (c :: k -> Constraint) (r :: (k -> *) -> *) 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.
Methods
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) =>Flayable1c 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) =>Flayable1c Foo
Generics
class (Generic s, Generic t, GFlay' c (Rep s) (Rep t) f g) => GFlay (c :: k -> Constraint) s t (f :: k -> *) (g :: k -> *) Source #
Convenience Constraint for satisfying basic GFlay' needs for s and t.
class GFlay' (c :: k -> Constraint) s t (f :: k -> *) (g :: k -> *) where Source #
Minimal complete definition
Instances
| GFlay' k1 k2 c (V1 k1) (V1 k1) f g Source # | |
| (GFlay' k1 k2 c sl tl f g, GFlay' k1 k2 c sr tr f g) => GFlay' k1 k2 c ((:+:) k1 sl sr) ((:+:) k1 tl tr) f g Source # | |
| (GFlay' k1 k2 c sl tl f g, GFlay' k1 k2 c sr tr f g) => GFlay' k1 k2 c ((:*:) k1 sl sr) ((:*:) k1 tl tr) f g Source # | |
| c a => GFlay' k1 k2 c (K1 k1 r (f a)) (K1 k1 r (g a)) f g Source # | |
| GFlay' k1 k2 c s t f g => GFlay' k1 k2 c (M1 k1 i j s) (M1 k1 i j t) f g Source # | |
Utils
class All' cs x => All (cs :: [k -> Constraint]) (x :: k) Source #
Ensure that x satisfies all of the constraints listed in cs.
class Trivial (a :: k) Source #
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.
Arguments
| :: (Applicative m, Flayable Trivial s t f g) | |
| => (forall a. Trivial a => f a -> m (g a)) | |
| -> s | |
| -> m t |
Arguments
| :: (Applicative m, Flayable1 Trivial r) | |
| => (forall a. Trivial a => f a -> m (g a)) | |
| -> r f | |
| -> m (r g) |
Arguments
| :: Applicative m | |
| => Flay c s t f g | |
| -> (forall a. Trivial a => f a -> m (g a)) | |
| -> s | |
| -> m t |
Arguments
| :: (Monad m, Typeable f, Flayable1 c s, Flayable1 Typeable s) | |
| => (forall x. Dict (c x) -> f x -> g x -> m (h x)) | |
| -> s f | |
| -> s g | |
| -> m (Maybe (s h)) |
Zip two Flayable1s together.
Example pairing two of the Foo values seen elsewhere in this file.
> let foo1 =Foo(Identity0) (IdentityFalse) > ::FooIdentity> let foo2 =Foo(Just1)Nothing> ::FooMaybe>zip1((Dict::Dict(Trivialx)) a b ->Paira b) foo1 foo2 > ::Foo(ProductIdentityMaybe)Foo(Pair(Identity0) (Just1)) (Pair(IdentityFalse)Nothing) >zip1((Dict::Dict(Showx)) (Identitya) yb -> case yb of >Nothing->Const(showa) >Justb ->Const(show(a, b)) ) > foo1 foo2 > :: Foo (ConstString) Foo (Const"(0,1)") (Const"False")
Returns Nothing in case the indivual target types do not match.
class Terminal a where Source #
Witness that a is a terminal object. That is, that a can always be
constructed out of thin air.
Minimal complete definition
Re-exports
data Dict a :: Constraint -> * where #
Values of type capture a dictionary for a constraint of type Dict pp.
e.g.
Dict::Dict(EqInt)
captures a dictionary that proves we have an:
instance Eq 'Int
Pattern matching on the Dict constructor will bring this instance into scope.
Instances
| 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)) | |
| () :=> (Semigroup (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) | |
| Semigroup (Dict a) | |
| a => Monoid (Dict a) | |
| NFData (Dict c) | |