tc39 / proposal-record-tuple

ECMAScript proposal for the Record and Tuple value types. | Stage 2: it will change!
https://tc39.es/proposal-record-tuple/
2.5k stars 62 forks source link

Alternative with symbols as object placeholders #273

Closed ByteEater-pl closed 2 years ago

ByteEater-pl commented 2 years ago

I'd like to suggest the following API:

optionally:

The objects are held weakly. This could be implemented in userland assuming symbols as weak map keys with one weak map per realm. (And, unless I'm missing something, with a map whose keys are symbols and values are weak references.) But providing the API in ES solves two problems discussed at the October virtual meeting: coordination and debugging.

It would be nice as a subclass of Symbol, but nope – symbols aren't objects.

ByteEater-pl commented 2 years ago

An example of the "other stuff" to be considered wrappable: Reference (ref) declarations and expressions for ECMAScript.

mhofman commented 2 years ago

I'm not sure I understand the motivation for yet another sub kind of Symbol.

One large sticking point with the "Symbol as WeakMap key" proposal is that there are 2 (arguably 3) kind of Symbols:

The first one can be implemented in different ways. No matter what, if either registered or well-known symbols are used as WeakMap key or WeakRef target, they will leak memory. This is a concern for some delegates.

What you're suggesting is to further overload type of symbols.

There are actually advantages to have symbols be considered unique values without an observable identity lifetime. They can be part of the "static structure" of a record/tuple and only the object placeholders would be the "exit doors" to the immutable graph, such that the symbol can be a sign indicating which type of thing you can find when you open the door next to it. See my "private marker" in the tagged records discourse thread.

I have since then found another use for a primitive value which clearly carries the meaning it holds an object. I am working on a list of example usages.

As for the ref as other stuff, you can put those in ObjectPlaceholder since they're objects. I would object to ref being primitive if they can be created and derefed by syntax or prototype, for the same reason as early iterations of primitive box were not acceptable.

It would be nice as a subclass of Symbol, but nope – symbols aren't objects.

What is the problem with having exactly what you described, but with typeof === 'objectplaceholder' and ObjectPlaceholder global, and leave existing Symbol as-is? (Actual name TBD)

acutmore commented 2 years ago

A similar approach here: https://github.com/tc39/proposal-record-tuple/issues/260#issuecomment-933829983 but under a new class instead of on Symbol.

The concern @ljharb raised is that this still introduces per-realm sate.

ljharb commented 2 years ago

It could certainly be cross-realm, but then you'd have a cross-realm communication channel for objects (since well-known and registry symbols are already cross-realm) which would probably break all sorts of stuff.

ByteEater-pl commented 2 years ago

@mhofman

As for the ref as other stuff, you can put those in ObjectPlaceholder since they're objects.

Oh, cool. I forgot they were objects.

What you're suggesting is to further overload type of symbols. What is the problem with having exactly what you described, but with typeof === 'objectplaceholder' and ObjectPlaceholder global, and leave existing Symbol as-is? (Actual name TBD)

It could be a separate primitive type. But I think it's a bigger change, to be avoided if an existing type can do the job well. It seems to me as if you said: numbers can be added, multiplied, negated, but why further overload them to index arrays and other structures, let's have a type which clearly carries that meaning.

Symbols are featureless (well, almost, they may have descriptions) primitives which can be cheaply minted in any quantity and only compared for equality. That's their characteristic, so once they're in the language, I think it's only natural to reuse them for new purposes where they fit.

(By the way, what I meant in my last sentence was that if symbols were objects, the symbols for wrapping objects could be constructed from a prototype whose prototype would be Symbol.prototype. So actual subclassing in the ES sense. Then the proposed methods could live on that prototype which would lead to terser and more natural invocations. (They could anyway with autoboxing, but there's no boxed variant for symbols. Which actually I believe to be a good thing.))

@acutmore

Yes, that's very relevant, and essentialy what @mhofman seems to prefer: a separate primitive type with an API for minting values with a given associated object.

The concern @ljharb raised is that this still introduces per-realm sate.

Wouldn't that be resolved by the comment you linked? I thought too about exposing the underlying weak map (could be called ObjectRegistry, but, same as above, I'm not convinced a new type is needed). But I concluded there's no apparent advantage to having more than one per realm, so it may be as well managed under the hood.

Why is per realm state considered bad though? User code creates it all the time. (And even modifies existing objects, like Array.prototype, though it's generally frowned upen in modern ES.) Presumably even more so with upcoming ShadowRealms and Compartments. And in this case it's something we believe people would often do anyway, but with slightly different names and maybe semantics, thus no interoperability, multiplied maintenance burden and so on. This clearly calls for standardization in the language. So, @ljharb, could you elaborate, please?

a cross-realm communication channel for objects

Oh, God forbid! That's why it was immediately obvious to me that the registry (whether exposed or not) should be per realm.

ljharb commented 2 years ago

User code creates it all the time; the language almost never does, with the sole exception of the identity of template objects.

ByteEater-pl commented 2 years ago

Is this almost a rule worth preserving? What value does it bring? (Don't get me wrong, I'm all for agreeing on good rules and strong guarantees. But mostly when they enable proving some useful theorems and irrefutably upholding desirable properties.)

Is it just that ES programmers aren't very used to such features? Well, this proposal already introduces powerful new semantics, so it seems to me expanding per realm state required by the spec isn't relatively an excessive chunk of novelty. And, as I mentioned, more of a similar kind are in the pipeline.

ljharb commented 2 years ago

I think cross-realm JS is a longstanding reality, and I am not on board with further constraining it by introducing any additional same-realm semantics.

ByteEater-pl commented 2 years ago

Fair point, in general. But does it apply here? The whole problem we're tackling is how to represent objects in a way that only the realm in which they're wrapped has the power to unwrap them. So same-realm semantics seems to be the very essence, which no approach can avoid except avoiding the issue. Do you believe it's worth solving in a standardized way, or would you prefer records and tuples without a blessed way to contain objects or their placeholders? Bearing in mind that many people, including library authors, indicate they have important use cases and would pursue userland implementations if ES doesn't meet this expectation.

ljharb commented 2 years ago

I have repeatedly stated that I think it's preferable that indeed any realm has the power to unwrap any realm's Box.

rricard commented 2 years ago

In order to focus on changes that are necessary to finish this proposal, we are closing issues related to follow-on proposals. You are free to continue the discussion here and reference it in other venues.