[Tutor] Grokking immutability

Danny Yoo dyoo@hkn.eecs.berkeley.edu
Fri Dec 13 05:32:02 2002


On Fri, 13 Dec 2002, Terry Carroll wrote:

> Beginner here.  I don't really get immutability.  Can someone explain
> its practical effect in small words?

Immutability affects many of the primitive data types that we know about.
For example, all the numbers in Python are immutable: if I have a number
42, and you also have that same number number 42, there's nothing I can
do, be it multiplying, or incrementing, or dividing, or bitshifting:
nothing affects your 42.

###
x = 42
y = x
x = x * 2          ## ... no effect on y
x += 1             ## ... still no effect...
###

This may seem obvious for numbers, but this idea of an immutable thing
also extends to strings in Python.  And Python's string immutability isn't
so obvious for people who've programmed in traditional languages like C:

###
x = "forty two"
y = x
x[2] = 'o'        ## ... won't work in Python, but would conceptually work
                  ## in the C language
###

So, the more common data things that we work in the language --- numbers
and strings --- are both immutable: if two variable name refer to the same
thing, if we start fiddling with one variable name, nothing appears to
reflect in the other variable.



> For example, I understand tuples are immutable, lists aren't.

Tuples are immutable in the sense that once we build up the container, we
can't do anything to affect it's physical form.

###
>>> x = ('this', 'is', 42)
>>> x[0] = 'that'
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: object doesn't support item assignment
###

In this sense, they're behaving just like strings and numbers: we just
don't have any operations that we can do to bend them!



But there's a simplification involved whenever we say a tuple is
immutable: it's only truly immutable, through and through, if the values
that are contained in it are themselves immutable.


If we stick a list in a tuple, for example, we've still got a shallow
sense of immutability,

###
>>> x = ('this', 'is', 'a', 'tuple', 'with', 'a', ['list'])
>>> x[0] = 'that'
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: object doesn't support item assignment
###

but the inner parts can still definitely change: the list that's inside
has contaminated the purity of that tuple's immutability!

###
>>> x[-1].insert(0, 'happy')
>>> x[-1][1] = 42
>>> x
('this', 'is', 'a', 'tuple', 'with', 'a', ['happy', 42])
###

So when we say tuples are "immutable", there's usually the caveat that
everything that's in the tuple is being assumed to be immutable as well.
Just something to make things more confusing... *grin*



> Okay, but I can do this:
>
>   t1 = ("x", "y")
>   t1 = ("x", "y", "z")
>   print "t1: ", t1, type(t1)
>
>  t1:  ('x', 'y', 'z') <type 'tuple'>
>
> Now, I understand that, technically, there is no variable t1 being
> modified; that a new tuple with "x", "y", and "z" is being created, and
> the name "t1" is now associated with that new tuple, and the old one can
> be garbage-collected.  But as a practical matter, it amounts to the same
> thing, no?


It might help to think of t1 as a honking big arrow pointed to some boxy
looking thing:

t1  ---------------> +-------------+
                     |  "x" |  "y" |
                     |      |      |
                     +-------------+

(This is a simplification: the 'x' and 'y' actually should be living
outside the boxes, but we'll ignore that for the moment...  *grin*)


What immutability is saying is that that box is made of concrete, and that
the values that are in there are physically glued in place: nothing we can
do to the tuple will let us plug in "w" anywhere in there, or make the box
larger.

The reason immutability matters is that it's very possible to make two
arrows to a single tuple:

###
t1 = ("x", "y")
t2 = t1
###

t1  ---------------> +-------------+
                     |  "x" |  "y" |
t2  ---------------> |      |      |
                     +-------------+

And what an immutable thing guarantess is that we can have absolute
certainly that if we pass t1 to some function, nothing can be done to make
t2 look different.  As long as t2 points to the same box, we're pretty
sure it stays as ("x", "y") throughout our program.


But immutability is a separate issue from being able to rebind t1 to a new
name:


t1  -------+         +-------------+
           |         |  "x" |  "y" |  <----------- t2
           |         |      |      |
           |         +-------------+
           |
           |         +-------------+-----+
           +------>  |  "x" |  "y" |  "z"|
                     |      |      |     |
                     +-------------+-----+


Immutability is more about the permanence of the structures that we play
with: it has nothing to do with us being able to toss them in the garbage
can.  Immutable structures are awesomely indestructible, but they're not
glued to our hands.



> Specifically, why bother to add the concept of immutability to the
> language?  Or, on a more specific level, why is there a tuple type in
> the language, when the list can pretty much do everything a tuple can?

One big reason is because immutability as a mathematical concept makes
sense for numbers, and can be extended to strings.  My concept of Pi can't
be redefined by any legislature.  My guess is that the designers of Python
wanted to bring immutability to a very simple container structure for
symmetry's sake.  And math is fraught with tuples!  A set of coordinates
on the x-y plane:

###
>>> x = 42
>>> y = 24
>>> p = x,y
>>> p2 = p
>>> p
(42, 24)
>>> p2
(42, 24)
###

is just as mathy an object as a number.  Python's core designer's a
mathematician, so that's probably also a contributing factor to having a
separate tuple type.


But a major one is that tuples can be used as dictionary keys:

###
>>> sparse_matrix = {}
>>> for i in range(5):
...     sparse_matrix[i,i] = 1
...
>>> sparse_matrix
{(1, 1): 1, (3, 3): 1, (0, 0): 1, (4, 4): 1, (2, 2): 1}
###

Dictionaries don't work well unless the keys are guaranteed not to melt or
or break or bend or shatter.  A doorknob lock is useless if the key's made
of butter.

###
>>> key = 42
>>> deep_thought = { key : "answer to life" }
###

We want to make sure we can get at the values in our dictionaries, no
matter how we construct the key:

###
>>> key = int("4") * 10 + int("2")
>>> deep_thought[key]
'answer to life'
###

42 is 42, no matter how we get there.  Same way with tuples:

###
>>> deep_thought[4,2] = 'another answer'
>>> import math
>>> math.pi, math.e
(3.1415926535897931, 2.7182818284590451)
>>> key = (int(str(math.pi)[3]), int(str(math.e)[0]))
>>> deep_thought[key]
'another answer'
###


> Oh, and while you're at it, what's the Meaning of Life?

*grin*



Best of wishes to you!