15 min read

In this article by Gastón C. Hillar, author of the book, Learning Object-Oriented Programming, you will learn the concept of mutability and immutability in the programming languages such as, Python, C#, and JavaScript.

What’s the difference?

By default, any instance field or attribute works like a variable; therefore we can change their values. When we create an instance of a class that defines many public instance fields, we are creating a mutable object, that is, an object that can change its state.

For example, let’s think about a class named MutableVector3D that represents a mutable 3D vector with three public instance fields: X, Y, and Z. We can create a new MutableVector3D instance and initialize the X, Y, and Z attributes. Then, we can call the Sum method with their delta values for X, Y, and Z as arguments. The delta values specify the difference between the existing value and the new or desired value. So, for example, if we specify a positive value of 20 in the deltaX parameter, it means that we want to add 20 to the X value.

The following lines show pseudocode in a neutral programming language that create a new MutableVector3D instance called myMutableVector, initialized with values for the X, Y, and Z fields. Then, the code calls the Sum method with the delta values for X, Y, and Z as arguments, as shown in the following code:

myMutableVector = new MutableVector3D instance with X = 30, Y = 50 and Z = 70
myMutableVector.Sum(deltaX: 20, deltaY: 30, deltaZ: 15)

The initial values for the myMutableVector field are 30 for X, 50 for Y, and 70 for Z. The Sum method changes the values of all the three fields; therefore, the object state mutates as follows:

  • myMutableVector.X mutates from 30 to 30 + 20 = 50
  • myMutableVector.Y mutates from 50 to 50 + 30 = 80
  • myMutableVector.Z mutates from 70 to 70 + 15 = 85

The values for the myMutableVector field after the call to the Sum method are 50 for X, 80 for Y, and 85 for Z. We can say this method mutated the object’s state; therefore, myMutableVector is a mutable object: an instance of a mutable class.

Mutability is very important in object-oriented programming. In fact, whenever we expose fields and/or properties, we will create a class that will generate mutable instances. However, sometimes a mutable object can become a problem. In certain situations, we want to avoid objects to change their state. For example, when we work with a concurrent code, an object that cannot change its state solves many concurrency problems and avoids potential bugs.

For example, we can create an immutable version of the previous MutableVector3D class to represent an immutable 3D vector. The new ImmutableVector3D class has three read-only properties: X, Y, and Z. Thus, there are only three getter methods without setter methods, and we cannot change the values of the underlying internal fields: m_X, m_Y, and m_Z. We can create a new ImmutableVector3D instance and initialize the underlying internal fields: m_X, m_Y, and m_Z. X, Y, and Z attributes. Then, we can call the Sum method with the delta values for X, Y, and Z as arguments.

The following lines show the pseudocode in a neutral programming language that create a new ImmutableVector3D instance called myImmutableVector, which is initialized with values for X, Y, and Z as arguments. Then, the pseudocode calls the Sum method with the delta values for X, Y, and Z as arguments:

myImmutableVector = new ImmutableVector3D instance with X = 30, Y = 50 and Z = 70
myImmutableSumVector = myImmutableVector.Sum(deltaX: 20, deltaY: 30, deltaZ: 15)

However, this time the Sum method returns a new instance of the ImmutableVector3D class with the X, Y, and Z values initialized to the sum of X, Y, and Z and the delta values for X, Y, and Z. So, myImmutableSumVector is a new ImmutableVector3D instance initialized with X = 50, Y = 80, and Z = 85. The call to the Sum method generated a new instance and didn’t mutate the existing object.

The immutable version adds an overhead as compared with the mutable version because it’s necessary to create a new instance of a class as a result of calling the Sum method. The mutable version just changed the values for the attributes and it wasn’t necessary to generate a new instance. Obviously, the immutable version has a memory and a performance overhead. However, when we work with the concurrent code, it makes sense to pay for the extra overhead to avoid potential issues caused by mutable objects.

Using methods to add behaviors to classes in Python

So far, we have added instance methods to classes and used getter and setter methods combined with decorators to define properties. Now, we want to generate a class to represent the mutable version of a 3D vector.

