The Artima Developer Community
Sponsored Link

Weblogs Forum
The Autoproxy Plugin - Part II

5 replies on 1 page. Most recent reply: Jan 19, 2010 7:58 AM by Kevin Wright

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 5 replies on 1 page
Kevin Wright

Posts: 38
Nickname: kevwright
Registered: Jul, 2009

The Autoproxy Plugin - Part II (View in Weblogs)
Posted: Dec 2, 2009 1:28 PM
Reply to this message Reply
Summary
Using Scala to make your code lovely
Advertisement

Follow-up to Part One: It looks like the name of the annotation is open for debate. For anyone with an account, please do head over to the wave for this project and add to the discussion there. Otherwise, feel free to add a comment below. For the remainder of this article I'm trialling the alternative @mixin annotation

Back Story

Some months ago, I ran into a small problem with the Scala REPL (Read-Evaluate-Print-Loop) under MS Windows.

This was using JLine, which hooks a native library into the command window to find the window size and to support key bindings such as tab completion. JLine is a great solution, except when you need to run the REPL in a nested process with redirected I/O, or connect to it remotely via a protocol such as Telnet. In such cases you have to sacrifice functionality.

Seeing the situation, I felt that Scala could benefit from a swing-based REPL. This would not need JLine to support the full range of functionality in 2.8, would be embeddable, and would allow for connecting to a remote JVM. It also looked like a good project to stretch my growing experience of coding in Scala and gave me a chance to experiment with a bit of swing in the language.

