Previous ... Next

Keep control: Modifiers, kinds of references and objects

(1/5)Kinds of objects

In object oriented languages, objects can form complex networks of dependencies by referring to each other using their fields. The Reachable Object Graph (ROG) of a given object is the set of all objects reachable from it, including itself.

An object is mutated if a field of an object in its ROG is updated . A mutable object is an object that can be mutated. The 42 type system is able to ensure that some objects can not be mutated. We call those immutable objects. All instances of «» are immutable. The ROG of a «» object contains the «» itself and the «» coordinates. Fields «» and «» can not be updated and the «» objects are immutable. Immutable objects are very easy to use but may be inadequate when representing entities whose state can change across time.

Let's now define a mutable «», whose location can be updated:

There are two new keywords used here:
  • The «» field is «». This is called a variable field: a field that can be updated by calling a setter. Non-variable fields can not be updated.
  • «» is a «».
    We have seen a «» already, and we have seen methods such as «» and «» showing no modifier; they implicitly have the default modifier «». Similarly, whenever a type does not specify a modifier, it has the default modifier «».
    «» methods can mutate the «» object. If you have experience with C++ you can see the contrast with const methods. Immutable (default) methods works only on immutable «» objects. Later, we will see much more about modifiers.

As you see, we are using the «» method from before. Also notice that we are calling the setter «» without providing the parameter name. While this is usual in other languages, in 42 parameters are selected by name. Sometimes writing down all the parameter names can get tedious. If the first parameter is called «», we can omit it: Writing «» is equivalent to writing «». This works also for methods with multiple parameters, if the first one is called «». Writing «» is equivalent to writing «».

We can use «» by writing, for example:

(2/5)Interaction between mutable and immutable

We now explore some interaction between mutable and immutable objects.

Here we use «» to denote a mutable list of points. Note the absence of «»; this is conceptually similar to a «» in C++ or «» in Java. To contrast, the declaration «» is similar to «» in C++ or «» in Java, for an opportune «» class. «» references always refer to mutable objects. «» references always refer to immutable objects. Fields can be declared «» independently from their modifier: In the code above, you can see that «» is a «» field of «» type. On the other hand, «» is a non-«» field of «» type.

The method «» first uses the «» setter method to update the «» field with the «» leftmost element of the field «». By the way, collections in «» are primarily designed to store and retrieve immutable objects; later we will show also how to manipulate mutable ones.
The method then uses the «» exposer method and the «» method to mutate the list of points. Both exposers and getters provide access to the value of a field; exposers are used to access the values of mutable fields. Exposers should be used with care: long term handling of references to (parts of) a mutable object could cause spooky action at a distance.
In general, methods starting with # should be used with care.

This code models an animal following a path. It can be used like this:

In this code the first dog goes to 12: 20. The second dog goes to 0: 0. This code involves a mutable animal with a mutable field. This is often a terrible idea, since its behaviour may depend on aliasing: what happens if two dogs follow the same path?
The first dog moves and consumes the path for the second one as well. That is, the first goes to 12: 20 and the second goes to 1: 2. This is because «» is deeply mutable : a mutable object with mutable fields. An amazing amount of bugs is caused by deep mutability. Note that we are using the exposer method «» in a safe pattern: it is only called over «», and the returned reference does not leak out of the method. The problem here arises since the object was shared to begin with.

(3/5)Capsules: Keep aliasing graphs untangled

In 42 we can change «» to prevent this aliasing issue.

Now we use the modifier «»; this requires the value of the field to be encapsulated. Immutable objects are also encapsulated since they do not influence aliasing, so they are free from aliasing limitations. The «» modifier forces the users to provide «» values, and ensures that instances of «» have encapsulated state; that is, the values of all fields in an «» are encapsulated.
A mutable object with encapsulated state can only be mutated by calling one of its methods. This allows for the same kind of local reasoning as if all of the fields were immutable.

A capsule mutator is a class method whose first parameter is the capsule field as «». It is a way to mutate the value of a capsule field without exposing it. «» recognizes only the methods annotated with «» as capsule mutators. Those methods can then be safely accessed as instance methods with the same name.
The annotation is called «» because capsule mutators also clear all the object based caches. Automatic caching is one of the coolest features of 42 and we will explore it later in this tutorial.

