Chapter 7. Object immutability

[Note]Note
Please note that this chapter is incomplete. An update will be published in the future.

Description

By default all objects are immutable in Obix. This means that, once an object is created or a service is initialized, their attributes cannot be set to another value.

If needed, attributes can explicitly be made mutable.

Rules

  1. By default, all attributes defined in an RSE (type, factory or service) are immutable.

    This signifies that:

    • once an object is created, it is not possible to set an attribute to another value.

    • once a service is initialized, it is not possible to set an attribute to another value.

    [Note]Note

    This rule is checked at compile-time and also applies to private attributes in factories and services.

  2. An attribute can explicitly be declared as mutable through the attribute's kind property.

    Keeping all attributes immutable is sometimes inappropriate or impossible. For example, a customer's identifier is typically immutable, but its address can change, therefore it makes sense to make attribute address mutable.

    Please refer to the section called “Attribute property kind for more information about how to use property kind

    Here is a reprint of the example used in that section:

    type person
       attribute first_name type:string kind:constant end            // first name never changes
                                                                     // ('kind:constant' could be omitted, 
                                                                     // because 'constant' is default value for 'kind')
       attribute last_name type:string kind:variable end             // last name can change, e.g. when woman gets maried
       attribute birthdate type:date end                             // birthdate never changes ('kind' defaults to 'constant')
       attribute age type:zero_positive32 kind:readonly_variable end // age changes each year, and depends on birthdate
    end type
  3. If an attribute is mutable then its setable property defines which software elements are allowed to change the attribute.

    Please refer to the section called “Attribute property setable for more information about how to use property setable

    Here is a reprint of the example used in that section:

    type person_2 default_factory:yes
    
       attribute_list type:string
    
          attribute first_name end             // attribute is immutable. it can only be set in the creator.
                                               // (property 'kind' defaults to 'constant', in which case
                                               // property 'setable' defaults to 'creator')
          attribute last_name &
             kind:variable setable:factory end // only scripts in the factory that creates the object can change 'last_name'
    
          attribute city &
             kind:variable setable:all end     // any script can change the value
    
       end attribute_list
    
    end type
[Note]Note

It is important to be aware of the fact that an immutable object can possibly have attribute values which are themselves mutable. In such a case the object's attributes cannot be set to another value, but an attribute of an attribute (i.e. a child-attribute) can still change its value.

Suppose, for example, an immutable list containing 3 customers. Suppose also that attribute address of type customer is mutable. In this case, the customers contained in the list will always remain the same, but any customer's address can change.

It follows from the above that it can sometimes be important to consider whether all attributes of an immutable object are themselves recursively immutable. If they are, then the object is said to be deeply immutable.

Rationale

The advantages of immutable objects are very well explained in chapter Favor immutability in Joshua Bloch's excellent book Effective Java (ISBN 0-201-31005-8).

Joshua Bloch concludes (on page 70): [Classes should be immutable unless there is a very good reason to make them mutable!]

[Note]Note

It is interesting to note that objects are mutable by default in most languages. Java Beans for example impose mutable objects, because the bean specification requires a default no-argument constructor, and a setter-method for each property (attribute).

Obix favors immutability for the following reasons:

  • Immutable objects are simple: Once created with all attributes set to valid values, their state doesn't change anymore and remains valid all the time. There are no state transitions to manage. Therefore, immutable objects are less error-prone.

  • Immutable objects are thread-safe. They don't require synchronization.

    Let's look at 2 examples of what can happen if mutable objects are used in a multi-threaded environment.

    Consider the following naive and oversimplified type for mutable dates:

    type mutable_date
    
       attribute_list type:positive32 kind:variable setable:all
       
          attribute day   check: i_day <= 31   end
          attribute month check: i_month <= 12 end
          attribute year  end
          
       end
    
    end

    Now suppose this type is used to hold the estimated delivery date for a customer's order, and the following happens:

    1. the initial estimated delivery date is set to 31.1.2008 (31th January 2008).

    2. later on the date changes to 15.2.2008 (15th February 2008).

      the software does this by:

      • first changing the day from 31 to 15.

      • then changing the month from 1 to 2.

      the instructions might look like this:

      estimated_delivery_date.day = 15
      estimated_delivery_date.month = 2
    3. at the same time another thread reads the order's estimated delivery date in order to send an email confirmation to the customer.

      the instruction might look like this:

      mail_message = "estimated delivery time: " & customer_order.estimated_delivery_date.to_string

    The problem with this code appears when the second thread reads the date after the day has been changed by the first thread, but before the month is changed. In that case, the date sent to the customer will be 15.1.2008, instead of 15.2.2008!

    Even worse, if the same happened with an initial date of 15.2.2008, and an updated value of 31.3.2008, then the date sent to the customer would be 31.2.2008, which is not only a wrong date, but also an illegal date!

    Another example: An accounting application includes a list containing the history of all accounting records. Task 1 appends a new operation by adding two records, one credit record and one debit record. Task 2 scans the list in order to print a balance sheet. Scanning is done after task 1 added the first record and before adding the second record of the new operation. The terrible consequence is that the balance will be unbalanced, because only the first record of the new operation is included in the scanning!

    [Note]Note

    It is really important to note that problems like the above ones count among the nastiest ones that can appear in practice. The reason is that they typically appear randomly and rarely, and they frequently just produce wrong results, but no run-time errors. This makes them often extremely difficult to detect, repeat and debug. They might stay undetected during the test phase, and afterwards the most bizarre phenomenon can appear randomly once the software is in production, where more users create more data in different environments.

  • Immutable objects can freely be shared. There is no need for creating copies of immutable objects, because their state can simply not change during the whole lifetime of the object. As a nice side-effect, this can sometimes considerably increase performance.

  • If a command's input argument is immutable, there is no risk of inadvertently (or voluntary) changing the input argument's state within the command.

  • Immutable objects are less vulnerable to malicious attacks, because evil code cannot change the state of immutable objects.

Despite the advantages of immutable objects, it does not always make sense to make objects immutable.

In some situations it is even impossible. For example, if two objects mutually refer to each other. A concrete example would be a tree with each node holding a reference to its parent node and its child nodes.

Another issue is that immutable types require a different object for each distinct value. Hence, each time the state changes, a new object has to be created, which can be too much time and space consuming. Consider, for example, a list containing all accounting records. Every time a new records was added, a new list would have to be created. Obviously, in a real-world application with thousands or millions of records, all memory resources would quickly be consumed and the performance would drop down to an unacceptable level.

Quite often, the best solution is to provide an immutable type together with a mutable counterpart. The mutable type is first used to build an object. Then, once the object doesn't change anymore it is converted into its immutable counterpart.

This method of first using a mutable type to build an object and then convert it into an immutable one (e.g. with command to_immutable) is largely supported in Obix's standard library.

The following code shows an example of creating an immutable list by first using a mutable list and then converting it to an immutable one:

Example 7.1. Building an immutable list

service geographic_data

   attribute list_of_continents type:!indexed_list<string>
      default
         script
            // declare mutable list
            const !mutable_indexed_list<string> result = !mutable_indexed_list_factory<string>.create

            // append items
            result.append ( "Africa" )
            result.append ( "Antartica" )
            result.append ( "Asia" )
            result.append ( "Australia" )
            result.append ( "Europe" )
            result.append ( "North America" )
            result.append ( "South America" )
            
            // convert to immutable
            o_list_of_continents = result.make_immutable
         end
      end
   end

end

See also