Lensed

217 views
Skip to first unread message

Jason Zaugg

unread,
Jul 18, 2011, 9:25:28 AM7/18/11
to sca...@googlegroups.com
Gerolf Seitz has been tinkering with Scalac to autogenerate Lenses for case classes fields.

The basics are already working with the caveats that 1) you have to compile these classes in a separate compiler run, most straightforwardly by defining them in a separate sub-project; and 2) IntelliJ doesn't know about these methods. The first problem is well known limitation in the scalac plugin architecture, and might be addressed in a future version of the compiler. I can update IntelliJ to fix the second.

So what else should we generate? We had a quick chat about it, and I've sketched out this proposal [2]. The idea, inspired by Edward's presentation and the code in scalaz.Lens, is to make Lens composition as easy as chaining method calls:

  Person.address.street.set(p, "Lower Main St")

We could also extend this plugin to generate instances of Show and Equal.

-jason


Chris Twiner

unread,
Jul 18, 2011, 9:32:03 AM7/18/11
to sca...@googlegroups.com
Seriously, you both are getting burgers the next time we meet. That's
simply just excellent, got a big smile on right now.

Re synthetics and friends in plugins, yeah that sucks - caused me a
tonne of pain a few years ago (to the point I just gave up), but at
least sbt makes the separation of compiler runs and plugins simpler.

Is the plugin being disabled on console via sbt (or is it no longer an
issue with 10.x)?

> --
> You received this message because you are subscribed to the Google Groups
> "scalaz" group.
> To view this discussion on the web visit
> https://groups.google.com/d/msg/scalaz/-/bLHs28NBnMgJ.
> To post to this group, send email to sca...@googlegroups.com.
> To unsubscribe from this group, send email to
> scalaz+un...@googlegroups.com.
> For more options, visit this group at
> http://groups.google.com/group/scalaz?hl=en.
>

Gerolf Seitz

unread,
Jul 18, 2011, 9:34:37 AM7/18/11
to sca...@googlegroups.com
The plugin works fine inside the console.
I think you can even run the console on the same sbt project and 
still get access to the generated code, since the source is compiled 
before entering the console. Haven't actually tried that though.

  Gerolf
--
Gerolf Seitz

twitter: @gersei

Chris Twiner

unread,
Jul 18, 2011, 9:52:09 AM7/18/11
to sca...@googlegroups.com
sweet

Runar Bjarnason

unread,
Jul 18, 2011, 10:08:08 AM7/18/11
to sca...@googlegroups.com
That is freaking amazing.


Robert Wills

unread,
Jul 18, 2011, 10:42:40 AM7/18/11
to sca...@googlegroups.com
Very cool.

Tony Morris

unread,
Jul 18, 2011, 5:25:00 PM7/18/11
to sca...@googlegroups.com

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Love it.

In Ed's presentation (if I remember right), he mentions his preference
for fusing the get/set. I have done this in scalaz7 where a Lens[A] is
represented by a function A => CoState[B, A]. I wonder if this would
change your proposal whereby a (new Person).name wraps and composes a
Lens, instead p.name would be a CoState[String, Person] with no
wrapping. "CoState" is also often called "Store" if you prefer that name.

- --
Tony Morris
http://tmorris.net/

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAk4kpKsACgkQmnpgrYe6r60/jQCeJ90PT04Qa3ONUEKpYjZJnCF8
H6YAn2Hk45YH/SGiJ9cQ542dqRgXDQdx
=FNZX
-----END PGP SIGNATURE-----

Jason Zaugg

unread,
Jul 18, 2011, 5:49:50 PM7/18/11
to sca...@googlegroups.com
On Mon, Jul 18, 2011 at 11:25 PM, Tony Morris <tonym...@gmail.com> wrote:
> In Ed's presentation (if I remember right), he mentions his preference
> for fusing the get/set. I have done this in scalaz7 where a Lens[A] is
> represented by a function A => CoState[B, A]. I wonder if this would
> change your proposal whereby a (new Person).name wraps and composes a
> Lens, instead p.name would be a CoState[String, Person] with no
> wrapping. "CoState" is also often called "Store" if you prefer that name.

Not sure I follow, I need to watch that presentation again.

