Following is a continuation of the topic of modular implicits , introduced in the previous post on implicit functors. This time we’ll look at how the extension can help simplifying lenses. I covered lenses in OCaml lenses via modules, where a rather verbose definition of a (van Laarhoven) lens was given in the form of a module signature LENS :

module type LENS = sig type a type b module Mk : functor ( F : FUNCTOR ) -> sig val run : ( b -> b F . t ) -> a -> a F . t end end

With modular implicits, much of the clunkiness will go away. At first, let’s cover some ground and bring into scope a couple of utility functions - a module type for representing functors and two functor instances (for identity and constant):

let id x = x let const x _ = x module type TYPE = sig type t end module type FUNCTOR = sig type ' a t val map : ( ' a -> ' b ) -> ' a t -> ' b t end module IdFunctor = struct type ' a t = ' a let map f x = f x end module ConstFunctor ( T : TYPE ) = struct type ' a t = T . t let map _ x = x end

All of the definitions are vanilla OCaml and were also described in the previous lens post.

The reason the lens representation required a module signature rather than a simple type is due to OCaml’s inability to parameterize over higher-kinded types. Powered by modular implicits however, there is a work around; We are now able to define lens as a function type:

type ( ' a , ' b ) lens = { F : FUNCTOR } -> ( ' b -> ' b F . t ) -> ' a -> ' a F . t

Note that the implicit argument F is available in scope for other arguments as well as the return type of the function. This is not achievable with normal first class modules in OCaml. More specifically, the following type construction is invalid:

(* Does not compile :( *) type ( ' a , ' b ) lens = ( F : FUNCTOR ) -> ( ' b -> ' b F . t ) -> ' a -> ' a F . t

Comparing with the previous version, the view and modify functions used for extracting and updating values respectively are also simplified:

let view ( type a ) ( type b ) ( l : ( a , b ) lens ) ( x : a ) : b = let module C = ConstFunctor ( struct type t = b end ) in l { C } id x let modify ( type a ) ( type b ) ( l : ( a , b ) lens ) ( f : b -> b ) ( x : a ) : a = l { IdFunctor } f x

Viewing a lens is accomplished by using the ConstFunctor , instantiated with the concrete type parameter b in order to smuggle out the value that the lens is pointing to. The function modify instead relies on IdFunctor for updating the value.

A utility, set , is introduced for convenience:

let set l x = modify l ( const x )

Since lenses are functions, lens composition is nothing but function composition:

let compose ( l2 : ( ' b , ' c ) lens ) ( l1 : ( ' a , ' b ) lens ) : ( ' a , ' c ) lens = fun { F : FUNCTOR } f x -> l1 { F } ( l2 { F } f ) x let ( // ) l1 l2 = compose l2 l1

To see how the pieces fit together, let’s take look at some concrete examples. Consider the following custom types:

type address = { street : string ; number : int } type person = { name : string ; age : int ; address : address } type compnay = { name : string ; ceo : person }

We first introduce lenses for some of the properties manually:

let ceo { F : FUNCTOR } f x = F . map ( fun ceo -> { x with ceo }) @@ f x . ceo let address { F : FUNCTOR } f x = F . map ( fun address -> { x with address }) @@ f x . address let street { F : FUNCTOR } f x = F . map ( fun street -> { x with street }) @@ f x . street

Although an improvement over the module based approach, lenses for record properties still require boiler plate code and should rather be automated by a deriving mechanism.

Now, given a value of type company :

let my_company = { name = "Lens Inc" ; ceo = { name = "Mary" ; age = 62 ; address = { street = "Highstreet" ; number = 13 ; } } }

Using the lenses from above, here is how to update the street component of my_company using a composed lens and the set function:

set ( ceo // address // street ) "Wallstreet" my_company ;;

This code results in the following value:

{ name = "Lens Inc" ; ceo = { name = "Mary" ; age = 62 ; address = { street = "Wallstreet" ; number = 13 } } }

As you may have observed, there is not a single implicit module in the code above - the value proposition of modular implicit goes beyond sparing an extra argument to a function.

Complete code here.