1

I've seen the official example of updating a Map but I'm having trouble with the syntax.

val pod: Lens[Event, Pod] = GenLens[Event](_.`object`)
val metadata: Lens[Pod, Metadata] = GenLens[Pod](_.metadata)
val labels: Lens[Metadata, Map[String, String]] = GenLens[Metadata](_.labels)

I want to update a key "app" in the labels Map. But I can't get the following to compile:

(labels.composeOptional(index("app"))).set("whatever")(someLabels)

In fact, this answer by one of the authors of Monacle doesn't compile.

Abhijit Sarkar
  • 21,927
  • 20
  • 110
  • 219

2 Answers2

3

Without having the definition of your Event class, I do not have an exact answer, but following the tutorial and the University example, I am able to update a nested Map with latest version as of this writing, monocle 1.5.0-cats-M1. Be sure to have both the monocle-core and the monocle-macros jars in your project. Then,

import monocle.macros.GenLens
import monocle.function.At.at // // to get at Lens 
import monocle.std.map._      // to get Map instance for At

Then, following the university example,

case class Lecturer(firstName: String, lastName: String, salary: Int)
case class Department(budget: Int, lecturers: List[Lecturer])
case class University(name: String, departments: Map[String, Department])

val departments = GenLens[University](_.departments) 

val uni = University("oxford", Map(
"Computer Science" -> Department(45, List(
  Lecturer("john"  , "doe", 10),
  Lecturer("robert", "johnson", 16)
)),
"History" -> Department(30, List(
  Lecturer("arnold", "stones", 20)
)))) 

I am able to

(departments composeLens at("History")).set(Some(Department(30, List(Lecturer("arnold", "stones", 30)))))(uni)

The major differences from your code above are the use of at() and wrapping of the Department with Some to correspond with an Option return type when accessing using a key to retrieve value from a Map.

Alan Effrig
  • 763
  • 4
  • 10
  • I went with quicklens since my usage was pretty minimal, and wasn't worth wading through these problems. Monocle also brings in lot of dependencies (Scalaz, Shapeless), that again, is overkill for my purposes. I'll play with it later, and accept your answer if it works for me. Until then, please consider an upvote as a token of appreciation. – Abhijit Sarkar Dec 14 '17 at 07:59
  • 2
    @AbhijitSarkar only monocle-generic module depends on shapeless which I don't think you're using it. Scalaz or cats is the only dependency in core and it is kind of required if you want to do fp in scala. – Julien Truffaut Dec 14 '17 at 08:24
0

Considering that someLabels is of type Map[String, String], your code is either excessive or just supplies wrong argument to composed Optional. If we simplify signature of composeOptional method in Lens[S, A], it yields:

def composeOptional(other: Optional[A, B]): Optional[S, B]

Optional[A, B], at the very imprecise approximation, corresponds to indirection that allows to:

  • look into value of type A and get its component of type B (or A itself if it's missing);
  • build a new object of type A by replacing its component of type B (or just return original object if there's no such component).

labels composeOptional index("app") yields Optional[Metadata, String]. That obviously won't work on Map[String, String]: it indirects from Metadata to Map[String, String] (via labels) and then immediately from Map[String, String] to its String element (via index("app")), hiding map access from the user entirely. If you're trying to just set a value at a given key in someLabels map, it suffices to use index:

val someLabels1 = Map("app" -> "any")
val someLabels2 = Map("unit" -> "any")
index("app").set("whatever")(someLabels1) // Map("app" -> "whatever")
index("app").set("whatever")(someLabels2) // Map("unit" -> "any")

Your composed Optional, on the other hand, works over Metadata:

case class Metadata(labels: Map[String, String])
val someLabels = Map("app" -> "any")
val meta = Metadata(someLabels)
(labels composeOptional index("app")).set("whatever")(meta) 
// Metadata(Map("app" -> "whatever")

I've checked it with following versions (in build.sbt):

scalaVersion := 2.12.3

libraryDependencies ++= Seq(
  "com.github.julien-truffaut" %% "monocle-core" % "1.4.0",
  "com.github.julien-truffaut" %% "monocle-macro" % "1.4.0"
)
P. Frolov
  • 876
  • 6
  • 15