Monocle 3 Roadmap
Monocle is quite an old Scala library. It was initially conceived in 2014 as a port of the Haskell Lens library. Over the years, we've adapted the optics encoding to better accommodate the JVM runtime and the unique blend of OOP and FP present in Scala. In turn, key aspects of Monocle’s design have been adapted for other programming languages such as Kotlin and Java.
Although Monocle is widely used in the Scala ecosystem, its API is complicated and quite verbose. In our next major version, Monocle 3, we intend to focus on user experience, especially for beginners. For this version, we have set four primary objectives, each of which will be expanded on below:
- Simpler API
- More pragmatic
- Smaller footprint
- Clearer documentation
We plan to release the first milestone for Monocle 3 as soon as possible to gather feedback from existing users. Then, we'll set a few more milestones to incorporate feedback and include more complicated features such as macros.
Our tentative timeline for Monocle 3:
- M1 (January 2021): early preview
- (March 2021): add macros and incorporate feedback from M1
- M3/RC1 (June 2021): Documentation and migration guide (preferably with scalafix)
How can I help?
You don't need to be an expert in optics to help us. You can contribute by doing any of the following:
- Try the new version and give us feedback on Gitter or GitHub.
- Share this article on social media or at your place of work.
- Report bugs or pain points with the API.
- Comment on the proposals in the issue tracker.
- Send us pull requests (let us know beforehand what you are working on).
Objectives for Monocle 3
1. Simpler API
The Monocle 2 API is complex and often requires in-depth knowledge of optics’ internals. In Monocle 3, we want to remove as much complexity as possible to improve user experience, particularly for new users.
1.1 Optics composition (Issue #964)
Planned for M1
In Monocle 2, each optics have several compose functions such as
composeOptional (see full table). This causes two problems:
- Verbose optics composition,
address composeOptional index("home") composeLens postcode. Here, more than half of the code consists of composeX methods.
- Users need to know that
Additionally, optics composition works very much like function composition; if you have an
Optic[A, B] and
Optic[B, C], you can compose them to get an
Optic[A, C]. However, in the example below, you will notice that the order of type parameters in
Optic#compose is more similar to the
andThen method of
Function1 than to
Therefore, in Monocle 3, we will deprecate all
composeX methods in favor of a new overloaded
andThen method. This change may cause regression in the type inference when composing:
- Optics with type parameters, e.g.
- Optics coming from typeclasses such as
each. However, most of these issues should be fixed by "shortcuts for popular optics" (see below) and Scala 3 which has a much better type inference algorithm.
1.2 Shortcuts for popular optics #978,
Planned for M1
Some optics are extremely common, yet it can be challenging to find and figure out which is suitable. For example, common questions from users are:
"How can I zoom into an Option?" or "How can I access an element at a particular key within a Map? What if the key doesn't exist?"
This is partly a documentation problem, but it is also due to the nature of optics composition. You can compose almost any pair of optics together as long as the output type of the first matches the input type of the second. This freedom makes it difficult to decide which to use. At least, this was the case in Monocle 2. In Monocle 3, we will add methods on all optics for all common cases of optics compositions. For example,
This change has multiple consequences:
- Users don’t need to add or learn about special imports.
- Users can use IDE completion to find out which optics are available.
- Side step type inference issue with overloaded
andThen(see example in Scala 3)
1.3 Use replace instead of set (issue #961)
Planned for M1
One of the most frequent questions in the Monocle Gitter is:
set insert a value when the input is None? "
In the example below, many users expect the result to be
While this is a linguistic issue, not a bug:
set implies it can insert a value when there is none, which is not possible. Therefore, we have decided to deprecate
set in favor of
replace. Hopefully, the latter indicates we are updating an existing value rather than inserting one.
1.4 Focus macro
Planned for M2
Monocle offers several macros to automate optics creation:
GenLensto focus into a field of a case class.
GenPrismto focus into a branch of an enumeration (
GenIsofor new types (e.g.
case class Id(value: Long)) or to see a case class as a tuple of fields.
This approach presents a few issues:
- Users need to know which macro to use for their use case (barrier to entry)
- The code is still verbose when you need to mix different types of optics, e.g.
Instead, Monocle 3 will propose a single macro (to rule them all) and offer an API similar to XPath/JsonPath.
No need to learn about and differentiate
Iso anymore. The macro will generate the right type for the path.
We are considering symbolic operators such as
? to zoom into an
* to access all elements in a collection. This would provide a similar API to JsonPath, which users may be familiar with. Though we are also wary of using symbols as they cause difficulties while searching for them.
We also plan to integrate
ApplyOptics to facilitate a more object-oriented syntax:
This syntax is particularly convenient for single-use optics as you don't need to give the optic name.
Focus macro will only be available in Monocle 3 for Scala 3. If you are using Scala 2, we will maintain the old macros for a few release cycles. It might be possible to port
Focus to Scala 2, but it requires too much effort. This is also some good motivation to migrate to Scala 3 ;)
2. More pragmatic
Planned for M1
Monocle defines properties (aka laws) for each optics. These properties are essentially a test suite that verifies optics behave as expected. For example, a
Lens focuses on a single piece of data inside a case class; hence, the test suite verifies that the optic doesn't modify other parts of the object.
Until now, we have refused to include any methods in the
core module that could break those properties, even if the chances were slim and the use case extremely convenient. In Monocle 3, we plan to take a more pragmatic approach and allow those combinators if we judge they are useful enough. We will also document if and when they might cause surprising behaviors.
You can find below a few examples of the new combinators.
To provide a default value in case an
Option is empty.
To select a target based on its value.
3. Smaller footprint
Planned for M1
Remove rarely-used features and simplify the build to reduce maintainer workload.
- Drop Scala 2.12.
atwith a singleton instead of dedicated methods, e.g.,
at(2), and so on.
- Deprecate symbolic operators
_2, and so on.
- Deprecate the generic module (shapeless dependency).
- Deprecate the state module. If you are a heavy user of the state module, we encourage you to create a separate library.
4. Clearer documentation
4.1 Scalafix migration
Planned for M3
Automate the migration from Monocle 2 to 3 using Scalafix. See this list of API changes #1001.
4.2 New website
Planned for M3
The current microsite is too focused on optics internals. We need to rewrite the documentation from scratch with a particular focus on the "Getting Started" section to better onboard new users.
4.3 Online course
FP-Tower is considering a short (3-5 hours) online course around Monocle API and optics fundamentals. If you would be interested in this type of resource, please let us know.
We hope that the Monocle 3 API will be simple enough for Scala developers to reach for whenever they need to access or transform immutable data. We believe that the
Focus macro and other API simplifications will help us achieve that goal.
However, we need your help in testing both the documentation and each Monocle 3 milestone to ensure we don’t miss our targets.
Have a great day everyone, happy coding.
You can discuss this article on reddit.