Open romshark opened 6 years ago
Okay I'm just throwing this out there because it doesn't have too much to do with the proposal itself - but slices aren't comparable and therefore cannot be used as map keys 😅
Anyway, I like that the type system in Go is simple. I don't want to have another language where every case is covered by built-in language features.
@deanveloper that's right, it was meant to be an array actually. Fixed it, need to go get some sleep ASAP.
Let's discuss the question whether or not immutability is necessary in another thread. This issue is all about MQP
This doesn't do much to simplify the type system because now you need three key words in stead of two.
I would say, please think about it in an engineering way. What you seem to want to achieve is to prevent accidental modifications to function arguments. What would be the simplest solution to achieve this?
~I would quit my job if I had to parse, understand and maintain mut map [ [3] immut * mut T ] [] * [] immut map [T] immut * T
. I use go because it feels close to the pareto optimal between (syntax patterns I must understand and use) and (language's capability envelope)~
sorry, link me to the other thread and i'll xpost and delete this
The issue tracker is not a great place for general discussion, because discussion is not threaded. the golang-nuts mailing list is a better place.
Oh, sorry, this is not on the Go project issue tracker. My apologies.
@beoran What's the third keyword you're referring to? I've only proposed mut
and immut
here.
Immutable types are not just about "accidental modification to function arguments", they're about accidental modification of anything, be it an argument, a field, a field of a field, a variable, a return value, a function receiver or a package-scope variable. It's about preventing mutable shared state making bugs less likely by making the intentions of the programmer clear.
Well I would say that perhaps we don't need all this detail. Simply have const apply on the whole type and all it's indirections.
I don't think immutsble by default is a good idea. Variables exist to change though time, this is the normal use case. I think languages like Rust get that wrong, the common use case should be the easiest to use.
The Qualification Verbosity Problem
A lot of people reject both
mut [] mut [] mut * mut T
andconst [] const [] const * const T
which is totally understandable, I don’t like this level of verbosity either. In fact, I hate it myself, but until now I didn’t really have another option to offer and considered it a "dirty, but necessary bureaucratic evil". I thought about it and I might have come up with a solution, which, unfortunately is only possible in Go 2.x (the reason is explained below) and is also yet to be discussed.Problem: Verbosity vs Mixed-Mutability Types
Most would prefer transitive immutability, where the following type definition is deeply immutable:
With transitive immutability, the above type definition would represent:
Compared to
const [] const [] const * const T
it's way less verbose and much more readable. But it didn’t answer the question:Transitive immutability reduces verbosity, but introduces another problem: it makes mixed-mutability types impossible to describe voiding the entire concept of immutable types! Developers will avoid immutable types in situations involving complex mixed-mutability types, but those are the situations they need the help of the compiler most! An immutability concept that works only in simple cases but fails to be useful in rather complex situations is simply pointless.
Potential Solution: Mutability Qualification Propagation
The concept of "mutability qualification propagation" (short: "MQP") attempts to reduce verbosity while still allowing mixed-mutability types. It's based on two fundamental rules:
MQP: A Simple Example
Consider the following deeply immutable two-dimensional slice of pointers to
T
:The above type definition implies immutability by default and represents:
To make the entire type deeply mutable we need to prepend the
mut
qualifier only ones:The qualifier will propagate to the right and affect every type in the type chain! The above type definition now represents:
To make an exception for the
T
at the end and make it mutable, we need to cancel out the firstmut
qualifier by animmut
qualifier the following way:The above type definition now represents:
MQP: A Ridiculously Complex Example
The following ridiculously complex example demonstrates, that this system consistently works across all levels of type complexity.
DISCLAIMER: THIS CODE IS FOR DEMONSTRATIONAL PURPOSES ONLY, DO NOT EVER WRITE CODE LIKE THIS IF YOU DON'T WANT TO END UP ON THE GUILLOTINE!
Consider the following type definition:
The above type definition implies immutability by default and represents:
This type definition consist of:
mut
propagates until the secondimmut
of theroot
path as well as until the firstimmut
in thefirst map-key branch
.immut
in thefirst map-key branch
propagates until it’s canceled out by the secondmut
.mut
propagates until the end of thefirst map-key propagation branch
path.immut
actually propagates all the way down to the end of theroot
propagation path. It’s canceled out by the thirdimmut
, but the third one has practically no effect because it's equal to the one it cancels out (The linter should complain about the thirdimmut
point out that's it's pointless)MQP & Go 1.x
Mutability qualification propagation cannot be implemented in the Go 1.x immutability proposal because it requires another keyword (namely:
mut
) that'd be able to cancel out the propagatingconst
. An additional keyword would require backward-incompatible changes and can't therefore be considered.MQP & Go 2.x
Mutability qualification propagation can be implemented in the backward-incompatible Go 2.x language specification by adding two new keywords:
mut
andimmut
. Both qualifiers are able to cancel each other out.Qualifier Keyword Naming
immut
was chosen becauseimut
is visually very similar tomut
and can lead to misinterpretation. Alternatively,nomut
could be proposed instead ofimmut
. More naming ideas are always welcome.MQP vs no MQP
[][]*T
mut [] mut [] mut * mut T
mut [][]*T
[][]*T
const [] const [] const * const T
[][]*T
[][]*T
const [][]*T
[][]* const T
mut [] mut [] mut * T
mut [][]* immut T
[][] const * const T
mut [] mut [] * T
mut [][] immut * T
const map[const * const T] const * const T
map[*T]*T
map[*T]*T
const map[*T]*T
map[const * const T] const * T
mut map[*T]* mut T
mut map[immut *T] immut * mut T