3

Control.Lens.Iso contains many wonderful functions for lifting Isos into various type arguments of useful abstractions. For example:

  • mapping for arbitrary Functors
  • contramapping for Contravariant functors
  • dimapping, lmapping and rmapping for Profunctors
  • bimapping for Bifunctors

I 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?

frasertweedale
  • 4,801
  • 3
  • 22
  • 36

1 Answers1

3

Update

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
dfeuer
  • 44,398
  • 3
  • 56
  • 155
  • 1
    Thanks for the breakdown; `bimapping p id` is nice. Sometimes with all the fancy type synonyms I overlook that optics are just functions :) – frasertweedale Dec 09 '15 at 00:35
  • @frasertweedale, you're welcome. I've written `seconding`, generalized the types of that and `firsting` to allow two different bifunctors (as in `bimapping`), and suggested my preferred implementations [on GitHib](https://github.com/ekmett/lens/pull/619). – dfeuer Dec 09 '15 at 01:40