softwaremill/quicklens

Composing updates on different paths of an object

benhutchison opened this issue · 5 comments

I am experimenting with porting a closed source codebase from monocle to quicklens.

One issue I have hit 3 times thus far is the need to combine multiple updates to the same object, that occur along different paths. I have boiled it down to this simple example which illustrates (a) the problem, and (b) my current not-very-elegant attempt.

Currently doesn't work: Path must have shape: _.field1.field2.each.field3.(...), got: path1

  case class Piece()
  case class ChessBoard(inPlay: Set[Piece], captured: Set[Piece]) {

    def capture(piece: Piece): Unit = {
      require(inPlay.contains(piece))
      require(!captured.contains(piece))

      modify2(this)(_.inPlay, _ - piece)(_.captured, _ + piece)
    }
  }

  def modify2[T, U, V](obj: T)(path1: T => U, update1: U=>U)(path2: T => V, update2: V=>V): T = {
    import com.softwaremill.quicklens._

    val temp = modify(obj)(path1).using(update1)
    modify(temp)(path2).using(update2)
  }

Here's another syntax option using implicit wrappers, but alas it suffers from the same "loss of path shape" problem:

  case class Piece()
  case class ChessBoard(inPlay: Set[Piece], captured: Set[Piece]) {

    def capture(piece: Piece): Unit = {
      require(inPlay.contains(piece))
      require(!captured.contains(piece))

      modify(this)(_.inPlay).using(_ - piece).andModify(_.captured).using(_ + piece)
    }
  }

  implicit class LensWrapper[A](a: A) {
    def andModify[T](path: A => T) = com.softwaremill.quicklens.modify(a)(path)
  }
adamw commented

How about new syntax for modify using an implicit class?

this // or any object
  .modify(_.inPlay).using(_ - piece)
  .modify(_.captured).using(_ + piece)
  .modify(...).using(...) // etc.
adamw commented

(this requires some changes to the macro, but I have a working version locally :) )

I like that syntax, keen to try the new version

adamw commented

Take a look at version 1.4.0 + example in the readme :)