upvote
The worst are methods that both mutate and return values.

I know this gets into a complex land of computer science that I don’t understand well, but I wish I could define in TypeScript “any object passed into this function is now typed _never_. You’ve destroyed it and can’t use it after this.” Because I sometimes want to mutate something in a function and return it for convenience and performance reasons, but I want you to have to reason about the returned type and never again touch the original type, even if they are the same object.

reply
> any object passed into this function is now typed _never_. You’ve destroyed it and can’t use it after this.

That is basically what affine types are. Once the value is "consumed" it can't be used again.

In rust, this is expressed as passing an "owned" value to a function. Once you pass ownership, you can't use that value anymore.

And having used it in rust, I wish more languages had something like that.

reply
> And having used it in rust, I wish more languages had something like that.

Same. I'm at the point where I feel like copy-by-default semantics are one of the ancient original sins of programming languages. Single-ownership is so, so useful, and it's trivial to implement and not at all difficult to understand (especially compared to something like Rust's borrow checker).

reply
> but I wish I could define in TypeScript “any object passed into this function is now typed _never_.

Having explicit language to differentiate between pass by reference and pass by value avoids this confusion. It requires a little more thought from the programmer but it’s really minimal once you internalize it.

Rust takes this a step further with an explicit ownership and borrowing model. The compiler will refuse your code if you try to write something that that violates the borrow checker. This is endlessly frustrating to beginners but after adapting your mind to ownership safety you find yourself thinking in the same way in other languages.

I always found real-world JavaScript codebases frustrating because there was so much sharing that wasn’t entirely intentionally. It only got fixed when someone recognized a bug as a result.

reply
Yeah exactly. That's what I've loved about Rust and hated about real-world JS. I end up having to reason about an entire case that might not be real at all: does this function mutate what I'm passing it? Should I eagerly deep copy my object? UGH.
reply
Just call "Object.freeze()" before "return" in your function.
reply
That only goes one level deep so it’s not much of a guarantee
reply
Rust ownership model ("stacked borrows" I believe it's called) is basically this
reply
Single-ownership ("affine types") is a separate concept from a borrow checker. Your language doesn't need a borrow checker (or references at all) to benefit from single-ownership, though it may make some patterns more convenient or efficient.
reply
rust would be pretty unusable without references. affine lambda calculus isn’t even turing complete. however, you’re right that a borrow checker is unnecessary, as uniqueness types (the technical term for types that guarantee single ownership) are implemented in clean and idris without a borrow checker. the borrow checker mainly exists because it dramatically increases the number of valid programs.
reply
I think it's the other way around - he's projecting rust as what he wants
reply
What you are describing is linear (or affine) types in academic parlance, where a value must be used exactly (or at most) once, e.g., being passed to a function or having a method invoked, after which the old value is destroyed and not accessible. Most common examples are prolly move semantics in C++ and Rust.
reply
ruby has the convention of ! for dangerous destructive or mutating methods. This is something that I wish would spread around a bit.

For example:

# Original array

array = [1, 2, 3]

# Using map (non-destructive)

new_array = array.map { |x| x * 2 }

# new_array is [2, 4, 6]

# array is still [1, 2, 3] (unchanged)

# Using map! (destructive)

array.map! { |x| x * 2 }

# array is now [2, 4, 6] (modified in-place)

reply
> convention

Is the keyword. Anything that should never be broken isn’t a convention. There’s no better convention than compiler error.

reply
Because Ruby is a dynamic language which mutates state. That isn't considered wrong or bad in those kinds of languages, just a way to make sure the programmer knows they're doing that. Not every PL tries to live up to the ideals of Haskell.

If you don't want an object mutated in Ruby, you can freeze it.

reply
I don't think they're saying it shouldn't be possible to mutate arguments, just that the ! convention should be enforced. The Ruby runtime could, for instance, automatically freeze all arguments to a function that doesn't end with a !. That way all code that correctly follows the mutation naming convention will continue to work, and any development who doesn't know about it will quickly learn when they try to mutate an argument and get an error. Ideally a helpful error telling them to add the !.
reply
That's really interesting. Although my worry is the freezing having bad effects down the line after the function returns.

  a = [1, 2]
  
  def check_this(arr)
    raise "doesn't start with 1" unless a.first == 1
  end

  check_this(a)
  
  a << 3 # Raises "FrozenError (can't modify frozen Array)" because check_this froze `a`
Now, if you could temporarily freeze, and then unfreeze only the ones you froze, that could be really cool.
reply
> Now, if you could temporarily freeze, and then unfreeze only the ones you froze, that could be really cool.

Is that a missing feature in Ruby? You can't have a frozen reference to an object while retaining unfrozen ones in another scope? That's too bad.

reply
> The worst are methods that both mutate and return values

Been bitten a few times by Array.sort().

Luckily there’s Array.toSorted() now.

reply
deleted
reply
If you want to upset people on the internet tell them that JavaScript is strongly typed, immutable, and everything is passed by value. Which is true. You can change member values though, which is the footgun.
reply
This is possible with the asserts x is y pattern no?

https://www.typescriptlang.org/play/?#code/C4TwDgpgBAYg9nKBe...

reply
I think the sticking points are:

1. You cannot return anything (say an immutable result that has consumed the input)

Okay, so don't return anything, just mutate the original. Except:

2. You cannot mutate the original, return nothing, but the mutated original isn't a subset of the original. For example: https://www.typescriptlang.org/play/?#code/GYVwdgxgLglg9mABB...

reply
Hmm, I see, yes it's quite limited.
reply
These LLM spambots are getting so good they're at the top of many discussions now, and people are none the wiser. Sad, but it was predictable.

Please look into its comment history and flag this. Not that it will solve anything.

reply
I hear you, but this also affects the good discussion below it.
reply
> The other half come from the implicit local timezone conversion in the Date constructor.

Outlook at that issue even in their old C++ (I think) version.

You're in London, you save your friend's birthday as March 11th.

You're now in SF. When is your friend's birthday? It's still all-day March 11th, not March 10th, starting at 5PM, and ending March 11th at 5PM.

reply
If your friend lives in London it may be useful to have that associated timezone so that you can be sure to message them that day in their timezone. They might better appreciate a message from SF sent on March 10th at 9PM "early that morning in London" than March 11th at 9PM "a day late".

A lot of that gets back to why Temporal adds so many different types, because there are different uses for time zone information and being clear how you shift that information can make a big difference. (A birthday is a PlainDate at rest, but when it is time to send them an ecard you want the ZonedDateTime of the recipient's time zone to find the best time to send it.)

reply
His birthday is always all day. The question is where he is. If he travels to Japan his birthday won't change - even if he was born late at night and thus it would be a different day if he was born in Japan.
reply
The other fun ones are daily recurring events for e.g. taking some medication. You take the last one at 10pm before going to bed. You go on a trip which moves you three hours east. Now your calendar helpfully reminds you to take your meds at 1 am.
reply
Immutability is underrated in general. It's a sore point every time I have to handle non-clojure code.
reply
Given the ubiquity of react, I think immutability is generally rated pretty appropriately. If anything, I think mutability is under-rated. I mean, it wouldn't be applicable to the domain of Temporal, but sometimes a mutable hash map is a simpler/more performant solution than any of the immutable alternatives.
reply
Props data passed to React itself isn't immutable which is probably one of the missing bricks.

React only checks references but since the objects aren't immutable they could have changed even without the reference changing.

Immutability also has a performance price which is not always great.

reply
Yes, you can mutate props. But no, it's probably not going to do what you want if you did it intentionally. If react added Object.freeze() (or deepFreeze) to the component render invoker, everything would be the same, except props would be formally immutable, instead of being only expected to be immutable. But this seems like a distinction without much of a difference, because if you just try to use a pattern like that without having a pretty deep understanding of react internals, it's not going to do what you wanted anyway.
reply
React doesn’t really force you to make your props immutable data. Using mutable data with React is allowed and just as error prone as elsewhere. But certainly you are encouraged to use something like https://immutable-js.com together with React. At least that’s what I used before I discovered ClojureScript.
reply
Immutability is often promoted to work around the complexity introduced by state management patterns in modern JS. If your state is isolated and you don't need features like time travel debugging, mutable data structures can be simpler and faster. Some so-called immutable libraries use hidden mutations or copy-on-write, which can actually make things slower or harder to reason about. Unless you have a specific need for immutability, starting with mutable structures is usually more sane.
reply
Well, mutability is the default, and React tries to address some of the problems with mutability. So React being popular as a subecosystem inside a mutable environment isn't really evidence that people are missing out on the benefits of mutability.

Though React is less about immutability and more about uni-directional flow + the idiosyncrasy where you need values that are 'stable' across renders.

reply
Yeah... I pretty early in my career firmly cemented on a couple things with date-times. It's either a date-time + zone/location detail or always seconds from unix epoc or UTC in iso-8601 style (later JSON's adopted default) across the wire. Since most systems and JS convert pretty easily between UTC and local.

Same for storage details. I started using the 8601 style mostly in file/log naming so they always sorted correctly, this kind of carried over into my code use pre-dating JSON spec.

Doing the above saves a lot of headaches... I'd also by convention use a few utility scripts for common formatting and date changes (I use date-fns now mostly), that would always start with dtm = new Date(dtm); before manipulation, returning the cloned dtm.

reply
It is not just in time keeping that mutable shared state is an issue, I have seen problems arising from it elsewhere as well in Python especially, but also in C and C++. Probably because Python is pass by reference implicitly, while C and C++ makes pointers/references more explicit, thus reducing the risk of such errors in the code.

There a few schools of thought about what should be done about it. One is to make (almost) everything immutable and hope it gets optimised away/is fast enough anyway. This is the approach taken by functional languages (and functional style programming in general).

Another approach is what Rust does: make state mutable xor shared. So you can either have mutable state that you own exclusively, or you can have read only state that is shared.

Both approaches are valid and helpful in my experience. As someone working with low level performance critical code, I personally prefer the Rust approach here.

reply
One of the first things I learnt to appreciate in C++ already during its C++ARM days was the ability to model mutability.

Naturally there are other languages that do it much better.

The problem is that it still isn't widespread enough.

reply
When I write JavaScript, I make as many things immutable as I can. Sometimes it adds verbosity and leads to less efficient computational patterns, but overall I believe I run into far fewer bugs that are hard to make sense of. There are things about the design of Temporal I don't really like, but immutability was a solid move.

What I don't understand is why they had to make string formatting so rigid. Maybe it has to do with internationalization? I'd have liked if it included a sort of templating system to make the construction of rendered date-time strings much easier.

reply
> What I don't understand is why they had to make string formatting so rigid. Maybe it has to do with internationalization? I'd have liked if it included a sort of templating system to make the construction of rendered date-time strings much easier.

I think Temporal takes the right approach: toString() is the (mostly) round-trippable ISO format (or close to it) and every other format is accessible by toLocaleString(). In Python terms, it is a bit like formally separating __repl__ and __str__ implementations, respectively. Date's toString() being locale-dependent made it a lot harder to round-trip Date in places like JSON documents if you forgot or missed toISOString().

Temporal's various toLocaleString() functions all take the same Intl.DateTimeFormat constructor parameters, especially its powerful options [1] argument, as Date's own toLocaleString() has had for a long while and has been the preferred approach to locale-aware string formatting.

[1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

reply
The problem is that sometimes you want a very specific format, not a locale-based format. This currently still has to be implemented in userland [1].

[1] https://github.com/js-temporal/proposal-temporal-v2/issues/5

reply
I remember the first time I got in touch with Elixir and immutability as a core principle. It changed the way I wrote JavaScript since
reply
The formatting decision is intentional and documented in the proposal: toString() is meant to be the round-trippable ISO representation, not a display string. For locale-aware output, toLocaleString() accepts the full Intl.DateTimeFormat options bag, which covers most real-world display needs.

For non-locale custom formats ("YYYY-MM-DD" style), there is a gap - Temporal deliberately does not ship a strftime-style templating system. The design rationale was that custom format strings are a source of i18n bugs (hard-coded separators, month/day order assumptions, etc). The idea was to push display formatting toward Intl so locale handling is correct by default.

In practice you can cover most cases with something like:

  const d = Temporal.PlainDate.from('2026-03-11')
  const parts = d.toLocaleString('en-CA', {year:'numeric',month:'2-digit',day:'2-digit'})
for YYYY-MM-DD (Canadian locale uses that order). Fragile, but workable.

The V2 issue Dragory linked is exactly tracking this - custom format pattern support is on the backlog. For now, date-fns or luxon work fine alongside Temporal if you need templated strings.

reply
They seem to have taken it from Joda time that revolutionized time in java 10+ years ago. Sadly no mention of Joda.
reply
Also no mention of Stephen Colebourne, author of Joda and the JSR-310 spec (the basis of java.time), the one person you should always stop and listen to when he says you are doing something wrong with time.

Alas, this post is not the only time the TC39 people ignored him.

reply
I think that actually may be the MOST appreciated design decision in Temporal ;) either way, I'm also a big fan
reply
Flagged because account is bot operated (posted again 2h40m later with same general comment)
reply