Skip to content

@Lenses generated summonable companion objects for less verbose syntax #327

@GiGurra

Description

@GiGurra

Hopefully my suggestion here is not too stupid. (This is my first day using more advanced scala like scalaz, monocle & some shapeless:)) - Also note: I have never written any macros for scala, so I don't know its limitations

Some background
Monocle is great - really makes creating permutations of immutable case class objects much easier. However the process is still rather verbose - Even with the @lenses macro. Compare for example the shapeless lens syntax (val myLens = lens[MyType].myfield.mynestedfield) - however IntelliJ really doesn't work well at all with Shapeless, so I end up using your Monocle lenses anyway.

The problem
For just updating nested case classes, the monocle lens composition syntax is a little verbose - and could be improved, see below for (possibly dumb :) ) suggestion on how this could work with the @lenses macro

Desired auto generated lens composition syntax (with proof of concept implementation below)

  1. A syntax similar in length to shapeless lenses, e.g. val myLens = MyType.myfield.mynestedfield
  2. A direct way to set fields, e.g. myInstance.set(myfield..mynestedfield, newValue)

Possible solution
I made some experiments with Monocle and concluded it is possible to implement syntax similar to (1) and (2) if generated type classes existed that made it easy to summon Companion objects (using the method from the original post by Miles Sabin).

Here is a manual example:

  @Lenses case class Nested(foo : String)
  @Lenses case class Address2(street : String, city : String, postcode : String, e: Nested)
  @Lenses case class Person2(name : String, age : Int, address : Address2)

  // If these could be generated with macros..
  object Address2 {
    implicit def aComp = new Companion[Address2] {
      type C = Address2.type
      def apply() = Address2
    }
  }
  object Person2 {
    implicit def pComp = new Companion[Person2] {
      type C = Person2.type
      def apply() = Person2
    }
  }
  object Nested {
    implicit def pComp = new Companion[Nested] {
      type C = Nested.type
      def apply() = Nested
    }
  }

If the above type class instances were generated by for example the @lenses macro, it is possible to implement something like the below (or better) without too much trouble (See links below for full POC source).

  val person = Person2("Joe Grey", 37, Address2("Southover Street", "Brighton", "BN2 9UA", Nested("123")))

  // Then we could do this!
  val fooLens = Person2.address(_.e(_.foo))
  fooLens.set("lalala")(person) shouldBe Person2("Joe Grey", 37, Address2("Southover Street", "Brighton", "BN2 9UA", Nested("lalala")))

  // --> Or even this! <--
  val p1 = person.set(_.name, "123")
  val p2 = person.set(_.address(_.city), "dumbletown")
  val p3 = person.set(_.address(_.e(_.foo)), "eeee")

  p1 shouldBe Person2("123", 37, Address2("Southover Street", "Brighton", "BN2 9UA", Nested("123")))
  p2 shouldBe Person2("Joe Grey", 37, Address2("Southover Street", "dumbletown", "BN2 9UA", Nested("123")))
  p3 shouldBe Person2("Joe Grey", 37, Address2("Southover Street", "Brighton", "BN2 9UA", Nested("eeee")))

Question to you

Could we have a discussion on whether we could have something like a LensesCompanion type class with auto generated instances for all @lenses annotated case classes?

For the full POC code with tests that the above is taken from, see The last test here and/or The full POC implementation

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions