NOTE: This article has been superceded by later changes to the Aether library in late 2015. See the Aether site for up to date information.
NOTE: This article was updated in June 2015 to indicate the use of the final syntax in Aether. Additionally, it is worth noting the changes around isomorphisms mentioned in the later update.
A couple of days ago I released Aether — introduced here — a little library for using Lenses in F#. FSharpx already includes a lens implementation (and it’s well written about by Mauricio Scheffer here), so “why bother?” is a reasonable question. Firstly, for libraries which wish to provide lenses over their own types, Aether does not require you to take a dependency on Aether — you can implement the types required natively. Secondly, Aether makes a distinction explicit between total and partial lenses.
First — Lenses
Lenses are a useful tool for working with functional data structures. They allow us to get, set, and modify properties of immutable entities (in reality, return a new entity with new properties) deep within some nested structure of immutable types. For example, take the following very simple example of two record types, one nested within the other.
type Inner = { Value: string }
type Outer = { Inner: Inner }
To get the value of Value
, if we have an instance of Outer
is simple, but to set it we need to modify two records, like so:
let set v outer = { outer with Inner = { outer.Inner with Value = v } }
That’s a bit awkward already, and we’re only two levels of nesting in! If we get to a situation where we’ve got several complex nested types, perhaps where some fields are Maps or Lists of other complex types, or Options of types, the logic just to set or modify a value deep in the structure becomes unwieldy and error-prone.
Lenses can solve this problem neatly. A lens in essence is simply a pair of functions, a getter and a setter. The first, given some entity, retrieves a value from that object and the second, given some entity and a value, returns a new entity with the value set. A lens as defined in this way is essentially ('a -> 'b) * ('b -> 'a -> 'a)
. A lens for the Value
field of the Inner
type from earlier would look like this:
let valueLens = (fun x -> x.Value), (fun v x -> { x with Value = v })
It’s a Lens<Inner,string>
— a lens from Inner
to string
. In isolation this might not look very useful but as they’re just functions, we can compose lenses together. If we have a lens from Outer
to Inner
and a lens from Inner
to string
we can compose them — and end up with a lens of Outer
to string
.
Now we can simply use a lens function (Lens.set
in this case) to set Value
like so (assuming we have a lens Outer
to Inner
named innerLens
):
(* (>-->) is an operator which composes two lenses *)
let composedLens = innerLens >--> valueLens
(* or with partial application, let setValue = Lens.set composedLens *)
let setValue v outer = Lens.set composedLens v outer
If we provide lenses for commonly used aspects of our types, they can be composed in all kinds of new ways and we can save ourselves a lot of awkward/tedious/error-prone “longhand” manipulation of immutable values.
Partial Lenses
The lenses we’ve seen so far have always been “reliable”. We know that the getter function for Inner
is going to return an instance of Inner
because the type system won’t let us - it’s got to be there for us to have constructed an instance of Outer
! But what about cases where a getter function might not be able to return something? Maybe the value is an option
type. Maybe we’re using a lens which refers to an item within a map at a certain key — and that key doesn’t exist.
In this case, we need a partial lens. The lenses so far have been total, with the signature ('a -> 'b) * ('b -> 'a -> 'a)
but for a partial lens, we need to change the first function there to be ('a -> 'b option)
. These partial lenses can still be composed — and composed with total lenses. To do this, we introduce three more composition operators in addition to >-->
we saw earlier.
(* composition operators *)
(>-->) // Total -> Total -> Total
(>?->) // Partial -> Total -> Partial
(>-?>) // Total -> Partial -> Partial
(>??>) // Partial -> Partial -> Partial
All except the (>-->)
operator return a new partial lens. Let’s change the earlier example slightly and see what this looks like:
type Inner = { Value: string }
type Outer = { Inner: Inner option }
Inner
is now optional in our Outer
type, so our new lenses look like this:
let innerLens = (fun x -> x.Inner), (fun i x -> { x with Inner = Some i })
let valueLens = (fun x -> x.Value), (fun v x -> { x with Value = v })
Our first lens now returns an option
type for the getter — but note that according to our signature the setter still takes a non-option
type, so we set the value to Some i
. Now our composed lens looks like this:
(* we use the partial -> total composition operator now *)
let composedLens = innerLens >?-> valueLens
(* returns a string option *)
let getValue outer = Lens.getPartial composedLens outer
(* still takes a string, but it'll only be set if Inner is not None *)
let setValue v outer = Lens.setPartial composedLens v outer
We can see that we’re using a couple of new functions here as well — Lens.getPartial
and Lens.setPartial
. They’re simply the equivalents of Lens.set
and Lens.get
for partial lenses, and in general this is a convention that the Aether library sticks to. You will be unsurprised to learn that these functions are joined by Lens.map
and Lens.mapPartial
too, behaving much as you would expect.
So, that’s a very brief intro to Aether, the next thing to do is to have a glance at the code if you’re interested in using it. It’s small and not very complicated and the useful functions are all quite apparent (hopefully). Head over to here and take a look, and feel free to get in touch/raise issues/send pull requests if you feel like it!