So it's kind of a categorical error. (I want to joke here that all categorical errors are just type errors in category theory.) When we speak of "type of a variable", we mean this variable can only be assigned (bound to) values of certain type. This has nothing to do with whether it can be reassigned (i.e. mutability).
So you don't even need the notion of subtyping to explain this.
Also, one could probably define variable as a monad over its type.
I feel like you're using "mutable" too narrowly, as it's used colloquially in some programming languages. E.g. in JS, MDN itself talks about "reassignment"[0] and not "mutability" even though people often use "mutability" as a word to refer to the distinction between `const` and `let`. The only mention of mutability there is:
> Others may prefer `let` for non-primitives that are mutated
I.e. explicitly using `let` for mutated arrays, even if not reassigned, to make the signal mutability (which JS cannot express).
Note that you used "reassignment" which is the specific word for "mutating a binding", but the article is not referring to bindings at all.
[0] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
Whether or not something belongs into a type system is ultimately determined by the type system. We can choose whether or not mutability is considered a part of a type.
> When we speak of "type of a variable", we mean this variable can only be assigned (bound to) values of certain type. This has nothing to do with whether it can be reassigned (i.e. mutability).
This is a bit too simplistic IMO. You're talking about name bindings, the article is talking more about things like interior mutability.
Rebinding a name is ... generally not a type system concern by my understanding.
When you say "we can choose mutability as a part of a type", the question is, what kind of errors are we trying to prevent? What is the semantics we want to give? From that it should be obvious whether it can be subtype or not.
really? i honestly dont have _too_ much experience with functional languages, basically only elixir and consequently some amount of erlang in that vain... but i'd feel like thats not the same? but it may be that my point of view is too narrow.
from my experience with that functional language, the equivalent to this scenario would be a struct - and wherever i can mutate properties within it -- or need to reconstruct the struct from scratch.
both have technical consequences, eg if i passed the struct into a consumer somewhere which keeps it, it would get the "modified" version automatically when the property was changed
but on reconstruction, it'd have to introduce some kind of event listener to handle the reconstruction.
simple example for such a scenario would be eg a session within a SSE api. the mutated struct would trivially allow for an uninterrupted stream no matter how long the session is extended, the latter needs to pay attention so its not opening a memory leak to support that feature.
Not sure if you're just being polite but, in case you're doubting, functional languages make that distinction too.
Haskell has `Data.Vector.Vector` vs `Data.Vector.Mutable.MVector`.
Clojure has `transient`.
OP is just mistaken.
Variables can be described by types. That is, a mutable slot referencing a value can be a value itself (a reference to a reference) and we can use subtype relationships to describe it. Variables are covariant when used as inputs (i.e code reads from the variable) and contravariant when used as outputs, and invariant when used as both. You can logically supply a Box<Cat> to a vet(in Box<Animal>) routine, and supply Box<Animal> to catchAndStore(out Box<Cat>). Substitute `ref X` for `Box<X>` when using a language that can pass variables by reference.
In statically typed languages, variables and expressions have a compile-time type and values have a run-time type.
> Mutability is a property of variable, not of a value.
In which languages?
In D, mutability is a property of a type. And variables and expressions have a compile-time type and values have a run-time type, so mutability is also a property of variables, expressions, and values.
(Immutability is also transitive in D, so an immutable value cannot have mutable parts, including mutable references, and a variable with an immutable type cannot contain a value or reference with any mutable parts.)
This seems confused. "Mutable" or "immutable" are types like any other, like "String" or "Integer".
In computer science, specifically programming language theory, types are properties of terms (syntactic expressions). It wouldn’t be possible in general to typecheck a program before running it if you had to have a value before you could discover its type.
- A: can I change the value
- B: can something else change the value (can I depend on a predictable stable value)
Because the axes are orthogonal, hierarchical based subtyping (inheritance) breaks, but type classes (interfaces), ad hoc polymorphism, would work.
In C, const answers A
In rust, due to pointer aliasing restrictions (either one mut pointer xor any amount of read only pointers), (lack of) mut answers both A and B
So mutability xor aliasing provides this strict subtyping relation. Of course, you also then need ways of loosening this by providing objects without such a contract and you enter the land of interior mutability, where again the mutable methods can be understood as a part of a subtype because a holder of the reference without mutable methods was explicitly told that there was no the guarantee that the object wouldn't change.
In C# ReadOnlyCollection<T> and ImmutableArray<T> are two completely different things for this exact reason.
In contrast, neither are the mutable things a subset of the immutable things nor the other way round. It's not the case that everything mutable is immutable nor that everything immutable is mutable. The two types are disjoint.
I'm currently being annoyed by python's type hinting system, which has exactly this sort of hierarchy for containers, but there's nothing stopping a caller/callee from using type-narrowing to "discover" that the underlying type is actually e.g. a (mutable) list, and then modifying it without any complaints from the type checker. The only way to enforce this would be to actually convert to an immutable implementation type, involving unnecessary copying.
Also, Back In The Old Days memory was very expensive and precious so short identifiers were important. Often labels were tightly constrained, for example being limited to six upper-case characters so they would fit in a single word.
CAR = Contents of the Address part of Register
CDR = Contents of the Decrement part of Register
This kind of TLA is common in assembly languages and LISP just inherited this because early implementation used these literal instructions.I also love how the register names don’t even have ANY mnemonic relation to what the contents mean in Lisp. Address and Decrement? Totally arbitrary. It’s like having a high-level language where function parameters are always names rsi, rdi, and so on. Because obviously "destination index" means "first argument"!
Something I tell junior developers: There is one way to correctly spell out a word in full, but many different ways of abbreviating it. Don't make me guess or memorise which one you chose, completely arbitrarily.
UNIX and languages "born from it" are especially prone to a ludicrous level of abbreviation because of its start in the earliest days of computing when serious machines had mere kilobytes of memory, and so every byte was precious. Compounding that were the use of teletypewriters, which did not generally allow edits and were glacially slow. Tab complete was impossible.
This has become so ingrained in the early generation of computing science pioneers that they passed on these behaviours almost like religious doctrine, like dogma or rituals that must be preserved. Perhaps it was mostly subconsciously done, but I have seen programmers in the 90s and early 2000s still instructing others to do these things, to shave bits and bytes off identifiers to 'save space'.
Computers used to have very little memory and processing power, and screens had very low pixel density so line width was not that big. Then you have to deal with the baggage 50 years later.
Modern Lisps have the luxury so they use head/tail or first/rest.
The concept is to make things easier for people who are familiar with the language. Making things harder for that group is always counterproductive, because they are the only people who can do any useful work.
More characters on the other hand prevents many potential types of mistakes, such as meaning one statement of target and actually entering another.
Look at the words in the Swadesh list[0]. They are all short.
Moreover, I suspect it is possible to construct an interface such that to prove statically that you can Liskov Substitute a type into it would be equivalent to deciding Halt: All you need are extensional semantics in your type system.
Basically inheritance is the wrong tool for this kind of stuff. NSMutableArray inherits from NSArray, so it can be passed to anywhere NSArray is expected (upcasting).
So you design your classes and expect them to be immutable, but you can't use NSArray anywhere. Because otherwise, it'll be mutable after all. You can do a runtime check as a workaround.
(I truly believe OOP should only be taught in computer science as a relic).
The problem is the "exclusive access to it", most language aren't Rust.. Some Ada-implementation has the problem ( https://news.ycombinator.com/item?id=43001394 )
Swift is completely different. Standard arrays are structures, always mutable - value types passed by value
https://commonplacefacts.com/2022/07/27/principia-mathematic...
Note, also, that the article isn't even objective. It asserts that the definition of a subtype is Liskov's principle. However, Liskov's principle is only one of multiple possible definitions. In other words, the article is really only invoking Liskov's name as an appeal to authority. So much for strict logic.