Can a whole program be immutable? [closed]

丶灬走出姿态 提交于 2019-12-10 15:36:49

问题


  • 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?


回答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.




回答2:


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.



来源:https://stackoverflow.com/questions/50985035/can-a-whole-program-be-immutable

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!