-- Hoogle documentation, generated by Haddock -- See Hoogle, http://www.haskell.org/hoogle/ -- | Work on your datatype without knowing its shape nor its contents. -- -- Work on your datatype without knowing its shape nor its contents using -- a principlied approach. @package flay @version 0.1 -- | The most commonly used names in this module are intended to be -- imported unqualified: -- --
--   import Flay (Flay, Flayable(flay), Dict(Dict))
--   
-- -- The rest of the names, qualified: -- --
--   import qualified Flay
--   
module Flay -- | Flay c m s t f g allows converting s 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 (and -- outer identity law as well, if the Flay fits the type -- expected by outer). -- -- When defining Flay values, you should leave c, -- m, f, and g fully polymomrphic, as these -- are the most useful types of Flays. -- -- When using a Flay, m will be required to be a -- Functor in case the Flay targets one element, or an -- Applicative if it targets more than one. There will be no -- constraints on the rest of the arguments to Flay. -- -- We use Dict (c a) -> instead of c a => -- because the latter is often not enough to satisfy the type checker. -- With this approach, one must explicitely pattern match on the -- Dict (c a) constructor in order to bring the 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 m s t f g: CoMmon STandard FoG. -- --

Example 1: Removing uncertaininy

-- -- Consider the following types and values: -- --
--   data Foo f = Foo (f Int) (f Bool)
--   
--   deriving instance (Show (f Int), Show (f Bool)) => Show (Foo f)
--   
-- --
--   flayFoo :: (Applicative m, c Int, c Bool) => Flay c m (Foo f) (Foo g) f g
--   flayFoo h (Foo a b) = Foo <$> h Dict a <*> h Dict b
--   
-- --
--   foo1 :: Foo Maybe
--   foo1 = Foo (Just 1) Nothing
--   
-- --
--   foo2 :: Foo Maybe
--   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 Maybe to Foo Identity. However, we -- can't just write a function of type Foo Maybe -> Foo -- Identity because we have the possiblity of some of the -- fields being Nothing, like in foo1. Instead, we are -- looking for a function Foo Maybe -> Maybe (Foo -- Identity) which will result on 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 :: Foo Maybe -> Maybe (Foo Identity)
--   fooMaybeToIdentity (Foo a b) = Foo <$> fmap pure a <*> fmap pure b
--   
-- -- Example using this in GHCi: -- --
--   > fooMaybeToIdentity foo1
--   Nothing
--   
-- --
--   > fooMaybeToIdentity foo2
--   Just (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 Applicatives: -- --
--   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 constrain 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 escence of Flay: We can work operate 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 Identity -- represents the presence of two values Int and Char, -- Foo Maybe represents their potential absence, Foo -- [] represents the potential for zero or more Ints and -- Chars, Foo (Const x) represent the presence of -- two values of type x, and Foo IO represents -- two 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 -- Trivial m (Foo m) (Foo g) m g when it was used in -- flayMToG. 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 Maybe to a Foo (Either -- String), prompting the user for the Left side of -- that Either whenever the original target value is missing. -- --
--   prompt :: IO String
--   prompt = do
--     putStr "Missing value! Error message? "
--     getLine
--   
-- --
--   fooMaybeToEitherIO :: Foo Maybe -> IO (Foo (Either String))
--   fooMaybeToEitherIO = trivial' flayFoo $ \case
--      Nothing -> 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 = do
--     putStr "Missing value! Replacement? "
--     readLn
--   
-- -- Notice how prompt now has a Read x -- constraint. In order to be able to use the result of prompt -- as a replacement for our missing values in Foo Maybe, -- we will have to mention 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 :: Foo Maybe -> IO (Foo Identity)
--   fooMaybeToIdentityIO = flayFoo h
--     where h :: Dict (Read a) -> Maybe a -> IO (Identity a)
--           h Dict = \case
--               Nothing -> fmap pure prompt
--               Just a -> pure (pure a)
--   
-- -- Notice how we had to give an explicit type to our function h: -- This is because can't infer our Read a constraint. You -- will always need to explicitly type the received Dict -- unless the c argument to Flay has been explicitely by -- other means (like in the definition of trivial', where we don't -- have to explicitly type Dict because c ~ -- Trivial according to the top level signature of -- 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 -> Constraint (where -- 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 -- Constraints at once. For example, instead of introducing -- ShowAndRead, we could use All '[Show, -- Read] as our 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. type Flay (c :: k -> Constraint) (m :: * -> *) s t (f :: k -> *) (g :: k -> *) = (forall (a :: k). Dict (c a) -> f a -> m (g a)) -> s -> m t -- | Inner identity law: -- --
--   (\fl -> runIdentity . trivial' fl pure) = id
--   
inner :: Flay Trivial Identity s s f f -> s -> s -- | Outer identity law: -- --
--   (\fl -> join . trivial' fl pure) = id
--   
outer :: Monad m => Flay Trivial m (m x) (m x) m m -> m x -> m x -- | Default Flay implementation for s and t. -- -- When defining Flayable instances, you should leave c, -- m, f, and g fully polymomrphic, as these -- are the most useful types of Flayabless. class Flayable (c :: * -> Constraint) m s t f g | s -> f, t -> g, s g -> t, t f -> s where flay = gflay -- | If s and g are instances of Generic, then -- flay 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 flay: -- --
--   instance (Applicative m, c Int, c Bool) => Flayable c m (Foo f) (Foo g) f g
--   
-- -- Notice that while this default definition works for an s -- having "nested Flayables", GHC will prompt you for some -- additional constraints related to GFlay' in order for it to -- compile. flay :: Flayable c m s t f g => Flay c m s t f g -- | If s and g are instances of Generic, then -- flay 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 flay: -- --
--   instance (Applicative m, c Int, c Bool) => Flayable c m (Foo f) (Foo g) f g
--   
-- -- Notice that while this default definition works for an s -- having "nested Flayables", GHC will prompt you for some -- additional constraints related to GFlay' in order for it to -- compile. flay :: (Flayable c m s t f g, Functor m, GFlay c m s t f g) => Flay c m s t f g gflay :: (Functor m, GFlay c m s t f g) => Flay c m s t f g -- | Convenience Constraint for satisfying basic GFlay' needs -- for s and t. class (Generic s, Generic t, GFlay' c m (Rep s) (Rep t) f g) => GFlay (c :: k -> Constraint) m s t f g class GFlay' (c :: k -> Constraint) m s t f g gflay' :: GFlay' c m s t f g => Flay c m (s p) (t p) f g -- | Ensure that x satisfies all of the constraints listed in -- cs. class All' cs x => All (cs :: [k -> Constraint]) (x :: k) -- | 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. class Trivial (a :: k) -- | Like trivial', but works on a Flayable instead of taking -- an explicit Flay. -- --
--   trivial = trivial' flay
--   
trivial :: Flayable Trivial m s t f g => (forall a. Trivial a => f a -> m (g a)) -> s -> m t -- | You can use trivial' if you don't care about the c -- argument to Flay. This implies that you won't be able to -- observe the a in forall a. f a, all you can do with -- such a is pass it around. -- --
--   trivial' fl h
--      = fl (\(Dict :: Dict (Trivial a)) (fa :: f a) -> h fa)
--   
trivial' :: forall m s t f g. Flay Trivial m s t f g -> (forall a. Trivial a => f a -> m (g a)) -> s -> m t -- | Like collect', but works on a Flayable instead of an -- explicit Flay. collect :: (Monoid b, Flayable c (Const b) s t f (Const ())) => (forall a. Dict (c a) -> f a -> b) -> s -> b -- | Collect all of the f a of the given Flay into a -- Monoid b. -- -- Example usage, given Foo and flayFoo examples given -- in the documentation for Flay: -- --
--   > collect' flayFoo
--         (\(Dict :: Dict (Show a)) (Identity (a :: a)) -> [show a])
--         (Foo (pure 4) (pure True))
--   ["4","True"]
--   
collect' :: Monoid b => Flay c (Const b) s t f (Const ()) -> (forall a. Dict (c a) -> f a -> b) -> s -> b -- | Values of type Dict p capture a dictionary for a -- constraint of type p. -- -- 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. data Dict (a :: Constraint) :: Constraint -> * [Dict] :: Dict a instance c a => Flay.Flayable c m (f a) (g a) f g instance forall k (a :: k). Flay.Trivial a instance forall k s t (c :: k -> GHC.Types.Constraint) (m :: GHC.Types.* -> GHC.Types.*) (f :: k -> GHC.Types.*) (g :: k -> GHC.Types.*). (GHC.Generics.Generic s, GHC.Generics.Generic t, Flay.GFlay' c m (GHC.Generics.Rep s) (GHC.Generics.Rep t) f g) => Flay.GFlay c m s t f g instance forall k (c :: k -> GHC.Types.Constraint) (m :: GHC.Types.* -> GHC.Types.*) (f :: k -> GHC.Types.*) (g :: k -> GHC.Types.*). Flay.GFlay' c m GHC.Generics.V1 GHC.Generics.V1 f g instance forall k (m :: * -> *) (c :: k -> GHC.Types.Constraint) (s :: GHC.Types.* -> *) (t :: GHC.Types.* -> GHC.Types.*) (f :: k -> GHC.Types.*) (g :: k -> GHC.Types.*). (GHC.Base.Functor m, Flay.GFlay' c m s t f g) => Flay.GFlay' c m (GHC.Generics.Rec1 s) (GHC.Generics.Rec1 t) f g instance (GHC.Base.Functor m, Flay.Flayable c m (f a) (g a) f g) => Flay.GFlay' c m (GHC.Generics.K1 r (f a)) (GHC.Generics.K1 r (g a)) f g instance forall k (m :: * -> *) (c :: k -> GHC.Types.Constraint) r x (f :: k -> GHC.Types.*) (g :: k -> GHC.Types.*). GHC.Base.Applicative m => Flay.GFlay' c m (GHC.Generics.K1 r x) (GHC.Generics.K1 r x) f g instance forall k (m :: * -> *) (c :: k -> GHC.Types.Constraint) (s :: GHC.Types.* -> *) (t :: GHC.Types.* -> GHC.Types.*) (f :: k -> GHC.Types.*) (g :: k -> GHC.Types.*) i (j :: GHC.Generics.Meta). (GHC.Base.Functor m, Flay.GFlay' c m s t f g) => Flay.GFlay' c m (GHC.Generics.M1 i j s) (GHC.Generics.M1 i j t) f g instance forall k (m :: * -> *) (c :: k -> GHC.Types.Constraint) (sl :: GHC.Types.* -> *) (tl :: GHC.Types.* -> GHC.Types.*) (f :: k -> GHC.Types.*) (g :: k -> GHC.Types.*) (sr :: GHC.Types.* -> *) (tr :: GHC.Types.* -> GHC.Types.*). (GHC.Base.Applicative m, Flay.GFlay' c m sl tl f g, Flay.GFlay' c m sr tr f g) => Flay.GFlay' c m (sl GHC.Generics.:*: sr) (tl GHC.Generics.:*: tr) f g instance forall k (m :: * -> *) (c :: k -> GHC.Types.Constraint) (sl :: GHC.Types.* -> *) (tl :: GHC.Types.* -> GHC.Types.*) (f :: k -> GHC.Types.*) (g :: k -> GHC.Types.*) (sr :: GHC.Types.* -> *) (tr :: GHC.Types.* -> GHC.Types.*). (GHC.Base.Functor m, Flay.GFlay' c m sl tl f g, Flay.GFlay' c m sr tr f g) => Flay.GFlay' c m (sl GHC.Generics.:+: sr) (tl GHC.Generics.:+: tr) f g instance forall k (cs :: [k -> GHC.Types.Constraint]) (x :: k). Flay.All' cs x => Flay.All cs x