Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign upGo 2: mutability qualification propagation #20
Comments
romshark
added
problem
discussion
labels
Oct 5, 2018
romshark
added this to the v2 milestone
Oct 5, 2018
romshark
self-assigned this
Oct 5, 2018
romshark
changed the title from
Go 2.x: mutability qualification propagation
to
Go 2: mutability qualification propagation
Oct 5, 2018
romshark
added
the
help wanted
label
Oct 5, 2018
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
deanveloper
Oct 5, 2018
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
commented
Oct 5, 2018
|
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. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
romshark
Oct 5, 2018
Owner
@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
|
@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 comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
beoran
Oct 5, 2018
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?
beoran
commented
Oct 5, 2018
|
This doesn't do much to simplify the type system because now you need three key words in stead of two. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
therealplato
Oct 5, 2018
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
therealplato
commented
Oct 5, 2018
•
|
|
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ianlancetaylor
Oct 5, 2018
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.
ianlancetaylor
commented
Oct 5, 2018
|
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. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ianlancetaylor
commented
Oct 5, 2018
|
Oh, sorry, this is not on the Go project issue tracker. My apologies. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
romshark
Oct 5, 2018
Owner
@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.
|
@beoran What's the third keyword you're referring to? I've only proposed 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. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
beoran
Oct 5, 2018
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.
beoran
commented
Oct 5, 2018
•
|
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. |
romshark commentedOct 5, 2018
•
edited
The Qualification Verbosity Problem
A lot of people reject both
mut [] mut [] mut * mut Tandconst [] const [] const * const Twhich 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:
const [][]*TWith transitive immutability, the above type definition would represent:
Compared to
const [] const [] const * const Tit'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
mutqualifier 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
Tat the end and make it mutable, we need to cancel out the firstmutqualifier by animmutqualifier the following way:mut [][]* immut TThe 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:
mutpropagates until the secondimmutof therootpath as well as until the firstimmutin thefirst map-key branch.immutin thefirst map-key branchpropagates until it’s canceled out by the secondmut.mutpropagates until the end of thefirst map-key propagation branchpath.immutactually propagates all the way down to the end of therootpropagation 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 thirdimmutpoint 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:
mutandimmut. Both qualifiers are able to cancel each other out.Qualifier Keyword Naming
immutwas chosen becauseimutis visually very similar tomutand can lead to misinterpretation. Alternatively,nomutcould be proposed instead ofimmut. More naming ideas are always welcome.MQP vs no MQP
[][]*Tmut [] mut [] mut * mut Tmut [][]*T[][]*Tconst [] const [] const * const T[][]*T[][]*Tconst [][]*T[][]* const Tmut [] mut [] mut * Tmut [][]* immut T[][] const * const Tmut [] mut [] * Tmut [][] immut * Tconst map[const * const T] const * const Tmap[*T]*Tmap[*T]*Tconst map[*T]*Tmap[const * const T] const * Tmut map[*T]* mut Tmut map[immut *T] immut * mut T