scalalensesmonocle-scala

How to update Map using monocle


I'd like to try Monocle library. But i could not find help resources for base syntax.

In short i need optics Map[K,V] -> A having optics V -> A, how could i define this?

Suppose i have some

import monocle.macros.GenLens

case class DirState(opened: Boolean)

object DirState {
  val opened = GenLens[DirState](_.opened)
}

type Path = List[String]
type StateStore = Map[Path, DirState]

Next I encounter place where i need simple StateStore => StateStore, so I'm importing

import monocle._
import monocle.std._
import monocle.syntax._
import monocle.function._

And trying to define first:

def setOpened(path: Path): StateStore => StateStore = 
  at(path) composeLens DirState.opened set true

Getting here

ambiguous implicit values: both method atMap in trait MapInstances of type [K, V]=> monocle.function.At[Map[K,V],K,V] and method atSet in trait SetInstances of type [A]=> monocle.function.At[Set[A],A,Unit] match expected type monocle.function.At[S,Path,A]

Trying to change my definition to

def setOpened(path: Path): StateStore => StateStore =
  index(path) composeLens DirState.opened set true

Getting now:

type mismatch; found : monocle.function.Index[Map[Path,Nothing],Path,Nothing] (which expands to) monocle.function.Index[Map[List[String],Nothing],List[String],Nothing] required: monocle.function.Index[Map[Path,Nothing],Path,A] (which expands to) monocle.function.Index[Map[List[String],Nothing],List[String],A]

Note: Nothing <: A, but trait Index is invariant in type A. You may wish to define A as +A instead. (SLS 4.5)


Solution

  • import monocle.function.index._
    import monocle.std.map._
    import monocle.syntax._
    
    def setOpened(path: Path)(s: StateStore): StateStore =
      (s applyOptional index(path) composeLens DirState.opened).set(true)
    

    let's have a look at the type of index

    def index[S, I, A](i: I)(implicit ev: Index[S, I, A]): Optional[S, A] = 
      ev.index(i)
    
    trait Index[S, I, A] {
      def index(i: I): Optional[S, A]
    }
    

    So index summon an instance of type class Index of type Index[S, I, A]. This permits to use index for Map, List, Vector and so on.

    The problem is that scala compiler needs to infer 3 types: S, I and A at the call site of index. I is easy, it is the type of the parameter you pass to index. However,S and A are only know when you call set.

    The apply syntax has been created to guide type inference for such scenario, basically applyOptionalcaptures S which is Map[Path, DirState]. This gives enough information for the compiler to infer A =:= DirState.