Is the motivation for the CoState representation a more efficient
Lens#mod? Person#name can't be both a String and CoState[String,
Person]. Do you mean Person.name?

Might be easiest just to update the sample to get it through to me :)

-jason

Tony Morris

unread,
Jul 18, 2011, 6:14:15 PM7/18/11
to sca...@googlegroups.com

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

I've added the alternative Lens representation here
https://gist.github.com/1090787

I'm on the fence about whether p.name should return Store[String,
Person]. It would break compatibility anyway.

- --
Tony Morris
http://tmorris.net/

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAk4ksDcACgkQmnpgrYe6r6131QCcDy2PWv+DWVf4KpeuMKDAoCOU
eqwAoMp55Tz7/GEk79ZlysI6w50rRqd1
=+VZq
-----END PGP SIGNATURE-----

Miguel Negrão

unread,
Jul 19, 2011, 8:11:56 AM7/19/11
to sca...@googlegroups.com
Amazing !! I was really looking foward to this !

Stephen Haberman

unread,
Jul 19, 2011, 10:50:06 AM7/19/11
to sca...@googlegroups.com
 
Gerolf Seitz has been tinkering with Scalac to autogenerate Lenses for case classes fields.

Wow, that's awesome.

I've done basically the same thing with Java and annotation processors, at http://www.bindgen.org. You annotate a class Person as @Bindable, and then a PersonBinding is generated with dummy version of Lens created for each of its public fields/strings. It's not as pretty, but the same effect:

    new PersonBinding().address().street().set(person, "the street")

That being said, annotation processors can be a pita, so I've been anxiously waiting to see how this could be done in scala.

One thought I'd considered, and just wanted to mention, is potentially using a syntax to generate lens, basically like Gosu's feature literals. So:

    val lens = Person#address#street

Would generate a lens class to do "return person.address.street" and "person.address.street = x".

There are few benefits of this:

1) It wouldn't have to be only for case classes
2) It's a "caller-side" concern, so you don't have to worry about whether the Person class is/is not a case class, and whether whoever compiled it had the compiler plugin turned on
3) It creates lenses on-demand (as needed for each call) where as the approach that both Lensed and bindgen take is to proactively generate lens/inner classes for every attribute you might access, whether or not you need them. Since Lensed is only for case classes, this is probably not a big deal, but bindgen would do any class, plus recurse into classes (in case you controlled PersonView, so could annotate it, but didn't control Person, but still wanted to bind against it), so it could get a little out of hand.

Anyway, I don't mean to distract from Lensed, it's very awesome and I'll look forward to using it when I get a chance. But I also really like Gosu's first-class syntax approach and so just wanted to mention it.

Thanks!

- Stephen


Gerolf Seitz

unread,
Jul 29, 2011, 3:11:33 AM7/29/11
to scalaz
A quick update on the project:

I've implemented Jason's proposal[2] and also made it work with case
classes
with simple type parameters (aka no view/context bounds).

I've updated the README on github and so far it works quite well for
basic use cases (no pun intended).

Regards,
Gerolf

Jason Zaugg

unread,
Jul 29, 2011, 3:28:30 AM7/29/11
to sca...@googlegroups.com
On Fri, Jul 29, 2011 at 9:11 AM, Gerolf Seitz <gerolf...@gmail.com> wrote:
> A quick update on the project:
>
> I've implemented Jason's proposal[2] and also made it work with case
> classes
> with simple type parameters (aka no view/context bounds).
>
> I've updated the README on github and so far it works quite well for
> basic use cases (no pun intended).

Cool!

Tony mentioned on IRC that it would be nice to add reflection on the
field names, maybe like:

/** A field of type B contained in an A */
case class Field[A, B](name: String, lens: Lens[A, B], man: Manifest[B])
case class FieldManifest[A](fields: Map[Field[A, _]))

case class Person(name: String, age: Int)

object Person {
implicit def PersonManifest: FieldManifest[Person] = ...
}

Not sure exactly what the use case was, perhap he can elaborate.

-jason

Tony Morris

unread,
Jul 29, 2011, 3:57:50 AM7/29/11
to sca...@googlegroups.com

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

One use-case at the very least, is a pretty-printer (Show).

Person(age = 7, name = Bob)

PS: Gerolf, you're awesome
- --
Tony Morris
http://tmorris.net/

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAk4yZ/4ACgkQmnpgrYe6r61ftwCdGdvwW1G3EjSliZfW2P/1yPAP
o+wAn38s539O2QmPjuneArKiewgR2Jjk
=99yM
-----END PGP SIGNATURE-----

Gerolf Seitz

unread,
Jul 29, 2011, 4:13:57 AM7/29/11
to sca...@googlegroups.com
On Fri, Jul 29, 2011 at 9:57 AM, Tony Morris <tonym...@gmail.com> wrote:

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

One use-case at the very least, is a pretty-printer (Show).

Person(age = 7, name = Bob)

Since Person is a case class, can't the Show implementation just call (new Person).toString, 
or use reflection to get the field names?
Am I missing something here?

 

--
You received this message because you are subscribed to the Google Groups "scalaz" group.
To post to this group, send email to sca...@googlegroups.com.
To unsubscribe from this group, send email to scalaz+un...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/scalaz?hl=en.

Tony Morris

unread,
Jul 29, 2011, 4:17:01 AM7/29/11
to sca...@googlegroups.com

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

1) Reflection is unsafe -- better for the compiler to produce it.
2) Given the Lens, there is no way to recover the field name, even
with reflection.

