-
-
Notifications
You must be signed in to change notification settings - Fork 205
Description
As discussed on Twitter, picking up this old PR again: #208
Given this dummy data
case class Inner( i: Int, d: Double )
case class Outer( inner: Inner, s: String )
val o = Outer( Inner(5), "foo" )it sounded like you are open to something like this:
GenApplyLens(
GenApplyLens(
GenApplyLens( o )( _.s ).set( "bar" )
)( _.inner.i ).modify( _+1 )
)( _.inner.d ).set( 5.0 )I am happy to send a PR with the above code. However, chaining calls here is just so much nicer than nesting. You can already do that now with Monocle:
o.applyLens( GenLens[Outer]( _.s ) ).set( "bar" )
.applyLens( GenLens[Outer]( _.inner.i ) ).modify( _+1 )
.applyLens( GenLens[Outer]( _.inner.d ) ).set( 5.0 )But it is still a bit noisy and requires providing an explicit type argument.
One could argue that the method name o.applyLens is redundant because what else would you do with the value o? Also given a function, not a lens, what else could Monocle do instead of trying to generate a lens from it? So one could argue to strip the apply and GenLens symbols from the syntax. (I suppose the same reasoning could be applied to applyIso, applyPrism, etc. And I agree that consistency in the api is very important for usability.)
So we came up with the following (IMHO much nicer) alternative syntax and have been using it for over a year now (published here https://github.com/xdotai/lens):
o.lens( _.s ).set( "bar" ) // .lens calls can be chained nicely
.lens( _.inner.i ).modify( _+1 )
.lens( _.inner.d ).set( 5.0 )An alternative name could be .genApplyLens( ... ), even though as I argued gen and apply are a bit redundant.
I would like to make another proposal to add it to Monocle. FWIW, the implementation shrunk quite a bit since the PR.
Implementation for GenApplyLens
def GenApplyLens[A,C]( value: A )( field: A => C ): ApplyLens[A,A,C,C]
= macro GenApplyLensMacros.GenApplyLensMacro[A, C]
def GenApplyLensMacro[A: c.WeakTypeTag, C]
( c: Context )( value: c.Expr[A] )( field: c.Expr[A => C] ): c.Expr[ApplyLens[A,A,C,C]] = {
import c.universe._
c.Expr[ApplyLens[A,A,C,C]](
q"""
import _root_.scala.language.higherKinds // is Monocle's GenLens requiring this?
_root_.monocle.syntax.ApplyLens(
$value,
_root_.monocle.macros.GenLens[${c.weakTypeOf[A]}](${field})
)
"""
)
}Implementation for .lens
implicit class ImplicitBoundLens[A]( val value: A ) extends AnyVal {
def lens[C]( field: A => C ): ApplyLens[A,A,C,C] = macro GenApplyLensMacros.lensMacro[A, C]
}
def lensMacro[A: c.WeakTypeTag, C]
( c: Context )( field: c.Expr[A => C] ): c.Expr[ApplyLens[A,A,C,C]] = {
import c.universe._
c.Expr[ApplyLens[A,A,C,C]](
q"""
import _root_.scala.language.higherKinds // is Monocle's GenLens requiring this?
_root_.monocle.syntax.ApplyLens(
${c.prefix.tree}.value,
_root_.monocle.macros.GenLens[${c.weakTypeOf[A]}](${field})
)
"""
)
}