We will use properties with simple getter and setter methods for x, y, and z. The sum public instance method receives the delta values for x, y, and z and mutates an object, that is, the setter method changes the values of x, y, and z. Here is the initial code of the MutableVector3D class:

class MutableVector3D:
   def __init__(self, x, y, z):
       self.__x = x
       self.__y = y
       self.__z = z
 
   def sum(self, delta_x, delta_y, delta_z):
       self.__x += delta_x
     self.__y += delta_y
       self.__z += delta_z
 
   @property
   def x(self):
       return self.__x
 
   @x.setter
   def x(self, x):
       self.__x = x
 
   @property
   def y(self):
       return self.__y
 
   @y.setter
   def y(self, y):
       self.__y = y
 
   @property
   def z(self):
       return self.__z
 
   @z.setter
   def z(self, z):
       self.__z = z

It’s a very common requirement to generate a 3D vector with all the values initialized to 0, that is, x = 0, y = 0, and z = 0. A 3D vector with these values is known as an origin vector. We can add a class method to the MutableVector3D class named origin_vector to generate a new instance of the class initialized with all the values initialized to 0. It’s necessary to add the @classmethod decorator before the class method header. Instead of receiving self as the first argument, a class method receives the current class; the parameter name is usually named cls. The following code defines the origin_vector class method:

@classmethod
def origin_vector(cls):
   return cls(0, 0, 0)

The preceding method returns a new instance of the current class (cls) with 0 as the initial value for the three elements. The class method receives cls as the only argument; therefore, it will be a parameterless method when we call it because Python includes a class as a parameter under the hood. The following command calls the origin_vector class method to generate a 3D vector, calls the sum method for the generated instance, and prints the values for the three elements:

mutableVector3D = MutableVector3D.origin_vector()
mutableVector3D.sum(5, 10, 15)
print(mutableVector3D.x, mutableVector3D.y, mutableVector3D.z)

Now, we want to generate a class to represent the immutable version of a 3D vector. In this case, we will use read-only properties for x, y, and z. The sum public instance method receives the delta values for x, y, and z and returns a new instance of the same class with the values of x, y, and z initialized with the results of the sum. Here is the code of the ImmutableVector3D class:

class ImmutableVector3D:
   def __init__(self, x, y, z):
       self.__x = x
       self.__y = y
       self.__z = z
 
   def sum(self, delta_x, delta_y, delta_z):
       return type(self)(self.__x + delta_x, self.__y + delta_y, self.__z + delta_z)
 
   @property
   def x(self):
       return self.__x
 
   @property
   def y(self):
       return self.__y
 
   @property
   def z(self):
       return self.__z
 
   @classmethod
   def equal_elements_vector(cls, initial_value):
       return cls(initial_value, initial_value, initial_value)
 
   @classmethod
   def origin_vector(cls):
       return cls.equal_elements_vector(0)

Note that the sum method uses type(self) to generate and return a new instance of the current class. In this case, the origin_vector class method returns the results of calling the equal_elements_vector class method with 0 as an argument. Remember that the cls argument refers to the actual class. The equal_elements_vector class method receives an initial_value argument for all the elements of the 3D vector, creates an instance of the actual class, and initializes all the elements with the received unique value. The origin_vector class method demonstrates how we can call another class method in a class method.

The following command calls the origin_vector class method to generate a 3D vector, calls the sum method for the generated instance, and prints the values for the three elements of the new instance returned by the sum method:

vector0 = ImmutableVector3D.origin_vector()
vector1 = vector0.sum(5, 10, 15)
print(vector1.x, vector1.y, vector1.z)

As explained previously, we can change the values of the private attributes; therefore, the ImmutableVector3D class isn’t 100 percent immutable. However, we are all adults and don’t expect the users of a class with read-only properties to change the values of private attributes hidden under difficult to access names.

Using methods to add behaviors to classes in C#

So far, we have added instance methods to a class in C#, and used getter and setter methods to define properties. Now, we want to generate a class to represent the mutable version of a 3D vector in C#.

