Immutable objects don't change. Calling SetFoo on an immutable object will return a new snapshot.
Flavours we can distinguish when looking at object graphs
Deep immutability: a deep immutable object doesn't mutate over time, nor does any object that is referenced by it.
Flat immutability: referenced objects may mutate internally, but the references themselves stay the same. (her mum... will always be her mum, but she is getting funnier every day...I love her)
What do we need immutability for?
Deep immutability: a deep immutable object can safely be accessed by several threads as it never changes nor does anything in its reach.
Flat immutability: Quite helpful in dataflow scenarios already. It turns out you typically don't bother so much about deep immutability. We need examples for that claim
How is Immutability integrated into the language?
So when choosing Record:
What do we choose: flat immutability or deep immutability?
That's where the explanation part slowly comes to an end and the quest part starts.
Some History
Back in the day, we intuitively were focussing on deep immutability. For example, we were stating: a Spread<T> is an immutable collection, which also means that T must be immutable. (The type inference was actually attaching a constraint to that T so that it only can be replaced by an immutable type later on.)
So we interpreted the fact that Spread<> is immutable in a way that the application, e.g. Spread<Integer32> also has to be immutable. But that would exclude certain applications. Shall we really not allow Spread<object>? We should allow that. But is that type application immutable? Well. It is flat immutable, but not deep immutable.
When you have AsyncLoop. Should you be allowed to link a Spread<object> into that region running on a different thread? Some of those objects might mutate. So it's not thread-safe...
In the early days, we tried to make sure that the type inference always makes sure that deep immutability is guaranteed where it is needed (e.g. on that link going into a delegate or higher-order region, since this might get executed on a different thread).
But while we were progressing, we also found out that when choosing Record you still want to be able to refer to mutable objects sometimes. You actually want to have a snapshot-based / copy-on-write / immutable behavior, BUT just on that level, not all the way "down". You might even want to be able to use process nodes that mutate. See the Syncer inside of SkiaPaint as an example. And you might want to be able to store mutable objects inside fields.
You still might want to have warnings, but no errors. So we learned that we have to allow more and more.
Process patches
When choosing Process you are saying that you don't want to decide really. You are actually saying: when the resulting process node is used inside a record definition, then please just adapt and also act as a record. This again comes from the desire to have deep immutable behavior whenever possible. Choosing Process could also be coined AdaptToContext regarding immutability. The fact that we don't allow a State Output here is due to the fact that we don't know how the process node will be used. Some instances of that Process will actually be initialized in a way that they act immutable, others will act mutable.
Interfaces
Interfaces currently don't have the notion of immutability. They can act as an interface for both immutable and immutable implementations. There were ideas of having also immutable interface definitions that then would guarantee that every implementation also has to be immutable...
The quest
some questions for which a precise answer would be nice:
Do we want to have a deep immutability option when defining a type? like DeepRecord
Do we ever want to have a type inference that infers that some type parameter has to be immutable? Making sure that certain types are not allowed? like object
But would that mean that when replacing T we need some new "flavor" in our type system stating that certain objects are immutable, like Spread<ImmutableObject> would describe a spread, that is not generic anymore and is able to hold entries of different immutable types [0, 0.0, (r0.0, g0.0, b0.0)]?
When choosing Process: Can we have a State Output? Can we just get rid of the idea that the type holds the information whether its instances are immutable or not. Just get rid of that idea. Some instances might be immutable others not. You can ask an instance, whether it is immutable at runtime if you like. Why? We would be able to call operations later on it... Currently, if you want to do that you need to choose Record or Class. Advanced users are unhappy about "stupid" process nodes that don't have a State Output
Or do we want to get rid of Process? Which comes from the deep immutability idea. Maybe it would feel fine to have a mutable LFO linked to a mutable Queue inside your custom record? Class would be the new default.
So there are quite some interconnected quests. We of course have ideas regarding all those questions and some tendency. But let's try to come up with 2 or 3 attempts at solving the quests and see which proposal shines the brightest.
Please also feel free to add questions or comments to the quest.
Introduction
What is Immutability?
Immutable objects don't change. Calling
SetFoo
on an immutable object will return a new snapshot.Flavours we can distinguish when looking at object graphs
Deep immutability:
a deep immutable object doesn't mutate over time, nor does any object that is referenced by it.Flat immutability
: referenced objects may mutate internally, but the references themselves stay the same. (her mum... will always be her mum, but she is getting funnier every day...I love her)What do we need immutability for?
Deep immutability
: a deep immutable object can safely be accessed by several threads as it never changes nor does anything in its reach.Flat immutability
: Quite helpful in dataflow scenarios already. It turns out you typically don't bother so much about deep immutability. We need examples for that claimHow is Immutability integrated into the language?
So when choosing
Record
: What do we choose: flat immutability or deep immutability?That's where the explanation part slowly comes to an end and the quest part starts.
Some History
Back in the day, we intuitively were focussing on deep immutability. For example, we were stating: a
Spread<T>
is an immutable collection, which also means thatT
must be immutable. (The type inference was actually attaching a constraint to thatT
so that it only can be replaced by an immutable type later on.) So we interpreted the fact thatSpread<>
is immutable in a way that the application, e.g.Spread<Integer32>
also has to be immutable. But that would exclude certain applications. Shall we really not allowSpread<object>
? We should allow that. But is that type application immutable? Well. It is flat immutable, but not deep immutable.When you have AsyncLoop. Should you be allowed to link a
Spread<object>
into that region running on a different thread? Some of those objects might mutate. So it's not thread-safe... In the early days, we tried to make sure that the type inference always makes sure that deep immutability is guaranteed where it is needed (e.g. on that link going into a delegate or higher-order region, since this might get executed on a different thread).But while we were progressing, we also found out that when choosing
Record
you still want to be able to refer to mutable objects sometimes. You actually want to have a snapshot-based / copy-on-write / immutable behavior, BUT just on that level, not all the way "down". You might even want to be able to use process nodes that mutate. See the Syncer inside ofSkiaPaint
as an example. And you might want to be able to store mutable objects inside fields.You still might want to have warnings, but no errors. So we learned that we have to allow more and more.
Process patches
When choosing
Process
you are saying that you don't want to decide really. You are actually saying: when the resulting process node is used inside a record definition, then please just adapt and also act as a record. This again comes from the desire to have deep immutable behavior whenever possible. Choosing Process could also be coinedAdaptToContext
regarding immutability. The fact that we don't allow aState Output
here is due to the fact that we don't know how the process node will be used. Some instances of that Process will actually be initialized in a way that they act immutable, others will act mutable.Interfaces
Interfaces currently don't have the notion of immutability. They can act as an interface for both immutable and immutable implementations. There were ideas of having also immutable interface definitions that then would guarantee that every implementation also has to be immutable...
The quest
some questions for which a precise answer would be nice:
DeepRecord
object
T
we need some new "flavor" in our type system stating that certain objects are immutable, likeSpread<ImmutableObject>
would describe a spread, that is not generic anymore and is able to hold entries of different immutable types[0, 0.0, (r0.0, g0.0, b0.0)]
?State Output
? Can we just get rid of the idea that the type holds the information whether its instances are immutable or not. Just get rid of that idea. Some instances might be immutable others not. You can ask an instance, whether it is immutable at runtime if you like. Why? We would be able to call operations later on it... Currently, if you want to do that you need to chooseRecord
orClass
. Advanced users are unhappy about "stupid" process nodes that don't have aState Output
Process
? Which comes from the deep immutability idea. Maybe it would feel fine to have a mutableLFO
linked to a mutableQueue
inside your custom record?Class
would be the new default.So there are quite some interconnected quests. We of course have ideas regarding all those questions and some tendency. But let's try to come up with 2 or 3 attempts at solving the quests and see which proposal shines the brightest.
Please also feel free to add questions or comments to the quest.