The following macros are used to cut down boilerplate when generating lens.
Thanks to @milessabin for refering the macros to us.
Thanks for looking into this! Another macro I wanted to implement but never got the time is GenLens equivalent for common fields of Coproduct e.g.
sealed trait Foo
case class Foo1(name: String, i: Int) extends Foo
case class Foo2(name: String, b: Boolean) extends Foo
val name = GenLens[Foo](_.name)
but it would fail if they were a 3 implementation of Foo like:
case class Foo3(x: Int) extends Foo
I'm pretty sure the answer is "yes", but can you confirm for me that this generalizes to nested selectors, ie. _.address.street?
good question, it would be awesome if it was.
It's pattern matching on the Tasty trees, so I _think_ it could be generalized and Just Work, but it'd be nice to be sure.
@julien-truffaut There is some difficulty to implement the Coproduct lens in Dotty macros:
Dotty macros should type check without expansion.
Even for the non-Coproduct GenLens macro, programmers have to write GenLens[Foo, String](_.name) in Dotty instead of GenLens[Foo](_.name) in Scala2.
Nested selectors _.address.street should be able to be supported.
Could you get it to typecheck by having the target type inferred as Nothing in the unexpanded context?
@milessabin The problem is that given the following macro signature:
object GenLens {
inline def apply[S, T](get: S => T): Lens[S, T] = ~impl('(get))
}
Dotty does not support only supplying S:
5 | val len = GenLens[Address](_.streetNumber)
| ^^^^^^^^^^^^^^^^^
| Not enough type arguments for GenLens.apply[S, T]
| expected: [S, T]
| actual: [Address]
@nicolasstucki Any thoughts about this?
In vanilla Scala the usual trick is something like,
object GenLens {
def apply[S] = new MkGenLens[S]
class MkGenLens[S] {
def apply[T](get: S => T): Lens[S, T] = ...
}
}
Then S can be explicit and T inferred. Could you do something like that here?
@milessabin Thanks a lot for sharing the trick, it works like a charm 👍 73524a7c1ae1367114b8188ecf08fbcb07313790
I thought Dotty was supposed to solve those issues
On Mon, Feb 18, 2019, 4:15 PM Fengyun Liu notifications@github.com wrote:
@milessabin https://github.com/milessabin Thanks a lot for sharing the
trick, it works like a charm 👍 73524a7
https://github.com/lampepfl/dotty/commit/73524a7c1ae1367114b8188ecf08fbcb07313790—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/lampepfl/dotty/issues/5941#issuecomment-464881673,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAGAUJm3K3LFbyhjTf6kcNGbY5pwlETaks5vOxftgaJpZM4bAlx5
.
Dotty does not support only supplying S:
You can write:
val len = GenLens[S=Address](_.streetNumber)
cf http://dotty.epfl.ch/docs/reference/other-new-features/named-typeargs.html
You can write
Yeah, but that's really quite awkward in this sort of context (which is really quite common). It's a shame ... we ought to be able to do better.
This works with poly functions,
trait Lens[S, T]
def GenLens[S] = [T] -> (get: S => T) => new Lens[S, T] {}
case class Foo(i: Int)
object Test {
GenLens[Foo](_.i)
}
But I think it would be nicer if we could support multiple type parameter lists, ie.,
def GenLens[S][T](get: S => T) = new Lens[S, T] {}
Make a SIP :).
Make a SIP :)
With a PR attached ;-)
The GenPrism and GenLens macros are implemented in #5944.
Unfortunately, we are unable to implement GenIso.fields due to missing support for whitebox macros.
Code examples:
val len2 = GenLens[Employee](_.addr.streetNumber)
val employee = Employee("Bob", Address(10, "High Street"))
assert(len2.get(employee) == 10)
val employee2 = len2.set(5, employee)
assert(employee2.name == "Bob")
assert(len2.get(employee2) == 5)
val jNum: Prism[Json, Double] = GenPrism[Json, JNum] composeIso GenIso[JNum, Double]
assert(jNum(3.5) == JNum(3.5))
assert(jNum.getOption(JNum(3.5)) == Some(3.5))
assert(jNum.getOption(JNull) == None)
The implementation of GenIso[JNum, Double] is supposed to be more robust than the Scala2 implementation, as it handles the case where the case classes are inner classes.
@liufengyun the work I'm doing on generics will cover GenIso.
It would be nice to implement GenIso.fields based on @milessabin 's work. Could you please let us know how to do that once your work is merged @milessabin ?
I started the implementation of Monocle for Dotty in the following repo: https://github.com/julien-truffaut/Monocly
It would be amazing if any of you would like to participate or point me to some documentation. I tried to adapt the macro in https://github.com/lampepfl/dotty/blob/master/tests/run-macros/i5941/macro_1.scala but I am running into some issues. It is probably because I am using polymorphic optics (4 or 5 type parameters) and a curried set method.
@julien-truffaut The following is the latest prototype implementation in Dotty https://github.com/lampepfl/dotty/blob/master/tests/run-macros/i5941/macro_1.scala . The documentation and the paper may also be useful.
In your prototype, I think you should remove the toolbox line, as they are not intended for macros.
def impl[A: Type, B: Type](getter: Expr[A => B])(given qctx: QuoteContext): Expr[Lens[A, B]] = {
implicit val toolbox: scala.quoted.staging.Toolbox = scala.quoted.staging.Toolbox.make(this.getClass.getClassLoader)
import qctx.tasty.{_, given}
Most helpful comment
Yeah, but that's really quite awkward in this sort of context (which is really quite common). It's a shame ... we ought to be able to do better.
This works with poly functions,
But I think it would be nicer if we could support multiple type parameter lists, ie.,