Open kenbot opened 7 years ago
I really want this feature, but the others (QuickLens, Shapeless, Monocle's @GenLens) don't support it, so it can probably wait until after 1.0.
Ok this one is interesting.
We can't just spit out a PSetter instance, because the STAB types would have to be known up front, and the B and T depend on what the modifying function does. We would have to generate a method, in the style of monocle.std.option.pSome
:
case class Foo[A](a: A)
def x[A,B] = PSetter[Foo[A], Foo[B], A, B](f => s => s.copy(a = f(s.a)))
x.modify(_.toString)(Foo(3)) // Compiles
// set"${Foo(3)}.$x" ~= (_.toString) // Doesn't compile
Calling modify in the usual Monocle way benefits from type inference in an interesting way:
(_.toString)
doesn't have enough information to know what _
is yetFoo(3)
gives the game away; S
= Foo[Int]
, therefore A
= Int
, and the toString bit means that B
= String
, and finally T
= Foo[String]
.Unfortunately, we seem to be tripping a limitation of this inference. Goggles generates something like:
set"${Foo(3)}.$x" ~= (_.toString)
// becomes
new MonocleModifyOps(AppliedObject.const(Foo(3)).composeSetter(x)) ~= (_.toString)
which fails compilation:
[error] /Users/ken_scambler/projects/toy/goggles/dsl/src/test/scala/goggles/SetDslSpec.scala:212: The types of consecutive sections don't match.
[error] found : SetDslSpec.this.Foo[Nothing]
[error] required: SetDslSpec.this.Foo[Int]
[error]
[error] Sections │ Types │ Optics
[error] ───────────┼──────────────────────────┼────────
[error] ${Foo(3)} │ Foo[Int] │
[error] .$x │ Foo[Nothing] ⇒ Nothing │ Setter
[error] set"${Foo(3)}.$x" ~= (_.toString)
[error] ^
We put Foo(3)
in the leftmost position, so that S
= Foo[Int]
is known from the outset, and can flow left-to-right. From Foo[Int]
, x
knows that A
is Int
, but does not yet know what B
or T
are.
However, once this incompletely-typed optic gets passed into the MonocleModifyOps
constructor, it seems that it passes a threshold where it doesn't want incomplete things any more. B
gets fixed to Nothing
, and by the time (_.toString)
gets read, it's too late.
Goggles' design is for the '~=' modification operator to be regular Scala, which sits outside the macro. It's probably quite hard to smuggle the type back through.
~=
a macro too, so we can replace it all with generated code that we know type checks.
I don't really want to make this more "magic" or opaque, but it might work.set"${Foo(3)}.$x ~= ${(_.toString)}"
.
I really don't want to do this; the more we put in actual Scala the easier it is to learn and use. There are no end of novelties we can cram in the syntax.enclosingXXX
methods that facilitate this are deprecated. Even if it is possible, it seems uncomfortably close to "reimplementing scalac in the macro"We should certainly do 1. in the short term; more thought and experimentation might yield better options.
Needs more research, descoping from 1.1.
Case classes naturally support polymorphic update:
This should work in Goggles too, as expected, using PSetters and PLenses.