For various reasons, this project has been postponed (demand from the community wasn't strong at the time, JEditorPane and JTextPane still need to be ported over to the scala swing library, and it looked unlikely that I would have it done in time for the then-predicted 2.8 release), but it did expose me to lots of code that looked a bit like this:

class MyClass(peer: OtherClass) {
  def method1 = peer.method1
  def method2(n:Int) = peer.method2(n)
  def method3(s: String) = peer.method3(s)
  def prop1 = peer.prop1
  def prop1_=(n:Int) = peer.prop1_=(n)
  def prop2 = peer.prop2
  ...
}

Pulling in a bunch of methods and properties from elsewhere is why traits were invented, it's the entire reason that Scala has "with" as a keyword. But, as discussed in part I, traits/mixins are of no use in this situation because the OtherClass instance, peer, already exists.

Still, the code contains a lot of boilerplate - the sort of thing that IDEs can readily generate for Java projects. This just has to be a code smell. Surely, there's something the compiler can do to help?

Dynamic Mixins

What I first wanted is a way to change an expression like this:

val x = new Foo with Bar

and instead write something like this:

val f = //create or fetch an instance of Foo
val x = f with Bar

Thus decorating a pre-existing instance of Foo with extra functionality. This capability is often referred to as "dynamic mixins" on the Scala mailing lists.

Although such syntax may be possible [1] , it would require a change to the parsing rules of Scala and is really beyond the scope of a compiler plugin.

Instead, I settled on the @mixin (formerly @proxy) annotation to extend the language in a more plugin-friendly fashion:

class Bar(@mixin foo: Foo) {
  ...
}
val f = ...some method returning an instance of Foo...
val x = new Bar(f)

I then sought feedback about this approach via the Scala mailing lists and the responses were unexpected! Instead of just seeing my proposal as a dynamic alternative to mixins, there was a lot of curiosity about other ways in which this annotation might be used. Since that time I've expanded the scope of the plugin and I'm now testing it in a number of scenarios that are increasingly removed from the original goal. The remainder of this article covers some of the uses...

[1]Use of the "new" keyword avoids any ambiguities. Consider "val f = new Foo with bar" and "val f = Foo with Bar", in the second case Bar is being mixed into the existing singleton object Foo. It should even be possible to refer to this type signature as "Foo.type with Bar", though in practice it would probably be cleaner to use a type alias or a dedicated interface trait for the purpose.

Customising accessors

Non-private member variables in Scala are generated as a hidden field, plus a getter and (in the case of vars) setter method. Sometimes it's desirable to override these generated methods, perhaps for logging, security or to notify change listerners.

One way to achieve this is to create a private member and hand-craft the accessors:

class Foo {
  private[this] val x0 : Int = 0
  def x = { x0 }
  def x_=(value : Int) = {
    // custom behaviour goes here
    x0 = value
  }
}

This needs a naming convention to be chosen for the private fields. It also needs the developer to write a getter method identical to the one the compiler would have generated, which feels a bit redundant.

It's easy enough to avoid the proliferation of private names by holding them all in a singleton object:

class Foo {
  private[this] object props {
    val x : Int = 0
  }
  def x = { props.x }
  def x_=(value : Int) = {
    // custom behaviour goes here
    props.x = value
  }
}

This approach then lends itself to using autoproxy for re-use of the compiler-generated methods:

class Foo {
  private[this] @mixin object props {
    var a : Int = _
    var b : Double = _
    var c : String = _
  }

  //The following defs are mixed in: a, a_=, b, b_=, c
  //a delegate for c_= is NOT created as it already exists below

  def c_=(str : String) = println("c set to " + str); props.c = str
}

Thus allowing accessors to be selectively overridden, with minimal boilerplate.

Mixing-in Parameterized Traits

Due to type erasure, the types Foo[String] and Foo[Bar] are identical at runtime, with the type safety only being enforced within the compiler. Scala 2.8 offers the @specialize annotation, manifests and view bounds to help out with many of the problems that this can cause, but there is one problem that can't be handled - mixing in differently paramaterized versions of the same trait:

trait Foo[T] {
  def foomethod(arg: T) = println(arg)
}

//can't do this!
class Bar extends Foo[String] with Foo[Int] {
  def barmethod = println("bar method")
}

Once again, autoproxy can help here:

trait Foo[T] {
  def foomethod(arg: T) = println(arg)
}

//this works though!
class Bar {
  @mixin val foo1 = new Foo[Int]
  @mixin val foo2 = new Foo[String]
  def barmethod = println("bar method")
}

This approach is fine for methods such as foomethod above that have different type signatures in each of the mixed-in traits, they will just end up as overloaded versions in the Bar class.

However, for any method that exists in both Foo[Int] and Foo[String] with the same signature, the generated delegate will use the version from Foo[Int] as it was the first mixin listed in Bar. Depending on your needs, this can be seen as either a problem to work around or a useful design feature.

The Future

The plugin is still in desperate need of testing in as many scenarios as possible. But I'm hopeful of having it up to production quality in time for the 2.8 Scala release.

One problem I would like to tackle though is javabean properties. Currently, if a field is annotated as @BeanProperty then it has getXXX and setXXX methods generated in addition to the standard scala getters and setters, when such an object is mixed-in then separate delegates will be created for the Scala and the JavaBean accessors. Ideally, it should be possible to mixin an object, override the Scala accessors, and have the JavaBean accessors delegate to the overridden implementation instead of the original.

It would also be a very useful extra if I could take an existing object using Javabean style properties, and proxy it using Scala style properties.

And The Next Article?

I'll cover how the plugin is implemented. This involved some fun interactions with the typer phase of the compiler, which fails if delegates have not yet been generated, but also needs to be run to provide information used in generating the delegates.


Sean Reque

Posts: 1
Nickname: sreque
Registered: Dec, 2009

Re: The Autoproxy Plugin - Part II Posted: Dec 4, 2009 12:31 PM
Reply to this message Reply
This plugin is a great idea! Do you know how it interacts with the current crop of Scala IDEs? I'm assuming that since the IDE plugins interact with the compiler directly that they are aware of the existence of the generated methods. Can They also properly deduce the line of code where these generated methods are defined via the annotation?

Kevin Wright

Posts: 38
Nickname: kevwright
Registered: Jul, 2009

Re: The Autoproxy Plugin - Part II Posted: Dec 5, 2009 4:12 AM
Reply to this message Reply
For the most part it works in eclipse, with outlining and code completion showing the generated delegates. However, all the normal disclaimers apply for working against trunk builds. When problems do occur it's not always obvious if the plugin has messed up or if something changed in eclipse or the core Scala libraries.

I've also started experimenting with NetBeans, which integrates with the Scala compiler in a manner similar to eclipse, and I'm hoping will work without too much pain.

IntelliJ IDEA is still an unknown quantity.

Ravindra Jaju

Posts: 1
Nickname: jaju
Registered: Dec, 2009

Re: The Autoproxy Plugin - Part II Posted: Dec 20, 2009 9:38 PM
Reply to this message Reply
Nice article - helps to know that you can really reduce the amount of code you need to write (and manage, and debug, and understand...) by switching over to Scala.
Eagerly looking forward to the next article.

Richard Wallace

Posts: 2
Nickname: rwallace
Registered: Nov, 2009

Re: The Autoproxy Plugin - Part II Posted: Jan 13, 2010 9:32 AM
Reply to this message Reply
I had a look at the source on github and was wondering if you decided you liked the @mixin annotation better than @proxy?

Kevin Wright

Posts: 38
Nickname: kevwright
Registered: Jul, 2009

Re: The Autoproxy Plugin - Part II Posted: Jan 19, 2010 7:58 AM
Reply to this message Reply
Oh yes...

Development slowed down a lot in the pre-beta churn for the 2.8 release (I was trying to hit a moving target...)

Now it's stabilised somewhat, I'll be updating to use the new annotation name

Flat View: This topic has 5 replies on 1 page
Topic: Owners vs Caretakers Previous Topic   Next Topic Topic: Wrong Correctness

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use