views:

183

answers:

6

I do not know what to call my "setters" on immutable objects?

For a mutable object Person, setters work like this:

class Person(private var _name: String) {
  def name = "Mr " + _name
  def name_=(newName: String) {
    _name = newName
  }
}

val p = new Person("Olle")
println("Hi "+ p.name)
p.name = "Pelle"
println("Hi "+ p.name)

This is all well and good, but what if Person is immutable?

class Person(private val _name: String) {
  def name = "Mr " + _name
  def whatHereName(newName: String): Person = new Person(newName)
}

val p = new Person("Olle")
println("Hi "+ p.name)
val p2 = p.whatHereName("Pelle")
println("Hi "+ p2.name)

What should whatHereName be called?

EDIT: I need to put stuff in the "setter" method, like this:

class Person(private val _name: String) {
  def name = "Mr " + _name
  def whatHereName(newName: String): Person = {
    if(name.length > 3)
      new Person(newName.capitalize)
    else
      throw new Exception("Invalid new name")
  }
}

The real code is much bigger than this, so a simple call to the copy method will not do.

EDIT 2:

Since there are so many comments on my faked example (that it is incorrect) I better give you the link to the real class (Avatar) http://bit.ly/djQVfm

The "setter" methods I don't know what to call are updateStrength, updateWisdom ... but I will probably change that to withStrength soon..

+7  A: 

I like the jodatime way. that would be withName.

val p = new Person("Olle")
val p2 = p.withName("kalle");

more jodatime examples: http://joda-time.sourceforge.net/

Mikael Sundberg
Cool thing. It's just that "with" makes me think about the with keyword used for mixin.
olle kullberg
They all vote for "with", so "with" it is.
olle kullberg
+9  A: 

Scala case classes have autogenerated method copy for this purpose. It's used like this:

val p2 = p.copy(name = "Pelle")

Oleg Galako
@Oleg The "setter" method should probably use the copy method internally. But the "setter" method might do more than just set the value, it could include logic that validates the new name.
olle kullberg
You asked about convention, so existing widely used method is a good example of how it can be done. You can write your own copy method with your own validation and it will look familiar to those who use case classes.
Oleg Galako
@Oleg I see you point.
olle kullberg
+1  A: 

Adding to Oleg answer, you would write the class like this:

case class Person(name: String) //That's all!

You would use it like this:

val p = Person("Olle") // No "new" necessary
println("Hi" + p.name)
val p2 = p.copy(name="Pelle")
println("Hi" + p2.name)    

Using the copy method like above is possible, but in your simple case I would just use:

val p2 = Person("Pelle")

The copy methods show their strengths if you have classes like:

case class Person(name: String, age: Int, email: EMail, pets: List[Pet] = List())
val joe = Person("Joe", 41, EMail("[email protected]"))
val joeAfterHisNextBirthday = joe.copy(age=42)
soc
Thanks @soc, but this is not really what I am looking for. My example above is a much simplified version of the real problem. In my real example I do need a "setter"-like method, that wraps the copy-method. And the question is: What should it be called.
olle kullberg
+1  A: 

As for now I am using update<Field> name convention for all "setter"-like methods on immutable objects.

I can not use set<Field> since it reminds too much about the mutable setters in Java.

How do you feel about using update<Field> for all methods that returns a new instance of the same identity as the current instance?

olle kullberg
That sounds ok, although I prefer with<Field> a tiny bit.
soc
If you're following other Scala conventions (especially with the collections library), then you should prefer 'updated' over 'update'. 'update' still suggests a mutating operation. Having said all that, I still favour 'with'
Kevin Wright
Well then "with" it is! Thank you guys.
olle kullberg
+2  A: 

If you need to perform validation, etc. when 'modifying' a field, then why should this be any different from validation when you first create the object?

In this case, you can then put the necessary validation/error-throwing logic in the constructor of a case class, and this will be used whenever a new instance is created via the copy method.

Kevin Wright
You are correct @Kevin, but the example above is not at all what my real code looks like (sorry about that). In my code all "setters" are not doing validation, they are calling a common utility method that takes care of instance creation. In my code i can not use copy since I have to mixin traits.
olle kullberg
@Kevin I updated the question with a link to the real code. It is a complex immutable object. I you have time to take a look I would be glad.
olle kullberg
Those mixins are just going to cause you more pain as time goes by. You should absolutely be favouring composition instead of inheritance here; Your code seems to be saying that an Avatar is-a Profession and is-a Race, this is false, an Avatar has-a Profession and has-a Race. Encode the fundamental concepts correctly and everything else should then fall into place much more easily and naturally.
Kevin Wright
@Kevin For the mutable object situation it is quite nice to have the Avatar "being" a creature (i.e. being a Dwarf) and "being" a professional (i.e. being a Thief), see http://bit.ly/bwDO1B. The reason Java favors composition for this case is IMHO the lack of multiple inheritance. For the immutable object situation this is, as you say, not very easy to do with traits in Scala. So the solution based on composition looks tempting. Or? Let me know if you reject the mutable solution too..
olle kullberg
The basic idea doesn't just come from lack of multiple inheritance (though this was doubtless a motivating force), you can see this by looking how design style has moved to favouring composition in C++. Current thinking is to reduce coupling as much as possible. This helps with unit testing, it also works very nicely with immutability - allowing composed immutable objects to be easily updated via something like the `copy` method available on case classes. And yes, I'd also advise you to favour immutable objects :)
Kevin Wright
@Kevin I am not sure that composition will reduce coupling very much for my code. If I use composition in my code, then Avatar would "have" a profession and "have" a race. But the profession class needs to have a reference back to Avatar to access the fields. Is this "double dependency" still called composition? In any case, Profession can not be tested in isolation.
olle kullberg
@Kevin Multiple inheritance does not work very well in C++, C++ does not have a good solution for the diamond problem. Scala is a different beast, so I think multiple inheritance can be used (if it feels "right" for the actual problem). My aim for the example I am working on is to explore how multiple inheritance works in Scala.
olle kullberg
From what I can see, all the coupling is via strength/charisma/etc. Why not pull these qualities out into a dedicated (immutable) `Stats` class that can be passed in and out of functions.
Kevin Wright
@Kevin That is a good idea!
olle kullberg
+3  A: 

You could define a single method for that. Either copy, or, in case it is already a case class, with:

class Person(private val _name: String) {
  def name = "Mr " + _name
  def copy(name: String = _name): Person = new Person(name)
}

EDIT

The copy method on the linked example should look like this:

// Setters
def copy(strength: Int = features.strength,
         wisdom: Int = features.wisdom,
         charisma: Int = features.charisma,
         hitpoints: Int = features.hitpoints): Avatar = {
  if (hitpoints != features.hitpoints)
    println("setHitpoints() old value: " + features.hitpoints + ", new value: " + hitpoints)

  if (hitpoints > 0) 
    updateCreatureFeature(
      features.copy(strength = strength,
                    wisdom = wisdom,
                    charisma = charisma,
                    hitpoints = hitpoints))
  else
    throw new DeathException(name + " died!")

  // Alternate coding (depend on thrown exception on "check"):
  // check(strength, wisdom, charisma, hitpoints)
  // updateCreateFeature(...)
}
Daniel
@Daniel I updated the question with a link to the real code. It is a complex immutable object.
olle kullberg
@olle see edit.
Daniel
@Daniel Thank you for helping me out. I see now that the copy strategy works for my code.
olle kullberg