Control.Lens.Iso
contains many wonderful functions for lifting Iso
s into various type arguments of useful abstractions. For example:
mapping
for arbitrary Functor
scontramapping
for Contravariant
functorsdimapping
, lmapping
and rmapping
for Profunctor
sbimapping
for Bifunctor
sI am looking for the function to lift an Iso
into the first
argument of a Bifunctor
, but it doesn't seem to be there. I am currently defining it thusly:
firsting
:: Bifunctor f
=> AnIso s t a b
-> Iso (f s x) (f t y) (f a x) (f b y)
firsting p = bimapping p (iso id id)
Does this function already exist somewhere, or is bimapping p (iso id id)
as good as it gets?
Thanks to your question, the next version of Control.Lens.Iso
will include firsting
and seconding
.
iso id id
looks a bit ugly. Let's try to take that apart.
type Iso s t a b =
forall f p . (Functor f, Profunctor p) =>
p a (f b) -> p s (f t)
--plain :: Iso s t s t
--plain = iso id id
plain :: (Functor f, Profunctor p) => p s (f t) -> p s (f t)
plain = id
So you can cut your implementation down to
firsting p = bimapping p id
This is probably the most concise form. If you want to really get to the bare bones, of it, read on.
Inlining the definition of bimapping
bimapping :: (Bifunctor f, Bifunctor g) => AnIso s t a b -> AnIso s' t' a' b' -> Iso (f s s') (g t t') (f a a') (g b b')
bimapping f g = withIso f $ \ sa bt -> withIso g $ \s'a' b't' ->
iso (bimap sa s'a') (bimap bt b't')
and simplifying using first
, you get
firsting p = withIso p $ \ sa bt ->
iso (first sa) (first bt)
This is a particularly clear expression, I think. It uses withIso
to break p
into the two functions that make up the isomorphism, uses first
to lift each of them to apply to the first argument of the bifunctor, and then packages them back up with iso
. If the relevant bifunctor has an optimized first
that does something better than can be done with bimap
, then this will also be faster than the implementation using bimapping
.
Inlining iso
gives
firsting p = withIso p $ \ sa bt ->
dimap (first sa) (fmap (first bt))
Finally, inlining withIso
(digging into Control.Lens.Internal.Iso
, which we probably shouldn't do),
firsting p =
case p (Exchange id Identity) of
Exchange sa bt ->
dimap (first sa) (fmap (first (runIdentity #. bt)))
By the way, the type signature for plain
, without the redundant context, is
plain :: p s (f t) -> p s (f t)
This is exactly the same as
plain :: Equality s t s t