Scala Properties Question
Asked Answered
L

3

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!

Lewan answered 11/11, 2010 at 20:20 Comment(0)
E
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
Eosinophil answered 11/11, 2010 at 20:38 Comment(6)
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?Lewan
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).Vanpelt
@Lewan - 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).Eosinophil
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?Lewan
@David - How do you override the autogenerated apply() method? I get 'error: method apply is defined twice' when I try and do it.Lewan
@Lewan - sorry, i got mixed up. you can overload apply (provide alternative definitions) but not override it.Vanpelt
L
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.

Lewan answered 11/11, 2010 at 22:50 Comment(1)
What is the neat solution in Scala 2.9?Zasuwa
E
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}
Euphroe answered 12/11, 2010 at 23:1 Comment(1)
That does have the disadvantage of creating an anonymous for each MutableNumber instantiation that uses braces.Bullhead

© 2022 - 2024 — McMap. All rights reserved.