Note that we cannot have «» exposers («») for capsule fields: other code could keep those references and then mutate the ROG of the field, breaking local reasoning about Animals. With «», we are forced to initialize two animals using different paths:

where the «» local binding is «»; it can satisfy the Animal.path requirement, but it can be used only once. «» has to use another capsule. It is okay to just write the object creation in place as is done. Alternatively, lists offer a «» method, so in this case we could write «»

(4/5)Handle mutability

Immutable objects of any class

How can we get an immutable «»? When an «» is created using «» we create a «». In most cases you can promote such reference to immutable/capsule; just make the type of the local binding explicit. The type system will take care of the rest. If a reference can not be safely promoted to immutable/capsule, you may have to clone some data or to refactor your code.

//dog2.move()  //ill-typed, requires a mut Animal
We will not explain in this tutorial the exact rules for promotion, but the main idea is that if the initialization expression uses local bindings in a controlled/safe way, then promotion can be applied. For example, a mutable expression using only capsule or immutable references can be promoted to capsule or immutable, as we prefer.

lent and read

We have seen immutable, mutable, capsule and class. The are still two modifiers: «» and «». They are hygienic references: they can be read but can not be stored in mutable/capsule/immutable fields. «» is an hygienic mutable reference, allowing mutation but not long term storage. «» is an hygienic read-only reference.

A method with a single mut parameter can still be called using a lent reference in place of it. «» is the common supertype of «»,«», «» and «». In general, we can use «» when we do not care about the mutability of an object. For example, we could add to «»

This method can be called on both mutable and immutable animals:

(5/5) Summary

Kinds of classes, summary

  • immutable classes: have only immutable fields. It is useful to model mathematical concepts. It is easy to reason about code using immutable classes, but some properties of real objects can be better modelled with state mutation.
  • shallow mutable classes: have only (variable) fields of immutable or capsule type (or class, as we will see later). Reasoning with shallow mutable classes is near as easy as reasoning with immutable ones, and often more natural.
  • deep mutable classes: have mutable fields. Reasoning with deep mutable classes can be very hard.

Modifiers: summary

  • immutable: the default. When you omit the modifier, you mean immutable. An immutable reference points to an object that is never changing. Its whole reachable object graph never changes and is immutable as well.
  • mutable: A mutable reference behaves like a normal reference in Java, C#, C++ , Python and many other languages. Mutable references require mutable objects and allow mutating the referred object.
  • capsule: capsule references are used only once and they guarantee that the whole reachable object graph is reachable only through that capsule reference. Capsule references provide a structured way to reason over deep mutable objects. Fields can be annotated capsule, the meaning is that they need to be initialized/updated with capsule variables. We will discuss more about capsule fields and how they differ from capsule references later.
  • read: A readable reference can not be used to mutate the referred object; but other mutable references pointing to the same object can mutate it. Read references can point to both mutable and immutable objects. It is easy to be confused between read and immutable references. As a rule of thumb, if you are in doubt about whether to use an immutable or a readable reference, you probably want an immutable reference.
  • lent: a hygienic mutable reference allowing mutation but not storage. Lent and read are useful to handle in controlled way the state of deep mutable classes; moreover using lent and read on method parameters allows to make explicit what are the method intentions and requirements.
  • class: class references denote the class object, on methods the meaning is the same of static methods in many languages, but it can consistently be used on parameters/local variables/fields to encode behaviours similar to dependency injection.

Keep control, summary

  • mutable: mutable objects can be freely aliased and mutated. They allow for a liberal programming style like we can find in Java/C++/C# or Python. They can be referred to by capsule, mutable, lent and read references.
  • immutable: immutable objects can be obtained by promoting instances of mutable classes. They can be referred to only by immutable and read references.
  • class: class objects can be accessed from anywhere by using the corresponding class name; It is also possible to store them into (class) local binding. Some programmers found the fact that class objects are instances of themselves deeply concerning or disturbing, while for others it is just a good story to tell to break the ice at parties.

      Previous ... Next