We will use auto-implemented properties for X, Y, and Z. The public Sum instance method receives the delta values for X, Y, and Z (deltaX, deltaY, and deltaZ) and mutates the object, that is, the method changes the values of X, Y, and Z. The following shows the initial code of the MutableVector3D class:

class MutableVector3D
{
public double X { get; set; }
public double Y { get; set; }
public double Z { get; set; }
public void Sum(double deltaX, double deltaY, double deltaZ)
{
   this.X += deltaX;
   this.Y += deltaY;
   this.Z += deltaZ;
}
public MutableVector3D(double x, double y, double z)
{
   this.X = x;
   this.Y = y;
   this.Z = z;
}
}

It’s a very common requirement to generate a 3D vector with all the values initialized to 0, that is, X = 0, Y = 0, and Z = 0. A 3D vector with these values is known as an origin vector. We can add a class method to the MutableVector3D class named OriginVector to generate a new instance of the class initialized with all the values initialized to 0. Class methods are also known as static methods in C#. It’s necessary to add the static keyword after the public access modifier before the class method name. The following commands define the OriginVector static method:

public static MutableVector3D OriginVector()
{
  return new MutableVector3D(0, 0, 0);
}

The preceding method returns a new instance of the MutableVector3D class with 0 as the initial value for all the three elements. The following code calls the OriginVector static method to generate a 3D vector, calls the Sum method for the generated instance, and prints the values for all the three elements on the console output:

var mutableVector3D = MutableVector3D.OriginVector();
mutableVector3D.Sum(5, 10, 15);
Console.WriteLine(mutableVector3D.X, mutableVector3D.Y, mutableVector3D.Z)

Now, we want to generate a class to represent the immutable version of a 3D vector. In this case, we will use read-only properties for X, Y, and Z. We will use auto-generated properties with private set. The Sum public instance method receives the delta values for X, Y, and Z (deltaX, deltaY, and deltaZ) and returns a new instance of the same class with the values of X, Y, and Z initialized with the results of the sum. The code for the ImmutableVector3D class is as follows:

class ImmutableVector3D
{
public double X { get; private set; }
public double Y { get; private set; }
public double Z { get; private set; }
public ImmutableVector3D Sum(double deltaX, double deltaY, double deltaZ)
{
   return new ImmutableVector3D (
   this.X + deltaX,
   this.Y + deltaY,
   this.Z + deltaZ);
}
public ImmutableVector3D(double x, double y, double z)
{
   this.X = x;
   this.Y = y;
   this.Z = z;
}
public static ImmutableVector3D EqualElementsVector(double initialValue)
{
   return new ImmutableVector3D(initialValue, initialValue, initialValue);
}
public static ImmutableVector3D OriginVector()
{
   return ImmutableVector3D.EqualElementsVector(0);
}
}

In the new class, the Sum method returns a new instance of the ImmutableVector3D class, that is, the current class. In this case, the OriginVector static method returns the results of calling the EqualElementsVector static method with 0 as an argument. The EqualElementsVector class method receives an initialValue argument for all the elements of the 3D vector, creates an instance of the actual class, and initializes all the elements with the received unique value. The OriginVector static method demonstrates how we can call another static method in a static method.

The following code calls the OriginVector static method to generate a 3D vector, calls the Sum method for the generated instance, and prints all the values for the three elements of the new instance returned by the Sum method on the console output:

var vector0 = ImmutableVector3D.OriginVector();
var vector1 = vector0.Sum(5, 10, 15);
Console.WriteLine(vector1.X, vector1.Y, vector1.Z);

C# doesn’t allow users of the ImmutableVector3D class to change the values of X, Y, and Z properties. The code doesn’t compile if you try to assign a new value to any of these properties. Thus, we can say that the ImmutableVector3D class is 100 percent immutable.

Using methods to add behaviors to constructor functions in JavaScript

So far, we have added methods to a constructor function that produced instance methods in a generated object. In addition, we used getter and setter methods combined with local variables to define properties. Now, we want to generate a constructor function to represent the mutable version of a 3D vector.

