4

Chain of immutable objects

  • I am familiar with immutability and can design immutable classes, but I have mostly academic knowledge and lacking hands on experience
  • Please refer to the linked image above (not allowed to embed yet)
  • Looking at it from the bottom up
  • Student needs a new address
  • Instead of really changing student, we create a new student which incorporates the new address
  • The mutator method returns this new object

Question: What do do with this new object, presuming the mutator call came from an immutable object?

  • The new student can't be saved in Lecture, because Lecture is immutable as well
  • So we need a new Lecture as well, which incorporates the new Student
  • But where to save the new Lecture?
  • In a new Semester, of course, but where does it end?
  • The chain can be broken at least, by using the component facade pattern, which handles the creation of all the new objects, without having to forward the call through the whole chain

Question: Where does this stop? Doesn't there have to be a mutable object somewhere to at least save the topmost instance?

Lino
  • 19,604
  • 6
  • 47
  • 65
Malte
  • 75
  • 7
  • 5
    Voting to move this post on [Software Engineering](https://softwareengineering.stackexchange.com/) because it is not a question about working code but rather programming concepts. – Spotted Jun 22 '18 at 09:40
  • For the whole programm to be immutable there should be a state which is saved in a file or something. so whenever the state changes. The new state should get persisted in that file and the programm restarted – Lino Jun 22 '18 at 09:40
  • 1
    Why does Student have a setter method then? – Herr Derb Jun 22 '18 at 09:55
  • @Lino There is no need for a file. The state can be saved in local variables and parameters. See my answer. – Stefan Dollase Jun 22 '18 at 10:05
  • 1
    @HerrDerb the setter of an immutable object does not change the instance itself, but instead returns a new instance which is similar to the former instance except for the intended change, providing the caller with the "changed" instance, but leaving the former instance untouched. – Malte Jun 22 '18 at 10:25
  • @Malte I currently don't see the use of this. What's the reason that you don't restrict the object to a constructor and getter methods? – Herr Derb Jun 22 '18 at 11:17
  • @HerrDerb That does not *really* matter. I am actually implementing in the builder pattern ( [link](http://www.informit.com/articles/article.aspx?p=1216151&seqNum=2) ) and don't have those "setter" methods. I just tried to keep this example as small as possible – Malte Jun 27 '18 at 08:40

2 Answers2

1

This is the idea of functional programming. Everything is immutable, no function call is allowed to have side-effects. The only way to mutate complex objects, like in your example, is to re-create the parent objects.

Now the question is how to alter the program state. Therefore, we first think about the stack. It contains the values of all local variables as well as the value of all parameters to the called functions. We can create new values by calling new functions. We can discard values by returning from a function. Thus, we can mutate the program state by calling functions. However, it is not always possible to return from the function to discard its local variables, because we might want to only discard some of the local variables, but need to keep the value of others for further operations. In this case, we simply cannot return, but we need to call another function and pass only some of the local variables to it. Now, to prevent a stack overflow, functional languages have a feature which is called tail call optimization, which is able to remove unnecessary entries from the call stack. An entry of the call stack is unnecessary if the only thing that is left to do for the associated function is to return the value of the function that was called by itself. In this case, there is no point in keeping the call stack entry. By removing the unnecessary call stack entry, the values of the otherwise unused local variables is discarded. You might want to read about it here. Also, tail recursion is related to this.

Again, this is the idea of purely functional programming languages like Haskell. It is really nice that everything is immutable, however these languages have their only issues and their own ways to handle these. For example, Monads (and therefore higher kinded types) are available in these languages, but are rarely seen in imperative/object oriented programming languages.

I like to have immutable values at the leaves of my program memory. However, the code to compose these immutable values, which actually forms the application logic does contain mutable state. For me, this combines the advantages of both worlds. However, this seems to be a matter of preference.

Stefan Dollase
  • 4,530
  • 3
  • 27
  • 51
  • Thank you for the extensive answer. This definitely helps. I'll have to see, how much of the state i'll actually have on the stack when the implementation proceeds. I'll consider having a mutable core component as well, as you suggested. – Malte Jun 27 '18 at 08:57
0

With your existing structure this would be quite difficult, and this is probably what you are supposed to learn with this exercise.

I would remove all relationships between the objects from the objects and implement those relationships using Map and Set.

Something like this would be a good starting point.

// Make sure all objects can be uniquely identified.
interface Id {
    public Long getId();
}

class HasId implements Id {
    private final Long id;

    // Normal constructor.
    public HasId(Long id) {
        this.id = id;
    }

    // Copy constructor.
    public HasId(HasId copyFrom) {
        this(copyFrom.id);
    }

    @Override
    public Long getId() {
        return id;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        HasId hasId = (HasId) o;
        return Objects.equals(id, hasId.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}

class Semester extends HasId {
    public Semester(Long id) {
        super(id);
    }

    public Semester(Semester copyFrom) {
        super(copyFrom);
        // TODO: Copy all the other fields of Semester to mine.
    }
    // Do NOT hold a list of Lectures for this semester.
}

class Lecture extends HasId {
    // ...
    // Do NOT hold a list of Students for this lecture.
}

class Student extends HasId {
    // ...
}

// Core structures.
Map<Id, List<Lecture>> semesters = new HashMap<>();
Map<Id, List<Student>> lectures = new HashMap<>();
Set<Id> students = new HashSet<>();
// Utility structures that need to be maintained.
Map<Id, Lecture> studentsInLecture = new HashMap<>();
Map<Id, Semester> lecturesInSemester = new HashMap<>();

In this way you can isolate the objects and keep them immutable but if you do need to change any student's details you can clone the original student and steal it's identity.

This is clearly not a complete solution yet but I hope the concept I am trying to suggest is clear.

OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
  • This does not actually answer the question, since the collections are mutable and supposed to be mutated. However, the OP explicitly asked about a program without mutable state. – Stefan Dollase Jun 22 '18 at 11:13
  • @StefanDollase - You are right. However, this is a pattern that is both usable *and* all code objects are immutable. I acknowledge that `Map` and `Set` are not immutable in this case. – OldCurmudgeon Jun 22 '18 at 11:17
  • 1
    Actually this answer seems to match the suggestion in @StefanDollase last paragraph. Having immutable leaves, but a mutable core to manage the state. I'd probably introduce the mutability on a higher level than illustrated in this example, but as a minimal working example, this makes the concept clear. Thank you too. – Malte Jun 27 '18 at 09:01