10
votes

Is it possible to use named arguments in a Scala constructor, and later on override getters and setters without breaking the constructor interface or making the code extremely ugly?

Take the following bit of scala code

class Person( var FirstName: String, var LastName: String )

Nice and clean. This would create a simple class called person, which we could use in the following way

val john = new Person( FirstName="John", LastName="Doe" )
john.FirstName = "Joe"
println( john.FirstName )

Later, we decide we want to add some validation to the FirstName setter. As such, we create a new private local variable and override the getter and setter methods

class Person( var _FirstName: String, var _LastName: String ) {

    def FirstName = _FirstName  
    def FirstName_= (value:String) = _FirstName = value

}

Still somewhat clean, however in order to do this, we've had to change the constructor argument names, thus breaking the external interface.

The first solution to this problem I came up with was

class Person {
    var _FirstName:String = null 
    var LastName:String  = null

    def FirstName = _FirstName  
    def FirstName_= (value:String) = _FirstName = value

    def this( FirstName: String, LastName: String ){
        this()
        this._FirstName = FirstName
        this.LastName = LastName 
    }

}

Which is somewhat ugly and inelegant, and removes most of the nice reasons I was using scala in the first place.

Is there a better way of doing this?

tl;dr How to override getters/setters for members defined in the default constructor without making the code ugly or changing the public interface?

3
I'm not sure if your example is real or made up for the sake of this discussion, but why'd anyone in their right mind open up the guts of a class to the world using public var? - Abhijit Sarkar
This was in the early days as I was first learning Scala. Absolutely everything about this question and what I was trying to do was stupid. - James Davies

3 Answers

10
votes

Did you consider using an companion object?

class Person private (f: String, l: String ) {
   var FirstName = f
   var LastName = l
}

object Person {
   def apply(FirstName:String, LastName:String) = 
       new Person(FirstName, LastName) 
}
5
votes

If you're not already using implicit conversions to create the arguments, you can do something like this:

def validateName(s: String) = {
  if (s.length>0 && s(0).isUpper) s
  else throw new IllegalArgumentException(s+" is not a name!")
}

object Example {
  private[Example] class ValidatedName(val s: String) { }
  class Person(var firstName: ValidatedName, var lastName: String) { }
  implicit def string2valid(s: String) = new ValidatedName(validateName(s))
  implicit def valid2string(v: ValidatedName) = v.s
}

scala> new Example.Person("Joe","Schmoe")
res17: Example.Person = Example$Person@51887dd5

scala> new Example.Person("ee","cummings")
java.lang.IllegalArgumentException: ee is not a name!

It's not binary compatible, but it is source compatible (again, if the names weren't already relying upon implicit conversions).

Another slightly longer possibility is to create a stealth ancestor:

class CheckedPerson(private var first: String, var lastName: String) {
  def firstName = first
  def firstName_=(s: String) { first = validateName(s) }
}
class Person(firstName: String, lastName: String) extends
  CheckedPerson(validateName(firstName),lastName) { }

for which I'm not sure about binary compatibility, but will definitely give source compatibility.

4
votes

No. There is currently no way to do that, it's currently not the focus of research.

It is one of my major pet peeves I have with the language: There is no sensible way to combine constructor arguments and self-defined getter/setter methods.

If you're not happy with the functionality class Person( var FirstName: String, var LastName: String ) provides, it basically means "back to Java's verboseness".