Monday, 30 September 2013

More on the Subtleties of Scala and the Uniform Access Principle

I posted before about Scala and the Uniform Access Principle.  At that point I felt pretty smug that I’d noticed something clever.  Well I didn’t realise everything, but luckily the Atomic Scala Atom: “Uniform Access and Setters” was there to bring me fully up to speed.

Once you’ve seen it, it makes perfect sense, but when I came into this chapter-ette, I’d been under the impression thought you could swap defs for vals or vars as you pleased.  However, it soon became clear that there was more to think about, and this thinking comes down to contracts; contracts that the language makes with us, and which it (and we) can’t break.

For this discussion, the relevant Scala contracts are:

  • vals are immutable
  • vars aren’t
  • functions (def) can return different values and so Scala can’t guarantee the result will always be the same

This means that you can’t just implement fields and methods from an abstract base type in a subtype using any old variable or function. You must make sure you don’t break the contract that was made already in the parent.  Explicitly:

  • an abstract def can be implemented as a val or a var
  • an abstract var can be implemented as a def as long as a you also provide a setter as well as a getter

You’ll note that abstract vals can’t be implemented with defs.  That makes sense if we think about it.  A def could return various things – Scala can’t guarantee it’ll always be the same (especially if you consider overriding), whereas vals are immutable.  Broken contract? Denied.

An Added Subtlety

But wait a minute, we missed a fourth contract.  That second bullet mentioned setters. The contracts in play here are actually four-fold:

  • vals are immutable
  • vars aren’t
  • functions (def) can return different values and so Scala can’t guarantee the result will always be the same
  • vars require getters and setters

But we can still roll with that if we add another little piece of Scala sugar, we can supply a setter method in the subtype:

def d3 = n
def d3_=(newVal:Int) = (n = newVal)

Here, the “def d3_=…” line adds the setter Scala needs to fulfil the contracts and we’re back in action.

Does This Also Stand For Non-Abstract Overrides?

One final thing to consider is how uniform really is the Scala implementation of the principle? Pretty well universal as far as I can see, because going beyond the scope of the Atom, what happens when the superclass and it’s methods and fields aren’t abstract?  It turns out it’s exactly the same as above, as long as you remember your override keywords.  Predictable. Nice.