Watched Properties in Scala

On 27 April 2010, in Programming, Scala, by stacy

Scala is pretty damn neat.  I’ve only just started learning the language (an earlier attempt had to be sidelined because “real” work interfered), but I’m back to it now.

Properties

Here’s a funny little class I’ve been playing with in some development work.

class Property[T](var value:T) {
  def get:T = value
  def set(newval:T) = {
    value = newval
    this
  }
  def :=(newval:T) = set(newval)
  override def toString = "Property(" + value + ")"
}
implicit def Property2value[T](prop:Property[T]) = prop.get

I think that’s just too neat. The property can be set with the (admittedly PL/1-ish) := operator, and the value can be extracted with a cast or assignment.

If I could override the assignment then I could make this very, very C++-ish-ish. Note that I could have written something like the following.

def `=`(newval:T) = set(newval)

But then I’d have to use this as follows.

scala> val p = new Property(12)
scala> p `=` 13

That’s just not the same thing. Scala does not allow overriding assignment; no copy constructor for you.

If you are willing to declare p to be a var, then you can do the following. But this is really not what you want since it creates a new property, and does not modify the value of the existing property.

scala> def ValueToProperty[T](value:T) = new Property(value)
 
scala> var p = new Property(12)
p: Property[Int] = Property(12)
 
scala> p = 171
p: Property[Int] = Property(171)

Watched Property

So what’s the point of having this mutable property class? Well, now that I have the above I can add code to watch the property.

trait Watcher[T,U] {
  def notify(value:T, data:U)
}
trait Watchable[T,U] {
  var watchers:Map[Watcher[T,U],U] = Map()
  def register(watch:Watcher[T,U], data:U) = {
    watchers += (watch -> data)
  }
  def notify(value:T) = {
    watchers foreach {
      case (watcher, data) => watcher.notify(value, data)
    }
  }
}
class WatchedProperty[T,U](value:T) extends Property(value) with Watchable[T,U] {
  override def set(newval:T) = {
    super.set(newval)
    notify(newval)
    this
  }
}

Now I have a watcher and a watchable. I mix in the watchable trait with the property, and presto! I have a watched property. It’s somewhat of a big deal for some work I’m doing now; this would have made my life much easier.

class See[T,U]() extends Watcher[T,U] {
  def notify(value:T, data:U) = {
    println("Value is now " + value + " and data is " + data + ".")
  }
}

Finally, we do some “work.”

scala> val see = new See[Int,String]
see: See[Int,String] = See@fab91
 
scala> val watch = new WatchedProperty[Int,String](1)
watch: WatchedProperty[Int,String] = Property(1)
 
scala> watch register (see,"watch")
 
scala> watch := 12
Value is now 12 and data is watch.
res22: Property[Int] = Property(12)
 
scala> watch := 31
Value is now 31 and data is watch.
res23: Property[Int] = Property(31)

Of course, I can also avoid new by declaring the companion object for WatchedProperty and adding a factory method. Life in Scala is pretty good (so far).

object WatchedProperty {
  def apply[T,U](value:T) = new WatchedProperty[T,U](value)
}

Cleaner Watched Property

There is a problem with the implementation of watched property, and it is the type annotation U. Why should the property have to know the type for the data? The data only exists for the benefit of the watcher. Worse, the watched property isn’t a drop-in replacement for property; it has two parameters instead of just one. Finally, everyone who wants to register must use the same data type U (or a subclass). Here are some alternatives.

  1. The watcher can maintain a map from watched property to the associated data. The watched property can then send itself with the property change notification. This is bad. The watcher has to maintain the map. If it is watching a lot of properties then it may have to maintain a big map, and there is lookup overhead. We avoid that by having the property return the data with the notification.
  2. We could treat the data as an Any. Then it is the responsibility of the watcher to correctly cast the value during notification. This is a nuisance because the compiler cannot catch type problems for us, but Java programmers should be used to that by now.

Let’s try the second approach.

trait Watcher[T] {
  def notify(value:T, data:Any)
}
trait Watchable[T] {
  var watchers:Map[Watcher[T],Any] = Map()
  def register(watch:Watcher[T], data:Any) = {
    watchers += (watch -> data)
  }
  def notify(value:T) = {
    watchers foreach {
      case (watcher, data) => watcher.notify(value, data)
    }
  }
}
class WatchedProperty[T](value:T) extends Property(value) with Watchable[T] {
  override def set(newval:T) = {
    super.set(newval:T)
    notify(newval)
    this
  }
}

