About Scala's assignments and setter methods

3.3k views Asked by At

Edit: The bug which prompted this question has now been fixed.


In the Scala Reference, I can read (p. 86):

The interpretation of an assignment to a simple variable x = e depends on the definition of x. If x denotes a mutable variable, then the assignment changes the current value of x to be the result of evaluating the expression e. The type of e is expected to conform to the type of x. If x is a parameterless function defined in some template, and the same template contains a setter function x_= as member, then the assignment x = e is interpreted as the invocation x_=(e) of that setter function. Analogously, an assignment f .x = e to a parameterless function x is interpreted as the invocation f.x_=(e).

So, for instance, something like this works fine:

class A {
  private var _a = 0
  def a = _a
  def a_=(a: Int) = _a = a
}

I can then write

val a = new A
a.a = 10

But if I define the class like this, adding a type parameter to method a:

class A {
  private var _a = 0
  def a[T] = _a
  def a_=(a: Int) = _a = a
}

then it doesn't work any more; I get an error: reassignment to val if I write a.a = 10. Funny enough, it still works with no type parameter and an implicit parameter list, for instance.

Arguably, in this example, the type parameter is not very useful, but in the design of DSLs, it would be great to have the setter method called even if the getter has type parameters (and by the way, adding type parameters on the setter is allowed and works fine).

So I have three questions:

  1. Is there a workaround?
  2. Should the current behavior be considered a bug?
  3. Why does the compiler enforce a getter method to allow using the syntactic sugar for the setter?

UPDATE

Here's what I'm really trying to do. It's rather long, sorry, I meant to avoid it but I realized it was more confusing to omit it.

I'm designing GUIs with SWT in Scala, and having huge fun using Dave Orme's XScalaWT, which immensely reduces the amount of needed code. Here's an example from his blog post on how to create an SWT Composite that converts °C to °F degrees:

var fahrenheit: Text = null
var celsius: Text = null

composite(
  _.setLayout(new GridLayout(2, true)),

  label("Fahrenheit"),
  label("Celsius"),

  text(fahrenheit = _),
  text(celsius = _),

  button(
    "Fahrenheit => Celsius",
    {e : SelectionEvent => celcius.setText((5.0/9.0) * (fahrenheit - 32)) }
  ),
  button(
    "Celsius -> Fahrenheit",
    {e : SelectionEvent => fahrenheit.setText((9.0/5.0) * celsius + 32) })
  )
)

The argument to each of the widget-constructing methods is of type (WidgetType => Any)*, with a few useful implicit conversions, which for instance allow to directly specify a string for widgets that have a setText() method. All constructor functions are imported from a singleton object.

In the end, I'd like to be able to write something along these lines:

val fieldEditable = new WritableValue // observable value

composite(
  textField(
    editable <=> fieldEditable,
    editable = false
  ),
  checkbox(
    caption = "Editable",
    selection <=> fieldEditable
  )
)

This would bind the editable property of the textfield to the selection of the checkbox through the WritableValue variable.

First: named arguments are not applicable here, so the line editable = false has to come from somewhere. So, along the widget-constructing methods in the singleton object, I could write, conceptually,

def editable_=[T <: HasEditable](value: Boolean) = (subject: T) => subject.setEditable(value)

... but this works only if the getter is also present. Great: I'd need the getter anyway in order to implement databinding with <=>. Something like this:

def editable[T <: HasEditable] = new BindingMaker((widget: T) => SWTObservables.observeEditable(widget))

If this worked, life would be good because I can then define <=> in BindingMaker and I can use this nice syntax. But alas, the type parameter on the getter breaks the setter. Hence my original question: why would this simple type parameter affect whether the compiler decides to go ahead with the syntactic sugar for calling the setter?

I hope this makes it a bit clearer now. Thanks for reading…

1

There are 1 answers

7
Kevin Wright On BEST ANSWER

UPDATE Deleted the entire previous answer in light of new information.

There's a lot of very odd stuff going on here, so I'm going try try and explain my understanding of what you have so far:

def editable_=[T <: HasEditable](value: Boolean) = (subject: T) => subject.setEditable(value)

This is a setter method, and exists purely so that it can give the appearance of beinng a named parameter in your DSL. It sets nothing and actually returns a Function.

textField(
  editable <=> fieldEditable,
  editable = false
)

This is calling the textField factory method, with what looks like a named param, but is actually the setter method defined previously.

Amazingly, the approach seems to work, despite my initial concern that the compiler would recognize this as a named parameter and produce a syntax error. I tested it with simple monomorphic (non-generic) methods, though it does require the getter method to be defined for the setter to be seen as such - a fact that you've already noted.

Some amount of "cleverness" is often required in writing a DSL (where it would otherwise be totally forbidden), so it's no surprise that your original intent was unclear. This is perhaps a completely new technique never before seen in Scala. The rules for setter and getter definitions were based on using them as getters and setters, so don't be surprised if things crack a little when you push at the boundaries like this.

It seems the real problem here is the way you're using type params. In this expression:

def editable_=[T <: HasEditable](value: Boolean) = (subject: T) => subject.setEditable(value)

The compiler has no way of inferring a particular T from the supplied argument, so it will take the most general type allowed (HasEditable in this case). You could change this behaviour by explicitly supplying a type param when using the method, but that would seem to defeat the entire point of what you're seeking to achieve.

Given that functions can't be generic (only methods can), I doubt that you even want type bounds at all. So one approach you could try is to just drop them:

def editable_=(value: Boolean) = (subject: HasEditable) => subject.setEditable(value)
def editable = new BindingMaker((widget: HasEditable) => SWTObservables.observeEditable(widget))