'Mergeable with sum and product in type indices
Is there anything in Haskell resembling the following type class?
class Mergeable (f :: Type -> Type -> Type) where
merge :: f a b -> f c d -> f (a, c) (Either b d)
In particular, imagine there is a Site
type indexed on some value and a route:
data Site a r
And we want to "merge" two sites, such that both their data (value) is kept in memory while supporting either of their routes.
instance Mergeable Site where
merge site1 site2 = ...
There is another type, called RouteEncoder a r
with the same type shape. So I figured I ought to look for a common pattern here ...
EDIT: Full type definitions of Site and RouteEncoder as requested:
data Site a r = Site
{ siteName :: Text,
siteRender ::
Some CLI.Action ->
RouteEncoder a r ->
a ->
r ->
Asset LByteString,
-- | Thread that will patch the model over time.
siteModelData :: ModelRunner a,
siteRouteEncoder :: RouteEncoder a r
}
type RouteEncoder a r = PartialIsoEnumerableWithCtx a FilePath r
-- | An Iso that is not necessarily surjective; as well as takes an (unchanging)
-- context value.
--
-- Parse `s` into (optional) `a` which can always be converted to a `s`. The `a`
-- can be enumerated finitely. `ctx` is used to all functions.
-- TODO: Is this isomrophic to `Iso (ctx, a) (Maybe a) s (ctx, s)` (plus, `ctx -> [a]`)?
data PartialIsoEnumerableWithCtx ctx s a
= PartialIsoEnumerableWithCtx (ctx -> a -> s, ctx -> s -> Maybe a, ctx -> [a])
The code with full context can be seen in this PR: https://github.com/srid/ema/pull/81/files (see also PartialIsoFunctor
which class probably should be simplified as well).
Solution 1:[1]
assumed understanding: the difference between covariant and contravariant functors, and by extension, bifunctors and profunctors
summary: you want either a bifunctor that's applicative and alternative, or a profunctor from divisible to alternative, depending on whether it's a bifunctor or a profunctor
a functor f, which can take an associative pair (aka product, tuple) of 'f's and return an f of pairs, is called applicative (well, it's the Apply typeclass from 'semigroupoids', applicatives also need a unit)
that is to say, f is equipped with:
pair :: f a -> f b -> f (a,b)
pair = liftA2 (,)
pairUnit :: f ()
pairUnit = pure ()
the equivalent thing for choices (aka sums, either) is similarly related to alternative functors (it's the Alt typeclass from 'semigroupoids'):
pick :: f a -> f b -> f (Either a b)
pick fa fb = fmap Left fa <|> fmap Right fb
pickUnit :: f Void
pickUnit = empty
something of note about these two formulations of applicative and alternative (sans units) is that they're not the whole story: there's also contravariant applicative functors, and contravariant alternative functors, which are the Divisible and Decidable typeclasses respectively from 'contravariant'
an applicative functor knows how to combine parts into a whole, a divisible functor knows how to split a whole into parts
pairCovariant :: ((a,b) -> c) -> f a -> f b -> f c
pairCovariant f fa fb = fmap f (pair fa fb)
-- liftA2 f = pairCovariant (curry f)
-- this is also called 'divide'
pairContravariant :: (c -> (a,b)) -> f a -> f b -> f c
pairContravariant f fa fb = contramap f (pair fa fb)
-- given an inhabitant of a, provide an f a
pure :: a -> f a
pure a = fmap (const a) . pairUnit
-- this is also called 'conquer'
emptyPair :: f a
emptyPair = fmap (const ()) . pairUnit
(note the similarity between pair and zip, and pairCovariant and zipWith - they're the same functions in different clothing, where zip and zipWith describe the ZipList interpretation of applicative lists)
an alternative functor knows how to combine options into a whole, a decidable functor knows how to split a whole into options
pickCovariant :: (Either a b -> c) -> f a -> f b -> f c
pickCovariant f fa fb = fmap f (pick fa fb)
-- aka choose
pickContravariant :: (c -> Either a b) -> f a -> f b -> f c
pickContravariant f fa fb = contramap f (pick fa fb)
empty :: f a
empty = fmap absurd . pickUnit
-- given proof a is uninhabited, provide an f a
-- aka lose
pureChoice :: (a -> Void) -> f a
pureChoice a = contramap a . pureUnit
so you want applicative/divisible on your first argument, and alternative/decidable on your second argument, depending on co/contravariance on each argument
in other words: if it's a bifunctor, applicative and alternative, if it's a profunctor, divisible and alternative
summary of the ideas here: applicative, alternative (sans the applicative superclass), divisible, and decidable functors are all versions of the same thing, made by choosing between pairs and choices, and co/contravariance
aka, applicatives are pairwise monoidal covariant functors, alternatives are choicewise monoidal contravariant functors, etc
further reading:
- on the applicative family described here: https://duplode.github.io/posts/divisible-and-the-monoidal-quartet.html
- profunctors that are divisible -> applicative: https://hackage.haskell.org/package/product-profunctors
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Eric Aya |