Skip to the content.

Type of optics, part 3

So now, we have Getter and Setter. Let’s try putting them together.

In the part 1 of this series, we defined Getter in terms of Profunctor, but let’s use the version not being generalized to Profunctor.

type Getter s a = forall f. (Functor f, Contravariant f) => (a -> f a) -> (s -> f s)
type Setter s t a b = forall f. Identical f => (a -> f b) -> (s -> f t)

When you extract a common factor from these two types, you’ll get this.

type Lens1 s t a b = forall f. Functor f => (a -> f b) -> (s -> f t)

Next, we’re going to put to and setting together.

to :: (s -> a) -> Getter s a
to get = \afb -> \s ->
  let a = get s
      fb = afb a
   in () <$ fb $< ()

setting :: ((a -> b) -> (s -> t)) -> Setter s t a b
setting map = \afb -> \s ->
  let ab a = extract (afb a)
      st = map ab
      t = st s
   in pure t

The function we’re going to define takes s -> a and (a -> b) -> (s -> t) and returns Lens1 s t a b.

lens1 :: (s -> a) -> ((a -> b) -> (s -> t)) -> Lens1 s t a b
lens1 get map = \afb -> \s ->
  let a = get s
      fb = afb a
      ft = fmap (\b -> let ab _ = b
                           st = map ab
                           t = st s
                        in t) fb
   in ft

As you can see, we ignored the parameter when we converted a to b. So (a -> b) -> (s -> t) can be b -> s -> t.

lens1' :: (s -> a) -> (b -> s -> t) -> Lens1 s t a b
lens1' get set = \afb -> \s ->
  let a = get s
      fb = afb a
      ft = fmap (\b -> let st = set b
                           t = st s
                        in t) fb
   in ft

What do we do here? We get a value a from s, and pass it to afb. So if we use Const a b as afb, we can get this value a out of fb. This happens when we use this lens as a getter.

One the other hand, when we use Identity b, we can get s -> t from b in fb to get ft, from which we can extract t.

We’ll get an ordinal lens function by swapping b and s in b -> s -> t.

type Lens s t a b = forall f. Functor f => (a -> f b) -> (s -> f t)

lens :: (s -> a) -> (s -> b -> t) -> Lens s t a b
lens get set afb s = fmap (set s) (afb (get s))

Since Lens is more generic than Getter and Setter, you can pass it to any actions taking Getting or Setting such as view and over.

Then, what happens when we put Fold and Setter together?

type Fold s a = forall f. (Functor f, Contravariant f, Applicative f) => (a -> f a) -> (s -> f s)
type Setter s t a b = forall f. Identical f => (a -> f b) -> (s -> f t)

Again, let’s extract a common factor from these two types. You’ll get Traversal.

type Traversal s t a b = forall f. Applicative f => (a -> f b) -> (s -> f t)

Then, let’s try putting these functions together.

folding :: Foldable g => (s -> g a) -> Fold s a
folding fold = \afb -> \s ->
  let ga = fold s
      fb = traverse_ afb ga
   in fb $< ()

setting :: ((a -> b) -> (s -> t)) -> Setter s t a b
setting map = \afb -> \s ->
  let ab a = extract (afb a)
      st = map ab
      t = st s
   in pure t

folding takes s -> g a to convert s to Foldable g => g a. Then instead of passing this function, let’s assume that s itself is Foldable g => g a. Then, you’ll get this folding'.

folding' :: Foldable g => Fold (g a) a
folding' = \afb -> \ga ->
  let fb = traverse_ afb ga
   in fb $< ()

Unfortunately, we cannot unify it with setting with Foldable, and we need Traversable. By making it use Traversable instead of Foldable, you’ll get traversing. As you can see traversing is identical to traverse itself.

traversing :: Traversable g => Traversal (g a) (g b) a b
traversing = \afb -> \ga ->
  let fgb = traverse afb ga
   in fgb

traversing' :: Traversable g => Traversal (g a) (g b) a b
traversing' = traverse

You can do the same thing to setting. When you replace s with g a and t with g b where g is Traversable, you’ll get Traversable g => ((a -> b) -> (g a -> g b)) -> Setter (g a) (g b) a b. When you look at this type, you’ll find that (a -> b) -> (g a -> g b) is a type of fmap for g, and g is actually an instance of Functor (as it’s an instance of Traversable).

When you remove this map parameter from setting, you’ll get this settings'.

setting' :: Traversable g => Traversal (g a) (g b) a b
setting' = \afb -> \ga ->
  let fgb = traverse afb ga
   in fgb

Again, this is identical to traverse.

To put them together, we can say that any functor that is an instance of Traversable can be used as Fold and Setter, and you can pass it to actions taking Getting or Setting such as view, views and over.

Also, this gives you an intuition that 1) when you traverse a container with Const, you’ll gather all its items by concatenating them as Monoid, and 2) when you traverse a container with Identity, you can apply a function a -> b to all its items without modifying the structure itself.