That’s better, actually. Watched property is again a referentially-transparent replacement for property, and watchers can use any data type they want. What could be better than this?

Better Watched Property

Actually that whole “Any” value is the problem now. This implementation cleans up the design a bit with better information hiding and referential transparency, but it also musses up type safety a bit. The watcher knows the type of the data; why can’t it get back properly-typed data during notification?

Maybe we’ve been asking the wrong question. The issue is, I think, the Java-esque nature of the solution. Why does notify really have to be passed the data? Why not do something very different? In fact, why not get rid of the watcher trait altogether? Why do we need it? We really just want to invoke a function (or method) when the property is changed. Let’s just directly register the callback, and forget the middleman.

trait Watchable[T] {
  var watchers:List[(T) => Unit] = List()
  def register(notify:(T) => Unit) {
    watchers += notify
  }
  def notify(value:T) = {
    watchers foreach (notify => notify(value))
  }
}
class WatchedProperty[T](value:T) extends Property(value) with Watchable[T] {
  override def set(newval:T) = {
    super.set(newval)
    notify(newval)
    this
  }
}

Watcher is gone. The data item is gone. The funny type problem is gone. The code is simpler, and even more flexible, since anything can be a watcher, including lambdas.

scala> val prop = new WatchedProperty(21)
prop: WatchedProperty[Int] = Property(21)
 
scala> def see(x:Int) = println("Value changed to: " + x + ".")
see: (Int)Unit
 
scala> prop.register(see)
 
scala> prop.register((x:Int) => println("Lambda sees value: " + x + "."))
 
scala> prop := 413
Value changed to: 413.
Lambda sees value: 413.
res10: Property[Int] = Property(413)

Data Again

Well, what about data? The value for data can be captured in a closure. Here’s an example, where the data is a name for the property being monitored.

class ChangeWatcher {
  def watch[T](prop:WatchedProperty[T], name:String) {
    prop register {
      (value:T) => println(name + ": " + value)
    }
  }
}

Did you get all that? The above is a fully-generic watcher. You pass it a property and a name for the property. Whenever the property is changed, it notifies you. This works for any kind of property.

scala> val prop = new WatchedProperty(21)
prop: WatchedProperty[Int] = Property(21)
 
scala> val status = new WatchedProperty("okay")
status: WatchedProperty[java.lang.String] = Property(okay)
 
scala> val temp = new WatchedProperty(21.8)
temp: WatchedProperty[Double] = Property(21.8)
 
scala> val cw = new ChangeWatcher
cw: ChangeWatcher = ChangeWatcher@3da8bc
 
scala> cw.watch(prop, "prop")
 
scala> cw.watch(status, "status")
 
scala> cw.watch(temp, "temperature")
 
scala> status := "Danger!"
status: Danger!
res18: Property[java.lang.String] = Property(Danger!)
 
scala> temp := 73.2
temperature: 73.2
res19: Property[Double] = Property(73.2)

The important thing to note is that you don’t have to have data. Or you can have lots of data. Or whatever you want. The responsibility for data is all back to the watcher, and off the shoulders of the watchable. This seems (to me) to be the correct division of responsibility.

To sum up: Functional Programming + Objects = Good.

Share
Tagged with:  

5 Responses to Watched Properties in Scala

  1. Wade says:

    Two newbie questions:

    “The responsibility for data is all back to the watcher, and off the shoulders of the watchable.” In what other language is the responsibility for data on the watchable, not the watcher? What can I compare this to?

    Is this example similar to an event-listener pattern?

  2. Landei says:

    “Scala does not allow overriding assignment; no copy constructor for you.”

    You can use an “update” method to come at least close to assignment syntax:

    class Property[T](var value:T) {
    def get:T = value
    def update(t:T) { value = t }
    }

    val v = new Property(“hi”)

    //this doesn’t work
    v = “hello”
    –> error: reassignment to val
    –> v = “hello”
    –> ^

    //but this *does* work
    v() = “hello”

    println(v.get)
    –> hello

  3. This is one area where the language features of Scala really comes together beautifully. There are lots of new concepts and synergies to explore in relation to reactive programming/properties/STM topics: transactions, signals, inheritance, reflection etc. Scala with it’s mix of functional programming, objects, powerful type system and runtime type information makes it a perfect language for this type of programming.

  4. Matt Hicks says:

    You should look at the properties framework that Sgine provides: http://www.sgine.org as it has listenable properties and dozens of other mixable traits to enhance your properties.

  5. ittay says:

    Did you consider scala.collection.mutable.Publisher?

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">