Using `OverloadedRecordDot` with "optics"

I like the “optics” library. I like OverloadedRecordDot. I would like to use dot-access syntax for nested record updates. If I have an optic foo that focuses on type R, and type R has a field named bar, foo.bar (dot access) would be a new optic that focuses on the field bar. How to achieve this?

We can define an (admittedly weird) HasField orphan instance on the Optic type itself. Note that this is only possible because Optic, unlike the lenses from “lens”, is a true abstract type, not simply a type synonym for polymorphic functions:

instance
    (GField (name :: Symbol) u v a b,
    JoinKinds k A_Lens m,
    AppendIndices is NoIx ks)
    =>
    HasField name (Optic k is s t u v) (Optic m ks s t a b) where
        getField = (% gfield @name)

That needs some explaining. We have sets of four type variables s t u v, u v a b, s t a b instead of pairs of type variables, to account for type-changing updates. Ignoring the type-changing complexity, the idea is that if we have a lens that focuses on u, and type u has a field named name of type a whose lens obtainable through the GField generics machinery, the dot accesor can just compose the original lens with the field lens.

What about JoinKinds and AppendIndices? The “optics” library keeps track of an optics’ properties in the k is type parameters and, when composing optics, these type families combine the properties of each component. When accessing a field, the optic kind will be A_Lens, and we keep no extra index (NoIx) information.

But how to get the initial lens to start accessing? We can begin with the “identity” optic, which in “optics” is an Iso called equality. Let’s rename it to the:

the :: Iso a b a b
the = equality

Now we can have syntax like whole & the.part.subpart.yet.ooo .~ "newval".

There’s room for improvement though. One problem is that we are wedded to the GField machinery. But we would like the dot access to work with other ways of defining lenses, like TH, HasField/SetField, and maybe others. Different records could use different ways.

We can achieve that by defining a separate helper typeclass parameterized by the “method” we want to use for deriving the optics. I’ve implemented that in the dani-optics-dot repo.

9 Likes

Very cool, thanks for prototyping on this. :slight_smile: