11

I'm still learning Scala, but one thing I thought was interesting is that Scala blurs the line between methods and fields. For instance, I can build a class like this...

class MutableNumber(var value: Int)

The key here is that the var in the constructor-argument automatically allows me to use the 'value' field like a getter/setter in java.

// use number...
val num = new MutableNumber(5)
num.value = 6
println(num.value)

If I want to add constraints, I can do so by switching to using methods in place of the instance-fields:

// require all mutable numbers to be >= 0
class MutableNumber(private var _value: Int) {
    require(_value >= 0)

    def value: Int = _value
    def value_=(other: Int) {
        require(other >=0)
        _value = other
    }
}

The client side code doesn't break since the API doesn't change:

// use number...
val num = new MutableNumber(5)
num.value = 6
println(num.value)

My hang-up is with the named-parameter feature that was added to Scala-2.8. If I use named-parameters, my API does change and it does break the api.

val num = new MutableNumber(value=5)  // old API
val num = new MutableNumber(_value=5) // new API

num.value = 6
println(num.value)

Is there any elegant solution to this? How should I design my MutableNumber class so that I can add constraints later on without breaking the API?

Thanks!

shj
  • 1,558
  • 17
  • 23

3 Answers3

11

You can use the same trick that case classes do: use a companion object.

object Example {
  class MutableNumber private (private var _value: Int) {
    require (_value >= 0)
    def value: Int = _value
    def value_=(i: Int) { require (i>=0); _value = i }
    override def toString = "mutable " + _value
  }
  object MutableNumber {
    def apply(value: Int = 0) = new MutableNumber(value)
  }
}

And here it is working (and demonstrating that, as constructed, you must use the object for creations, since the constructor is marked private):

scala> new Example.MutableNumber(5)
<console>:10: error: constructor MutableNumber cannot be accessed in object $iw
   new Example.MutableNumber(5)
   ^

scala> Example.MutableNumber(value = 2)
res0: Example.MutableNumber = mutable 2

scala> Example.MutableNumber()
res1: Example.MutableNumber = mutable 0
Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
  • Interesting! So by hiding the constructor I force everyone to use the companion-object. What if I wanted to make MutableInteger itself a case-class? I know that if I just put 'case' in front of the class definition, Scala automatically creates the companion object for me...would this solution still work? – shj Nov 11 '10 at 20:44
  • Yes, if you have an explicitly defined companion object for a case class then the members of that object will be merged into the generated one (and can override, if for example you want to provide a tweaked `Companion.apply()` method but keep the autogenerated `unapply`). – David Winslow Nov 11 '10 at 20:47
  • @shj - No, that wouldn't work because case classes assume direct (not guarded) access to the constructor variables. You are doing this because you want guards (in the form of `require(_value >= 0)` in this case). – Rex Kerr Nov 11 '10 at 20:48
  • Does scala pattern-matching require direct access to constructors? I thought one of the reasons Scala adds companion-objects is so you don't need to use the constructor explicitly. Should I not use guards in case classes? – shj Nov 11 '10 at 21:03
  • @David - How do you override the autogenerated apply() method? I get 'error: method apply is defined twice' when I try and do it. – shj Nov 11 '10 at 21:37
  • @shj - sorry, i got mixed up. you can *overload* apply (provide alternative definitions) but not *override* it. – David Winslow Nov 11 '10 at 22:00
2

Thanks for the answer! As an aside, I think the Scala-guys might be aware that there's an issue:

What's New in Scala 2.8: Named and Default Parameters

... Until now, the names of arguments were a somewhat arbitrary choice for library developers, and weren't considered an important part of the API. This has suddenly changed, so that a method call to mkString(sep = " ") will fail to compile if the argument sep were renamed to separator in a later version.

Scala 2.9 implements a neat solution to this problem, but while we're waiting for that, be cautious about referring to arguments by name if their names may change in the future.

shj
  • 1,558
  • 17
  • 23
2
class MutableNumber {
    private var _value = 0 //needs to be initialized
    def value: Int = _value
    def value_=(other: Int) {
        require(other >=0) //this requirement was two times there
        _value = other
    }
}

you can modify all members of any class within curly braces

val n = new MutableNumber{value = 17}
Aaron Novstrup
  • 20,967
  • 7
  • 70
  • 108
Arne
  • 7,921
  • 9
  • 48
  • 66
  • That does have the disadvantage of creating an anonymous for each MutableNumber instantiation that uses braces. – Ken Bloom Nov 15 '10 at 01:50