Skip to content

ApplyLens macro #410

@cvogt

Description

@cvogt

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})
      )
    """
  )
}

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