Overview
I’m happy to announce a new library, generic-optics, accompanied by version 2.0.0.0 of generic-lens.
Background
A few months ago, the folks at Well-Typed announced the optics
library,
which aims to improve on the user experience compared to the lens
library.
Oleg Grenrus has written an excellent migration guide
from lens
to optics
, so please have a look there for some more background.
generic-optics
is essentially a port of generic-lens
that is
compatible with optics
, and is designed to be a drop-in replacement
for generic-lens
. This means that if you’re already using generic-lens
with lens
and decide to migrate to optics
, you should be able to replace the
generic-lens
dependency with generic-optics
and expect things to just work.
Examples
To explain why I’m so excited about optics
, I’m going to
compare a real-life workflow between generic-lens
and generic-optics
.
First, language pragmas and imports:
Note that the module Data.Generics.Product
is shared between
generic-lens
and generic-optics
.
When using generic-lens
with the lens
library, we would import
When using generic-optics
with optics
, the import becomes
Now we define a simple record:
With either library, we can view the a
field using
the field
lens:
If we ask what the type of field @"a"
is in GHCi, we already see
the advantage of optics
’s opaque representation.
Compare
with
Now let us use the typed
lens, which performs a type-directed lookup in
a product type, as long as there is a unique field with that type:
When the type of the field is not unique (such as if we tried to retrieve a field
of type Int
), both generic-optics
and generic-lens
provides a helpful type error:
For situation likes this, both libraries provide a traversal called
types
that focuses on all values of the given type.
Let’s see what happens if we replace typed
with types
in the above
example when using lens
:
This error is rather puzzling. Unless we know what’s going on under
the hood, it’s not obvious where the Monoid
constraint is coming from.
Compare this with generic-optics
:
Right! types @Int
is a traversal, but ^.
takes a getter!
Arguably this is a more helpful message. Consulting the documentation
of optics
, we find the combinator we’re looking for: ^..
, which returns
all the values focused on by a traversal:
This now of course works in both libraries.
To summarise, using the two libraries should be nearly identical as
long as everything goes well and we’re not hitting type errors.
Where generic-optics
(but really, optics
itself) shines is when things
do not go all that well, in which case the resulting error messages are a lot more
comprehensible.
Differences
The above was just to give a little taste of using
generic-optics
. The interface of generic-optics
is
intended to be largely identical to that of generic-lens
.
Labels
At the time of writing, the main difference is the support for overloaded
labels in generic-lens
, which allows writing
I intend to add support for this for generic-optics
too, but it
isn’t implemented yet.
Changes in generic-lens
To support this new interface, generic-lens
itself has undergone a major
reorganisation. I thought this was a good opportunity to clean some things
up and change the interface at places, which ultimately resulted in a new major
version bump.
Most notably, GHC versions below 8.4 are no longer
supported. generic-lens
(and generic-optics
too) promises good
performance by making sure that the generic overhead is eliminated at
compile time. Doing so requires really careful coding practices, and
GHC’s optimiser changes between every version, which meant that
certain tricks that worked for 8.2 didn’t work for 8.6 and vice
versa. The result was horrible CPP macros to enable certain hacks on
certain versions of GHC. In the end, I decided it wasn’t worth the
effort to maintain these hacks for older versions of the compiler.
I intend to write a blog post in the near future describing some of these hacks, as they are quite interesting and potentially educational.
For a more comprehensive list of changes, refer to the changelog.
Conclusion
Thanks for reading this blog post, and I’m hope you’re as excited
about generic-optics
as I am! Since this release required a major refactoring
and moving things around, it is possible that some documentation is out of date, or
certain functions are not exported from where you would expect. If you find anything
that looks off, please either open a pull request or let me know on the issue tracker!
Finally, if you find generic-lens
or generic-optics
useful,
consider buying me a coffee!