9 ms·
Experiment: Making TypeScript immutable-by-default
- zero_shift 11mo ago> If you figure out how to do this completely, please contact me—I must know! I think you want to use a TypeScript compiler extension / ts-patch This is a bit difficult as it's not very well documented, but take a look at the examples in https://github.com/nonara/ts-patch https://github.com/nonara/ts-patch Essentially, you add a preprocessing stage to the compiler that can either enforce rules or alter the code It could quietly transform all object like types into having read-only semantics. This would then make any mutation error out, with a message like you were attempting to violate field properties. You would need to decide what to do about Proxies though. Maybe you just tolerate that as an escape hatch (like eval or calling plain JS) Could be a fun project!
- Cthulhu_ 11mo agoOne "solution" is to use Object.freeze(), although I think this just makes any mutations fail silently, whereas the objective with this is to make it explicit and a type error.
- dunham 11mo agoI thought Object.freeze threw an exception on mutation. Digging a little more, it looks like we're both right. Per MDN, it throws if it is in "use strict" mode and silently ignores the mutation otherwise.
- zelphirkalt 11mo agoIsn't the idea to get a compile time error, rather than a runtime exception?
- rezistik 11mo agoconst exploring = Object.freeze({ immutable: true }) exploring.thing = 'new' Property 'thing' does not exist on type 'Readonly<{ immutable: true; }>'.ts(2339) So it would be a simple way to achieve it.
- ItsHarper 11mo agoThat's opting into immutability, the point of the experiment is having it by default. Plus, that's just the type system preventing you from adding a property. It won't stop you from trying to change the `immutable` field. I'm genuinely curious, was this AI generated, or just a lack of understanding?
- nightpool 11mo agoNo, you're the one that's incorrect, typescript blocks mutations when you use Object.freeze too: "Cannot assign to 'immutable' because it is a read-only property. (2540)" You can also use "as const" to get the same behavior without any runtime calls: const exploring = { immutable: true } as const exploring.immutable = false ^ Cannot assign to 'immutable' because it is a read-only property.(2540) But yes, OP wasn't referring to the article, they were just pointing out the narrower fact that Typescript does in fact have compile-time errors for mutating Object.freeze's return values.
- giancarlostoro 11mo agoI used to have code somewhere that would recursively call Object.freeze on a given object and all its children, till it couldn't "freeze" anymore.
- voidUpdate 11mo agoHow do immutable variables work with something like a for loop?
- foxygen 11mo agoYou use something else like map/filter/reduce or recursion.
- tgv 11mo agoUnless you need the index, you can write: for (const x of iterable) { ... } or for (const attribute in keyValueMap) { ... }. However, loops often change state, so it's probably not the way to go if you can't change any variable.
- Cthulhu_ 11mo agoIf you need the index, you can use .keys() or .entries() on the iterable, e.g. for (const [index, value] of ["a", "b", "c", "d", "e"].entries()) { console.log(index, value); } Or forEach, or map. Basically, use a higher level language. The traditional for loop tells an interpreter "how" to do things, but unless you need the low level performance, it's better to tell it "what", that is, use more functional programming constructs. This is also the way to go for immutable variables, generally speaking.
- tgv 11mo agoThere's no difference between for (x of a) stmt; and a.forEach(x => stmt), except for scope, and lack of flow control in forEach. There's no reason to prefer .forEach(). I don't see how it is "more functional."
- tantalor 11mo agoIs TFA (or anyone else for that matter) actually concerned with "immutable variables"? e.g., `let i = 0; i++;` They seem to be only worried about modifying objects, not reassignment of variables.
- tyleo 11mo agoThis is tangential but one thing that bothers me about C# is that you can declare a `readonly struct` but not a `readonly class`. You can also declare an `in` param to specify a passed-in `struct` can’t be mutated but again there’s nothing for `class`. It may be beside the point. In my experience, the best developers in corporate environments care about things like this but for the masses it’s mutable code and global state all the way down. Delivering features quickly with poor practices is often easier to reward than late but robust projects.
- bilekas 11mo agoI'm not sure if it's what you mean, but can't you have all your properties without a setter, and only init them inside the constructor for example ? Would your 'readonly' annotation dictate that at compile time ? eg class Test { private readonly string _testString {get;} public Test(string tstSrting) => _testString = tstSrting ; } We may be going off topic though. As I understand objects in typescript/js are explicitly mutable as expected to be via the interpertor. But will try and play with it.
- vips7L 11mo agoI think you would want to use an init only property for your example class Test { public string Test { get; init; } } I'm not a C# expert though, and there seems to be many ways to do the same thing.
- bilekas 11mo agoI don't use the init decorator myself but I would hazard a guess it's similar. Don't quote me on that though. The point does stand though, outside of modifying properties I'm not sure what a "private" class itself achieves.
- vips7L 11mo ago> I don't use the init decorator myself but I would hazard a guess it's similar. Genuinely curious, why not? Seems to be less verbose. I don’t write C#s so I’m not sure of the downsides of any particular feature.
- phplovesong 11mo agoSounds easier to just use some other compile to js languge, its not like there are no other options out there.
- petejodo 11mo agoAgreed. Gleam is a great one that targets JavaScript and outputs easy to read code
- phplovesong 11mo agoYup. Also rescript if your not a fan of the elm architecture.
- k__ 11mo agoI'm still mad about Reason/ReScript for fumbling the bag here.
- phplovesong 11mo agoRescript/reasonml is still in development, and a more seasoned dev team can easily pick it as an better alternative to typescript. Its a bummer haxe did not promote itself more for the web, as its a amazinlgy good piece of tech. The languge shows age, but has an awesome typesystem and metaprogramming capabilities. That said, haxe 5 is on the horizon.
- k__ 11mo agoWhile TS allows easy integration with JS, this doesn't work well with other languages that compile to JS. You lose all type benefits of libraries that are written in TS.
- phplovesong 11mo agoIts quite rare to see interop between compile to js languages tho. Also rare to see projects using more (if not in the middle of a rewrite/port) than one compile to js language. YMMV.
- drob518 11mo agoIt’s interesting to watch other languages discover the benefits of immutability. Once you’ve worked in an environment where it’s the norm, it’s difficult to move back. I’d note that Clojure delivered default immutability in 2009 and it’s one of the keys to its programming model.
- hden 11mo agoMutability is overrated.
- recursive 11mo agoImmutability is also overrated. I mostly blame react for that. It has done a lot to push the idea that all state and model objects should be immutable. Immutability does have advantages in some contexts. But it's one tool. If that's your only hammer, you are missing other advantages.
- WolfOliver 11mo agoexactly, react could not deal with mutable object so they decided to make immutability seem to be something that if you did not use before you did not understood programming.
- iLemming 11mo agoReact made immutability patterns more relevant which increased discussion of it. Some people did get preachy about it. Yet dismissing immutability entirely just because of that misses the entire point of why it's actually useful in managing complex state. Have you ever thought about instead of having emotional reaction to obnoxious gatekeeping to learn about actual benefits of immutability?
- iLemming 11mo agoImmutability is really valuable for most application logic, especially: - State management - Concurrency - Testing - Reasoning about code flow Not a panacea, but calling it "overrated" usually means "I haven't felt its benefits yet" or "I'm optimizing for the wrong thing" Also, experiencing immutability benefits in a mutable-first language can feel like 'meh'. In immutable-first languages - Clojure, Haskell, Elixir immutability feels like a superpower. In Javascript, it feels like a chore.
- bilekas 11mo agoThis has really irrationally interested me now, Im sure there is something there with the internal setters on TS but damn I need to test now. My thinking is that overriding the setter to evaluate if its mutable or not, the obvious approach.
- WorldMaker 11mo agoYeah there's a lot you could do with property setter overrides in conditional types, but the tricky magic trick is somehow getting Typescript to do it by default. I've got a feeling that `object` and `{}` are just too low-level in Typescript's type system today to do those sorts of things. The `Object` in lib.d.ts is mostly for adding new prototype methods, not as much changing underlying property behavior.
- manoDev 11mo ago> That should make arr[1] possible but arr[1] = 9 impossible. I believe you want `=`, `push`, etc. to return a new object rather than just disallow it. Then you can make it efficient by using functional data structures. https://www.cs.cmu.edu/~rwh/students/okasaki.pdf https://www.cs.cmu.edu/~rwh/students/okasaki.pdf
- eyelidlessness 11mo agoYou might want that, I might too. But it’s outside the constraints set by the post/author. They want to establish immutable semantics with unmodified TypeScript, which doesn’t have any effect on the semantics of assignment or built in prototypes.
- cipehr 11mo agoWell said. (I too want that.) I found my first reaction to `MutableArray` was "why not make it a persistent array‽" Then took a moment to tame my disappointment and realized that the author only wants immutability checking by the typescript compiler (delineated mutation) not to change the design of their programs. A fine choice in itself.
- inbx0 11mo agoAt TypeScript-level, I think simply disallowing them makes much more sense. You can already replace .push with .concat, .sort with .toSorted, etc. to get the non-mutating behavior so why complicate things.
- edem 11mo agoFor immutability to be effective you'd also need persistent data structures (structural sharing). Otherwise you'll quickly grind to a halt.
- epolanski 11mo agoWhy would you quickly grind to a halt.
- iLemming 11mo agoWithout persistent data structures (structural sharing) - every change requires copying the entire data structure, memory usage explodes, time complexity suffers, GC pressure increases dramatically. With persistent data structures - only the changed parts are new; unchanged parts are shared between versions; adding to a list might only create a few new nodes while reusing most of the structure; it's memory efficient, time efficient, multiple versions can coexist cheaply. And you get countless benefits - fearless concurrency, easier reasoning, elimination of whole class of bugs.
- hyperrail 11mo agoAside: Why do we use the terms "mutable" and "immutable" to describe those concepts? I feel they are needlessly hard to say and too easily confused when reading and writing. I say "read-write" or "writable" and "writability" for "mutable" and "mutability", and "read-only" and "read-only-ness" for "immutable" and "immutability". Typically, I make exceptions only when the language has multiple similar immutability-like concepts for which the precise terms are the only real option to avoid confusion.
- Waterluvian 11mo agoSame reason doors say PUSH and PULL instead of PUSH and YANK. We enjoy watching people faceplant into doors... er... it's not a sufficiently real problem to compel people to start doing something differently.
- afandian 11mo ago"read-only-ness" is much more of a mouthful than "immutable"! Generally immutability is also a programming style that comes with language constructs and efficient data structures. Whereas 'read-only' (to me) is just a way of describing a variable or object.
- forty 11mo agoRead only does not carry (to me) the fact that something cannot change, just that I cannot make it change. For example you could make a read only facade to a mutable object, that would not make it immutable.
- iLemming 11mo ago> Why do we use the terms "mutable" and "immutable" to describe those concepts? Mutable is from Latin 'mutabilis' - (changeable), which derives from 'mutare' (to change) You can't call them read-only/writable/etc. without confusing them with access permissions. 'Read-only' typically means something read-only to local scope, but the underlying object might still be mutable and changed elsewhere - like a const pointer in C++ or a read-only db view that prevents you from writing, but the underlying data can still be changed by others. In contrast, an immutable string (in java, c#) cannot be changed by anyone, ever. Computer science is a branch of mathematics, you can't just use whatever words you think more comfortable to you - names have implications, they are a form of theorem-stating. It's like not letting kids call multiplication a "stick-piling". We don't do that for reasons.
- alan-jordan13 11mo ago[flagged]
- Random09 11mo agoMaking web pages even more slow. Normal people complain about it all the time, arguing that modern programmers are lazy.
- CactusBlue 11mo ago@dang likely a bot
- dredmorbius 11mo ago"@dang" is a no-op. Email mods at hn@ycombinator.com. <https://news.ycombinator.com/item?id=42177590 https://news.ycombinator.com/item?id=42177590>
- Waterluvian 11mo agoI love this idea so so much. I have maybe 100k lines of code that's almost all immutable, which is mostly run on the honor system. Because if you use `readonly` or `ReadOnlyDeep` or whatnot, they tend to proliferate like a virus through your codebase (unless I'm doing it wrong...)
- zelphirkalt 11mo agoDefinitely need purely functional data structures then. Is there a rich ecosystem for that for TypeScript?
- epolanski 11mo agofp-ts is the strictest fp implementation in typescript land. https://gcanti.github.io/fp-ts/modules/ https://gcanti.github.io/fp-ts/modules/ But the most popular functional ecosystem is effect-ts, but it does it's best to _hide_ the functional part, in the same spirit of ZIO. https://effect.website/ https://effect.website/
- hackthemack 11mo agoI am a fan of immutability. I was toying around with javascript making copies of arguments (even when they are complex arrays or objects). But, strangely, when I made a comment about it, it just got voted down. https://news.ycombinator.com/item?id=45771794 https://news.ycombinator.com/item?id=45771794 I made a little function to do deep copies but am still experimenting with it. function deepCopy(value) { if (typeof structuredClone === 'function') { try { return structuredClone(value); } catch (_) {} } try { return JSON.parse(JSON.stringify(value)); } catch (_) { // Last fallback: return original (shallow) return value; } }
- bilekas 11mo agoThere's something in here for sure, switch over to TS with strict typing and you've got generics to help you out more, at least for validation. A deep clone isn't a bad approach but given TS' typing, I don't know if they allow a pure 'eval' by default.. Still playing with this in my free time though and it's still tricky.
- hackthemack 11mo agoOne thought I recently had, since using deepCopy is going to slow things down, is if the source code for QuickJS could be changed just make copies. Then load up quickJs as a replacement for the browsers javascript by invoking it as wasm.
- bagswatchesus 11mo ago[dead]