We will use properties with simple getter and setter methods for x, y, and z. The sum public instance method receives the delta values for x, y, and z and mutates an object, that is, the method changes the values of x, y, and z. The following code shows the initial code of the MutableVector3D constructor function:

function MutableVector3D(x, y, z) {
var _x = x;
var _y = y;
var _z = z;
Object.defineProperty(this, 'x', {
   get: function(){ return _x; },
   set: function(val){ _x = val; }
});
Object.defineProperty(this, 'y', {
   get: function(){ return _y; },
   set: function(val){ _y = val; }
});
Object.defineProperty(this, 'z', {
   get: function(){ return _z; },
   set: function(val){ _z = val; }
});
this.sum = function(deltaX, deltaY, deltaZ) {
   _x += deltaX;
   _y += deltaY;
   _z += deltaZ;
}
}

It’s a very common requirement to generate a 3D vector with all the values initialized to 0, that is, x = 0, y = 0, and, z = 0. A 3D vector with these values is known as an origin vector. We can add a function to the MutableVector3D constructor function named originVector to generate a new instance of a class with all the values initialized to 0. The following code defines the originVector function:

MutableVector3D.originVector = function() {
return new MutableVector3D(0, 0, 0);
};

The method returns a new instance built in the MutableVector3D constructor function with 0 as the initial value for all the three elements. The following code calls the originVector function to generate a 3D vector, calls the sum method for the generated instance, and prints all the values for all the three elements:

var mutableVector3D = MutableVector3D.originVector();
mutableVector3D.sum(5, 10, 15);
console.log(mutableVector3D.x, mutableVector3D.y, mutableVector3D.z);

Now, we want to generate a constructor function to represent the immutable version of a 3D vector. In this case, we will use read-only properties for x, y, and z. In this case, we will use the ImmutableVector3D.prototype property to define the sum method. The method receives the values of delta for x, y, and z, and returns a new instance with the values of x, y, and z initialized with the results of the sum. The following code shows the ImmutableVector3D constructor function and the additional code that defines all the other methods:

function ImmutableVector3D(x, y, z) {
var _x = x;
var _y = y;
var _z = z;
Object.defineProperty(this, 'x', {
   get: function(){ return _x; }
});
Object.defineProperty(this, 'y', {
   get: function(){ return _y; }
});
Object.defineProperty(this, 'z', {
   get: function(){ return _z; }
});
}
 
ImmutableVector3D.prototype.sum = function(deltaX, deltaY, deltaZ) {
return new ImmutableVector3D(
this.x + deltaX,
this.y + deltaY,
this.z + deltaZ);
};
 
ImmutableVector3D.equalElementsVector = function(initialValue) {
return new ImmutableVector3D(initialValue, initialValue, initialValue);
};
 
ImmutableVector3D.originVector = function() {
return ImmutableVector3D.equalElementsVector(0);
};

Again, note that the preceding code defines the sum method in the ImmutableVector3D.prototype method. This method will be available to all the instances generated in the ImmutableVector3D constructor function. The sum method generates and returns a new instance of ImmutableVector3D. In this case, the originVector method returns the results of calling the equalElementsVector method with 0 as an argument. The equalElementsVector method receives an initialValue argument for all the elements of the 3D vector, creates an instance of the actual class, and initializes all the elements with the received unique value. The originVector method demonstrates how we can call another function defined in the constructor function.

The following code calls the originVector method to generate a 3D vector, calls the sum method for the generated instance, and prints the values for all the three elements of the new instance returned by the sum method:

var vector0 = ImmutableVector3D.originVector();
var vector1 = vector0.sum(5, 10, 15);
console.log(vector1.x, vector1.y, vector1.z);

Summary

In this article, you learned the concept of mutability and immutability in the programming languages such as, Python, C#, and JavaScript and used methods to add behaviors in each of the programming language.

So, what’s next? Continue Learning Object-Oriented Programming with Gastón’s book – find out more here.


LEAVE A REPLY

Please enter your comment!
Please enter your name here