Case class, immutability and getter/setter method

573 views
Skip to first unread message

Ugo Matrangolo

unread,
Sep 9, 2011, 7:40:47 PM9/9/11
to scala...@googlegroups.com
Hi,

I have recently started to play with Scala and I came across case classes and pattern matching. 

I came from a Java world where I was a big fan of immutability so I started soon to look on how to achieve the same effect in Scala.

Case classes seems to me the best answer but there it is still something that I would like to clarify.

Writing an immutable bean in Java requires all fields final. This means that, usually, a setter method does not modify the internal state of the object but it creates a new version of the same object with the changed field.

I was trying to simulate the same effect in Scala:

The first approach would be :

scala> case class Person(name: String)
defined class Person

scala> val p1 = new Person("Mike")
p1: Person = Person(Mike)

scala> p1.name = "Brian"
<console>:10: error: reassignment to val
       p1.name = "Brian"
               ^
scala> val p2 = p1.copy(name = "Brian")
p2: Person = Person(Brian)
 
This seems the mainstream approach. 

I was wondering about this one:

scala> case class Person(private val _name: String) {
     | def name = _name
     | def name_= (_name: String) = this.copy(_name = _name)
     | }
defined class Person

scala> val p1 = Person("Mike")
p1: Person = Person(Mike)

scala> p1.name = "Brian"

scala> p1.name
res3: String = Mike

scala> val p2 = p1.name = "Brian"
p2: Person = Person(Brian)

scala> p2.name
res4: String = Brian

Basically, here I'm redefining the _= operator to make a copy of the current object with the changed field. I think that this could be a better approach compared to explicitly invoking the copy() method.

Any comments ??

Regards,
Ugo.

Tony Morris

unread,
Sep 9, 2011, 7:51:25 PM9/9/11
to Ugo Matrangolo, scala...@googlegroups.com

You've almost invented lenses. It is possible to emulate imperative programming, without the nasty bits, using lenses; I think it is called := in scalaz.

On 10/09/2011 9:40 AM, "Ugo Matrangolo" <ugo.mat...@gmail.com> wrote:
> Hi,
>
> I have recently started to play with Scala and I came across case classes
> and pattern matching.
>
> I came from a Java world where I was a big fan of immutability so I started
> soon to look on how to achieve the same effect in Scala.
>
> Case classes seems to me the best answer but there it is still something
> that I would like to clarify.
>
> Writing an immutable bean in Java requires all fields final. This means
> that, usually, a setter method does not modify the internal state of the
> object but it creates a new version of the same object with the changed
> field.
>
> I was trying to simulate the same effect in Scala:
>
> The first approach would be :
>
> *scala> case class Person(name: String)*
> *defined class Person*
> *
> *
> *scala> val p1 = new Person("Mike")*
> *p1: Person = Person(Mike)*
> *
> *
> *scala> p1.name = "Brian"*
> *<console>:10: error: reassignment to val*
> * p1.name = "Brian"*
> * ^*
> *scala> val p2 = p1.copy(name = "Brian")*
> *p2: Person = Person(Brian)*

>
> This seems the mainstream approach.
>
> I was wondering about this one:
>
> *scala> case class Person(private val _name: String) {*
> * | def name = _name*
> * | def name_= (_name: String) = this.copy(_name = _name)*
> * | }*
> *defined class Person*
> *
> *
> *scala> val p1 = Person("Mike")*
> *p1: Person = Person(Mike)*
> *
> *
> *scala> p1.name = "Brian"*
> *
> *
> *scala> p1.name*
> *res3: String = Mike*
> *
> *
> *scala> val p2 = p1.name = "Brian"*
> *p2: Person = Person(Brian)*
> *
> *
> *scala> p2.name*
> *res4: String = Brian*

Tony Morris

unread,
Sep 9, 2011, 8:46:40 PM9/9/11
to Ugo Matrangolo, scala...@googlegroups.com

Lars Hupel

unread,
Sep 10, 2011, 4:58:27 AM9/10/11
to scala...@googlegroups.com
And if you want your lenses to be auto-generated, try
<https://github.com/gseitz/Lensed>.

Ugo Matrangolo

unread,
Sep 10, 2011, 6:39:39 AM9/10/11
to tmo...@tmorris.net, hu...@in.tum.de, scala...@googlegroups.com
Thank you.

Seems that I have to learn Scalaz as well :)

Cheers,
Ugo.

Lars Hupel

unread,
Sep 10, 2011, 6:43:52 AM9/10/11
to scala...@googlegroups.com
> Seems that I have to learn Scalaz as well :)

Don't worry. The amount of Scalaz needed for Lenses is quite small and
you can safely ignore the other bits. The "Lensed" plugin should do
everything for you. Just have a look at the examples there. I think the
most difficult part is to figure out how to factor out your case classes
into a different sub-project ;)