The toString for a case class doesn't include the field name. Another
use-case for the field name might be for example, binding to a web form.



On 29/07/11 18:13, Gerolf Seitz wrote:
> On Fri, Jul 29, 2011 at 9:57 AM, Tony Morris <tonym...@gmail.com>
> wrote:
>
>> **
 --
You received this message because you are subscribed to the Google Groups
"scalaz" group.
To post to this group, send email to sca...@googlegroups.com.
To unsubscribe from this group, send email to
scalaz+un...@googlegroups.com.
For more options, visit this group at
http://groups.google.com/group/scalaz?hl=en.
>>

- --
Tony Morris
http://tmorris.net/

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAk4ybH0ACgkQmnpgrYe6r60bdQCgm+7Poy4ymk6KKzSvQmcHeQfT
ie8An3BFvzPEmW9QlCAkJdQ1x9UJqLpi
=wWxa
-----END PGP SIGNATURE-----

Gerolf Seitz

unread,
Jul 29, 2011, 4:20:54 AM7/29/11
to sca...@googlegroups.com
On Fri, Jul 29, 2011 at 10:17 AM, Tony Morris <tonym...@gmail.com> wrote:

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

1) Reflection is unsafe -- better for the compiler to produce it.
2) Given the Lens, there is no way to recover the field name, even
with reflection.

But the Lens is not in the case class itself, but only in its companion object.
So this wouldn't be an issue.
I agree on the other points though.

Jason Zaugg

unread,
Jul 29, 2011, 4:22:25 AM7/29/11
to sca...@googlegroups.com
On Fri, Jul 29, 2011 at 10:17 AM, Tony Morris <tonym...@gmail.com> wrote:
>
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> 1) Reflection is unsafe -- better for the compiler to produce it.
> 2) Given the Lens, there is no way to recover the field name, even
> with reflection.
>
> The toString for a case class doesn't include the field name. Another
> use-case for the field name might be for example, binding to a web form.

There is likely to be some overlap with the in-progress scala.reflect.

Another interesting avenue for experimentation performance: what can
we do with @specialized and (scalac) inlining to bring the performance
of Lens based access up to par with direct access?

-jason

Tony Morris

unread,
Jul 29, 2011, 4:32:04 AM7/29/11
to sca...@googlegroups.com

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

I wonder if it would be useful to generate instances of:

type F[A] = List[A] // for now (there is a O(1) snoc list in scalaz7)

case class @!@[A, B](names: F[String], lens: Lens[A, B]) {
  def compose[C](z: B @!@ C): (A @!@ C) =
    @!@(names ::: z.names, lens compose z.lens)
}

...giving access to field names as the lenses compose.


- --
Tony Morris
http://tmorris.net/

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAk4ycAQACgkQmnpgrYe6r63RtQCfasSQAs3TElIvlAlYB4xPgvZ6
YRAAoMGeuyEJhYrpMEldCFnz9qbwifGa
=4Cgq
-----END PGP SIGNATURE-----

Reply all
Reply to author
Forward
0 new messages