0

I am probably thinking about this the wrong way, but I am having trouble in Scala to use lenses on classes extending something with a constructor.

class A(c: Config) extends B(c) {
    val x: String = doSomeProcessing(c, y) // y comes from B
}

I am trying to create a Lens to mutate this class, but am having trouble doing so. Here is what I would like to be able to do:

val l = Lens(
    get = (_: A).x,
    set = (c: A, xx: String) => c.copy(x = xx) // doesn't work because not a case class
)

I think it all boils down to finding a good way to mutate this class.

What are my options to achieve something like that? I was thinking about this in 2 ways:

  • Move the initialization logic into a companion A object into a def apply(c: Config), and change the A class to be a case class that gets created from the companion object. Unfortunately I can't extend from B(c) in my object because I only have access to c in its apply method.
  • Make x a var. Then in the Lens.set just A.clone then set the value of x then return the cloned instance. This would probably work but seems pretty ugly, not to mention changing this to a var might raise a few eyebrows.
  • Use some reflection magic to do the copy. Not really a fan of this approach if I can avoid it.

What do you think? Am I thinking about this really the wrong way, or is there an easy solution to this problem?

Charles Menguy
  • 40,830
  • 17
  • 95
  • 117

1 Answers1

1

This depends on what you expect your Lens to do. A Lens laws specify that the setter should replace the value that the getter would get, while keeping everything else unchanged. It is unclear what is meant by everything else here.

Do you wish to have the constructor for B called when setting? Do you which the doSomeProcessing method called?

If all your methods are purely functional, then you may consider that the class A has two "fields", c: Config and x: String, so you might as well replace it with a case class with those fields. However, this will cause a problem while trying to implement the constructor with only c as parameter.

What I would consider is doing the following:

class A(val c: Config) extends B(c) {
  val x = doSomeProcessing(c, y)
  def copy(newX: String) = new A(c) { override val x = newX }
}

The Lens you wrote is now perfectly valid (except for the named parameter in the copy method).

Be careful if you have other properties in A which depend on x, this might create an instance with unexpected values for these.

If you do not wish c to be a property of class A, then you won't be able to clone it, or to rebuild an instance without giving a Config to your builder, which Lenses builder cannot have, so it seems your goal would be unachievable.

Cyrille Corpet
  • 5,265
  • 1
  • 14
  • 31