-- Hoogle documentation, generated by Haddock -- See Hoogle, http://www.haskell.org/hoogle/ -- | Work generically on your datatype without knowing its shape nor its contents. -- -- Work generically on your datatype without knowing its shape nor its -- contents using a principlied approach. @package flay @version 0.2 -- | 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 --module Flay -- | Flay c 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. -- -- When defining Flay values, you should leave c, -- f, and g fully polymorphic, as these are the most -- useful types of Flays. -- -- 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 explicitly 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 s t f g: Common STandard FoG. -- --
-- 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 (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 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 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. -- --
-- 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) ---- --
-- 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 explicitly 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. -- --
-- (\fl -> runIdentity . trivial' fl pure) = id --inner :: Flay Trivial s s f f -> s -> s -- | 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. class Flayable (c :: k -> Constraint) s t (f :: k -> *) (g :: k -> *) | s -> f, t -> g, s g -> t, t f -> s -- | 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 (c Int, c Bool) => Flayable c (Foo f) (Foo g) f g where -- flay = 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 flay = -- gflay, because when doing that the kind of c -- infers incorrectly. flay :: Flayable c s t f g => Flay c s t f g -- | 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. class Flayable1 (c :: k -> Constraint) (r :: (k -> *) -> *) where flay1 = gflay -- | 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 (c Int, c Bool) => Flayable1 c Foo ---- -- Notice that flay1 can be defined in terms of flay as -- well. flay1 :: Flayable1 c r => Flay c (r f) (r g) f g -- | 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 (c Int, c Bool) => Flayable1 c Foo ---- -- Notice that flay1 can be defined in terms of flay as -- well. flay1 :: (Flayable1 c r, GFlay c (r f) (r g) f g) => Flay c (r f) (r g) f g gflay :: GFlay c s t f g => Flay (c :: k -> Constraint) s t (f :: k -> *) (g :: k -> *) -- | Convenience Constraint for satisfying basic GFlay' needs -- for s and t. 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 -> *) gflay' :: GFlay' c s t f g => Flay c (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 :: (Applicative m, Flayable Trivial s t f g) => (forall a. Trivial a => f a -> m (g a)) -> s -> m t -- | Like trivial', but works on a Flayable1 instead of -- taking an explicit Flay. -- --
-- trivial = trivial' flay1 --trivial1 :: (Applicative m, Flayable1 Trivial r) => (forall a. Trivial a => f a -> m (g a)) -> (r f) -> m (r g) -- | 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. Applicative m => Flay Trivial 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 s t f (Const ())) => (forall a. Dict (c a) -> f a -> b) -> s -> b -- | Like collect', but works on a Flayable1 instead of an -- explicit Flay. collect1 :: (Monoid b, Flayable1 c r) => (forall a. Dict (c a) -> f a -> b) -> r f -> 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 s t f (Const ()) -> (forall a. Dict (c a) -> f a -> b) -> s -> b -- | Zip two Flayables together. -- -- zip is like zip1, but for Flayables. -- -- Note: zip is safer but less general than unsafeZip. 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 -- | Zip two Flayable1s 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") ---- -- Note: zip1 is safer but less general than unsafeZip. zip1 :: (Record (s f), Flayable1 c s) => (forall x. Dict (c x) -> f x -> g x -> h x) -> s f -> s g -> s h -- | Unsafe version of zip that doesn't guarantee that the given -- Flays target the same values. zip makes this function -- safe by simply using flay twice. 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 -- | Handwavy class of which only product or record types are supposed to -- be instances. class Record a class GRecord (a :: * -> *) terminal :: Terminal a => a -- | Witness that a is a terminal object. That is, that a -- can always be constructed out of thin air. class Terminal a terminal :: Terminal a => a class GTerminal (f :: * -> *) gterminal :: GTerminal f => f p -- | 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 forall k (a :: k). Flay.Trivial a instance forall k s t (c :: k -> GHC.Types.Constraint) (f :: k -> GHC.Types.*) (g :: k -> GHC.Types.*). (GHC.Generics.Generic s, GHC.Generics.Generic t, Flay.GFlay' c (GHC.Generics.Rep s) (GHC.Generics.Rep t) f g) => Flay.GFlay c s t f g instance forall k (c :: k -> GHC.Types.Constraint) (f :: k -> GHC.Types.*) (g :: k -> GHC.Types.*). Flay.GFlay' c GHC.Generics.V1 GHC.Generics.V1 f g instance forall k (c :: k -> GHC.Types.Constraint) (a :: k) r (f :: k -> *) (g :: k -> *). c a => Flay.GFlay' c (GHC.Generics.K1 r (f a)) (GHC.Generics.K1 r (g a)) f g instance forall k (c :: k -> GHC.Types.Constraint) (s :: GHC.Types.* -> *) (t :: GHC.Types.* -> *) (f :: k -> GHC.Types.*) (g :: k -> GHC.Types.*) i (j :: GHC.Generics.Meta). Flay.GFlay' c s t f g => Flay.GFlay' c (GHC.Generics.M1 i j s) (GHC.Generics.M1 i j t) f g instance forall k (c :: k -> GHC.Types.Constraint) (sl :: GHC.Types.* -> *) (tl :: GHC.Types.* -> *) (f :: k -> GHC.Types.*) (g :: k -> GHC.Types.*) (sr :: GHC.Types.* -> *) (tr :: GHC.Types.* -> *). (Flay.GFlay' c sl tl f g, Flay.GFlay' c sr tr f g) => Flay.GFlay' c (sl GHC.Generics.:*: sr) (tl GHC.Generics.:*: tr) f g instance forall k (c :: k -> GHC.Types.Constraint) (sl :: GHC.Types.* -> *) (tl :: GHC.Types.* -> *) (f :: k -> GHC.Types.*) (g :: k -> GHC.Types.*) (sr :: GHC.Types.* -> *) (tr :: GHC.Types.* -> *). (Flay.GFlay' c sl tl f g, Flay.GFlay' c sr tr f g) => Flay.GFlay' c (sl GHC.Generics.:+: sr) (tl GHC.Generics.:+: tr) f g instance (GHC.Generics.Generic a, Flay.GRecord (GHC.Generics.Rep a)) => Flay.Record a instance Flay.GRecord GHC.Generics.U1 instance Flay.GRecord (GHC.Generics.K1 r x) instance Flay.GRecord x => Flay.GRecord (GHC.Generics.M1 i j x) instance (Flay.GRecord l, Flay.GRecord r) => Flay.GRecord (l GHC.Generics.:*: r) instance Flay.Terminal () instance (GHC.Generics.Generic a, Flay.GTerminal (GHC.Generics.Rep a)) => Flay.Terminal a instance forall k (a :: k). Flay.Terminal (Data.Functor.Const.Const () a) instance Flay.Terminal x => Flay.GTerminal (GHC.Generics.K1 i x) instance Flay.GTerminal f => Flay.GTerminal (GHC.Generics.M1 i c f) instance (Flay.GTerminal l, Flay.GTerminal r) => Flay.GTerminal (l GHC.Generics.:*: r) instance forall k (cs :: [k -> GHC.Types.Constraint]) (x :: k). Flay.All' cs x => Flay.All cs x