Adrian

unread,
Sep 10, 2011, 7:26:42 AM9/10/11
to scala-user
Nice trail Ugo!

I think that overriding the assignment operator here gives the wrong
impression; the code

p2 = p1.name = "ade"

looks to me like we're updating the 'name' field of p1 *and* then
setting p2 to point to p1. The actual intent - that p1 is not modified
and that p2 is a new object, it not clear. For me, '=' is all about,
well, assignment - changing a field; that said, I'm coming from a long
Java tradition of mutability: anyone coming from a more functional /
immutable object background may find your assignment more intuitive. I
suspect though that unsuspecting programmers using your '=' will end
up doing

p1.name = "Adrian"

and then getting really, really perplexed when p1 doesn't get updated.

The copy approach is clearer in it's intent, so, while it's more
characters-per-intent, it also provides way more intent-per-character,
so I'd plump for that for now.

The link to http://stackoverflow.com/questions/3900307/cleaner-way-to-update-nested-structures
(Thanks Tony!) does make a clean case as to when and how copy() hit's
its limits for nested structures, so methinks that the Lens approach
is the way to go if nested structures are required.

Russ P.

unread,
Sep 11, 2011, 4:29:35 AM9/11/11
to scala-user
I have immutable classes for aircraft position, velocity, and state.
Here is my position class (most of it):

case class Position(x: Scalar=0, y: Scalar=0, alt: Scalar=0) { // 3D
position

override def clone = Position(x, y, alt)

def apply(x: Scalar=x, y: Scalar=y, alt: Scalar=alt) = Position(x,
y, alt)

def to2D = Position2D(x, y)

def + (p: Position) = Position(x + p.x, y + p.y, alt + p.alt)
def - (p: Position) = Position(x - p.x, y - p.y, alt - p.alt)
}

As you can see, this class is very simple, and I use apply with
default values to return a new object with any or all fields modified.
Here is an example usage:

val pos1 = Position(100 * nmi, 110 * nmi, 35 * kft)
val pos2 = pos1(alt = 33 * kft)

This seems to work reasonably well for me, but is there a better or
simpler way? Comments welcome.

In case you're wondering, "Scalar" is my Scalar class, which is
available from http://russp.us/scalar-scala.htm.

--Russ P.


On Sep 9, 4:40 pm, Ugo Matrangolo <ugo.matrang...@gmail.com> wrote:
> Hi,
>
> I have recently started to play with Scala and I came across case classes
> and pattern matching.
>
> I came from a Java world where I was a big fan of immutability so I started
> soon to look on how to achieve the same effect in Scala.
>
> Case classes seems to me the best answer but there it is still something
> that I would like to clarify.
>
> Writing an immutable bean in Java requires all fields final. This means
> that, usually, a setter method does not modify the internal state of the
> object but it creates a new version of the same object with the changed
> field.
>
> I was trying to simulate the same effect in Scala:
>
> The first approach would be :
>
> *scala> case class Person(name: String)*
> *defined class Person*
> *
> *
> *scala> val p1 = new Person("Mike")*
> *p1: Person = Person(Mike)*
> *
> *
> *scala> p1.name = "Brian"*
> *<console>:10: error: reassignment to val*
> *       p1.name = "Brian"*
> *               ^*
> *scala> val p2 = p1.copy(name = "Brian")*
> *p2: Person = Person(Brian)*
>
> This seems the mainstream approach.
>
> I was wondering about this one:
>
> *scala> case class Person(private val _name: String) {*
> *     | def name = _name*
> *     | def name_= (_name: String) = this.copy(_name = _name)*
> *     | }*
> *defined class Person*
> *
> *
> *scala> val p1 = Person("Mike")*
> *p1: Person = Person(Mike)*
> *
> *
> *scala> p1.name = "Brian"*
> *
> *
> *scala> p1.name*
> *res3: String = Mike*
> *
> *
> *scala> val p2 = p1.name = "Brian"*
> *p2: Person = Person(Brian)*
> *
> *
> *scala> p2.name*
> *res4: String = Brian*
>

Roland Kuhn

unread,
Sep 11, 2011, 4:48:39 AM9/11/11
to Russ P., scala-user
Hi Russ,

On Sep 11, 2011, at 10:29 , Russ P. wrote:

> I have immutable classes for aircraft position, velocity, and state.
> Here is my position class (most of it):
>
> case class Position(x: Scalar=0, y: Scalar=0, alt: Scalar=0) { // 3D
> position
>
> override def clone = Position(x, y, alt)
>

Since you override clone() you probably have a use case for it. I wonder why you don’t just return “this”, though, given that your class is immutable. If you do need a new object, you may save some characters (and be unaffected by refactorings) by just using copy().

> def apply(x: Scalar=x, y: Scalar=y, alt: Scalar=alt) = Position(x,
> y, alt)
>
> def to2D = Position2D(x, y)
>
> def + (p: Position) = Position(x + p.x, y + p.y, alt + p.alt)
> def - (p: Position) = Position(x - p.x, y - p.y, alt - p.alt)
> }
>
> As you can see, this class is very simple, and I use apply with
> default values to return a new object with any or all fields modified.
> Here is an example usage:
>
> val pos1 = Position(100 * nmi, 110 * nmi, 35 * kft)
> val pos2 = pos1(alt = 33 * kft)
>
> This seems to work reasonably well for me, but is there a better or
> simpler way? Comments welcome.
>

It looks like the obvious solution. Whether to use apply() instead of copy() for the mutators is a matter of taste, I guess (I have a slight tendency to spend extra characters for indicating copying at the use site).

Roland Kuhn
Typesafe – Enterprise-Grade Scala from the Experts
twitter: @rolandkuhn

Ken McDonald

unread,
Sep 11, 2011, 12:49:38 PM9/11/11
to scala...@googlegroups.com
One possible drawback with your approach is that "assigning to" multiple fields generates multiple instances of the case class. Usually not important, but maybe so in high-performance applications. Using "copy" generates (I assume) just a single new instance.

Ken

Russ P.

unread,
Sep 11, 2011, 2:13:19 PM9/11/11
to scala-user
Thanks for the feedback, Roland. Yes, now that you mention it, I guess
having clone make a copy is pointless for an immutable class. I might
as well just return "this". Better yet, I could just get rid of clone
altogether and use assignment at the call site. That would be
equivalent for an immutable class, wouldn't it?

By the way, for mutable classes, I've never really understood the
difference between copy and clone. Can someone clarify that?

--Russ P.

HamsterofDeath

unread,
Sep 11, 2011, 2:29:03 PM9/11/11
to scala...@googlegroups.com
did not read the question, but i will attempt to answer it anyway:
the copy method of case classes allows creating a modified copy.
java.lang.Object.clone just returns an exact copy

Am 11.09.2011 20:13, schrieb Russ P.:
> Thanks for the feedback, Roland. Yes, now that you mention it, I guess
> having clone make a copy is pointless for an immutable class. I might
> as well just return "this". Better yet, I could just get rid of clone
> altogether and use assignment at the call site. That would be
> equivalent for an immutable class, wouldn't it?
>
> By the way, for mutable classes, I've never really understood the
> difference between copy and clone. Can someone clarify that?
>
> --Russ P.
>
>
> On Sep 11, 1:48 am, Roland Kuhn <goo...@rkuhn.info> wrote:
>> Hi Russ,
>>
>> On Sep 11, 2011, at 10:29 , Russ P. wrote:
>>
>>> I have immutable classes for aircraft position, velocity, and state.
>>> Here is my position class (most of it):
>>> case class Position(x: Scalar=0, y: Scalar=0, alt: Scalar=0) { // 3D
>>> position
>>> override def clone = Position(x, y, alt)

>> Since you override clone() you probably have a use case for it. I wonder why you don�t just return �this�, though, given that your class is immutable. If you do need a new object, you may save some characters (and be unaffected by refactorings) by just using copy().


>>
>>
>>
>>> def apply(x: Scalar=x, y: Scalar=y, alt: Scalar=alt) = Position(x,
>>> y, alt)
>>> def to2D = Position2D(x, y)
>>> def + (p: Position) = Position(x + p.x, y + p.y, alt + p.alt)
>>> def - (p: Position) = Position(x - p.x, y - p.y, alt - p.alt)
>>> }
>>> As you can see, this class is very simple, and I use apply with
>>> default values to return a new object with any or all fields modified.
>>> Here is an example usage:
>>> val pos1 = Position(100 * nmi, 110 * nmi, 35 * kft)
>>> val pos2 = pos1(alt = 33 * kft)
>>> This seems to work reasonably well for me, but is there a better or
>>> simpler way? Comments welcome.
>> It looks like the obvious solution. Whether to use apply() instead of copy() for the mutators is a matter of taste, I guess (I have a slight tendency to spend extra characters for indicating copying at the use site).
>>
>> Roland Kuhn

>> Typesafe � Enterprise-Grade Scala from the Experts
>> twitter: @rolandkuhn

Russ P.

unread,
Sep 11, 2011, 3:30:02 PM9/11/11
to scala-user
On Sep 11, 11:29 am, HamsterofDeath <h-s...@gmx.de> wrote:
> did not read the question, but i will attempt to answer it anyway:
> the copy method of case classes allows creating a modified copy.
> java.lang.Object.clone just returns an exact copy

So I reinvented the wheel here. Looks like I can cut a few lines of
code. Thanks for the clarification. For a hamster, you're pretty
sharp.

--Russ P.
Reply all
Reply to author
Forward
0 new messages