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.47k stars 62 forks source link

Collect developer feedback about the ergonomics of `#{ }`/`#[ ]` (vs alternatives `@[ ]`/`@{ }` or `{| }`/`[| ]`) #10

Closed littledan closed 2 years ago

littledan commented 5 years ago

Notice by @rricard: This issue is now only open to discuss the following alternatives: #{ }/#[ ], @{ }/@[ ], {| |}/[| |] or {| }/[| ]


Original issue text:

Most people I've talked to are pretty positive about @const in two ways:

It'd be good to continue collecting feedback to understand if these intuitions are widely shared.

rricard commented 5 years ago

For now it does not seem to bug people I showed that to but we should try to get more feedback surely

robpalme commented 5 years ago

How would @const interact with decorators?

rricard commented 5 years ago

That's a good question, I should address that in the proposal text

silverwind commented 5 years ago

I don't think it's a good idea to overload the const word, either via a const operator or @const. Why not use a new word like immutable <varname> = <expression> that acts just like var, let and const?

arxpoetica commented 5 years ago

I agree about not overloading const. Need a new word.

rricard commented 5 years ago

Ok so here is what we've seen so far:

rricard commented 5 years ago

I honestly do have a personal preference with const but if there is a strong rejection of it, I'm fine to discuss the other options.

zenflow commented 5 years ago

I don't think it's a good idea to overload the const word, either via a const operator or @const

I agree with this. This is one of the first issues I found with the proposal. It shouldn't be hard to pick a new keyword that doesn't imply equality with the existing semantics of const (an association which seems false to me).

I think immutable would be the perfect keyword (actually I would even change this in the name of this feature, since I didn't know what proposal-const-value-types could be until I read "immutable" in the description) but it's a bit long for a keyword.. not sure if that matters. immut? sealed? frozen?

I would even personally prefer #{} syntax to using the const keyword, but as @littledan said above, it's a bit cryptic, and would be hard to google.

zenflow commented 5 years ago

Con: it adds a new keyword is not necessary (using const in declaration and expression is differenciable)

It may not be necessary for compilers to parse the code properly, but I think it would be helpful to make things clear to developers.

To draw a parallel: the let keyword was not "necessary".. we could have reused the if keyword, and statements like if foo = 3 would have been compiled correctly, but it's not optimal for developers for obvious reasons.

vweevers commented 5 years ago

A new keyword would help in conversation (casual or educational):

Versus "I've got this immutable value".

rricard commented 5 years ago

That makes sense, I'll talk with my co-authors and see what we should do!

getify commented 5 years ago

I am deeply, deeply troubled by the conflation of const here.

Speaking as a frequent teacher of these exact topics (const declarations vs immutable data structures), I can confidently say that most learners will not find something like const obj = const { a: 1, b: 2 }; intuitive, but will on the contrary be even more confused and unlikely to understand the differences.

The claims in the README that const x and const { .. } are obviously distinct / orthogonal do not track at all with my experience.

rricard commented 5 years ago

This is completely relevant and I think I can definitely put aside my affinity to what C++ would do (as a matter of fact const const in C++ is a pain to teach) so let's find a replacement!

getify commented 5 years ago

Just to be clear, I love the feature. I want JS to have immutable data structures. I just don't like calling them "Constant Objects" or using const to make them.

FWIW, "immutable" may or may not be the right word, either. Generally, immutable data structures (in userland) mean data structures that allow structured mutations (via sort of copy-on-mutate behavior). If these are intended to have something like that -- awesome! -- then "immutable" is a good word.

But if not, if they're more like a deeply frozen object, then I think "frozen" or "read-only" are the more appropriate term than "constant" or "immutable".

EDIT:

After some clarifications in various tweet threads, I actually think "fixed" is better than "frozen" / etc.

EDIT 2:

if we go in the direction of "immutable", I like imm as a short useful keyword.

ljharb commented 5 years ago

Since Object.isFrozen exists along with the concept of "frozen", for whatever term we choose here I'd also expect an API predicate to determine whether the term applies - ie, Object.isFixed, etc.

Jamesernator commented 5 years ago

An alternative syntax (and naming) could just be to use two decorators or tagged object literals and use the fairly popular "tuple" and "record" names:

e.g. With decorators:

const vector = @Tuple [3, 4]
const point = @Record { x: 10, y: 20 }

e.g. With tagged literals:

const vector = Tuple#[1, 2]
const point = Record#{ x: 0, y: 10 }
ryan-codingintrigue commented 5 years ago

I agree that fixed seems like a good option. Neither const nor immutable feel right because individually they only really describe one aspect of the behavior.

I can see Object.isFixed being a tad odd given the existence of Number.isFixed but the namespace should provide enough distinction

kleinfreund commented 5 years ago

What about a final keyword as in const point = final { x: 0, y: 10 };? Is there a documented reason why this wouldn’t work?

getify commented 5 years ago

One possible issue with the keyword approach is using this feature with primitives, specifically a template literal, since that would be ambiguous with template tags, or with regexes:

var x = fixed "string";  // ok
var y = fixed `string`;  // oops
var z = fixed / a / gi;  // oops

There's no advantage of using these with primitives, per se, but I also think it might be strange (with a keyword rather than syntax) to explain that it can only be used with array/object values and not other primitives.

The mental model I'm using here is that a value-type is basically a bit like defining some arbitrary array/object as a single primitive value unit. So it'd be a shame if this feature couldn't reflect that symmetry.

kleinfreund commented 5 years ago

One possible issue with the keyword approach is using this feature with primitives, specifically a template literal, since that would be ambiguous with template tags, or with regexes:

Would that only be an issue for a keyword (e.g. `fixed) if it’s already in use in a lot of code (e.g. in a tagged template literal)?

There's no advantage of using these with primitives, per se, but I also think it might be strange (with a keyword rather than syntax) to explain that it can only be used with array/object values and not other primitives.

I agree.


It seems like there are two fundamental questions worth investigating: What to call it (e.g. const, fixed, final, immutable, etc.) and what kind of language structure should be used (e.g. keyword, at-prefixed name, the weird bracket syntax).

TehShrike commented 5 years ago

A note on final – that keyword is used in Java to indicate variables that will not be reassigned (the equivalent of const variables in JS right now).

arxpoetica commented 5 years ago

@getify wait, what's wrong with this?

var y = fixed `string`;  // oops
ljharb commented 5 years ago

@arxpoetica that already works by using the identifier “fixed” as a template tag.

arxpoetica commented 5 years ago

oh oh yeah. hmm. so maybe

var y = @fixed `string`

?

hormesiel commented 5 years ago

Disclaimer: I probably don't know what I'm talking about but I'll give it a try anyway.

Among the following syntaxes :

// const
const obj = const { a: 1, b: 2 };

// fixed
const obj = fixed { a: 1, b: 2 };
fixed obj = { a: 1, b: 2 };

// final
const obj = final { a: 1, b: 2 };
final obj = { a: 1, b: 2 };

// frozen
const obj = frozen { a: 1, b: 2 };
frozen obj = { a: 1, b: 2 };

// immutable
const obj = immutable { a: 1, b: 2 };
immutable obj = { a: 1, b: 2 };

immutable seems like the most intuitive and straightforward solution to me. It's so obvious what it does you don't even need to explain it! Which is not the case for most of (if not all) the other proposed keywords / syntaxes.

Also, I prefer the RHS solution because it means that you can do :

let obj = immutable { a: 1, b: 2 };
obj = null; // OK
obj = { ... }; // OK
obj = immutable { ... }; // OK

function fn() {
  // ...
  return immutable { ... }; // OK
}

obj = fn(); // OK

and reuse the obj variable, while the LHS version could lead to unexpected results in some cases and would have annoying limitations :

immutable obj = { a: 1, b: 2 };
obj = null; // ???
obj = { ... }; // ???
obj = immutable { ... }; // Not possible just like you can't do `obj = const { ... };`

function fn() {
  // ...
  return immutable { ... }; // Not possible just like you can't do `return const { ... };`
}

Now if a 9-letter keyword is too long I think immut could be a great fit too.

shannon commented 5 years ago

Please forgive me if I am not fully understanding this proposal but what's the advantage of explicitly assigning the structure to a variable?

If a keyword is used could you not just go the way of function?

fixed map1 {
    a: 1,
    b: 2,
    c: 3,
};

const map2 = map1 with .b = 5;

assert(map1 !== map2);
assert(map2 === fixed { a: 1, b: 5, c: 3}); //<- something like an anonyomous fixed

If this were the case I would think something like struct would make sense as a keyword but I'll admit I don't know the full implications of this when compared to other languages.

shannon commented 5 years ago

I also don't know what the possibility if it being "called" like a function as well instead of using with

const map2 = map1{ b: 5 };
//or with an array
const array2 = array1{ 0: 'x' }

As far as ergonomics go that's pretty easy. I know this doesn't say much about the understand-ability, teach-ability, or possible confusion when reading at a quick glance.

rricard commented 5 years ago

Alright everyone, we do appreciate the feedback but let's keep that issue on point: the keyword.

I don't think the const keyword will be accepted, however adding a new keyword can have bad effects, notably parsing existing javascript. Reusing an existing keyword in a different situation is a good way out of that issue (you can't name a variable const, same with with).

From there if we do not go with const we'll have two other solutions:

I'm going to rephrase the proposal so it is open ended between const/#/@const/@immutable/@fixed/@final.

rricard commented 5 years ago

Just updated the document to reflect that, consider @const as a placeholder in the document for whatever we'll choose!

shannon commented 5 years ago

@rricard

Reusing an existing keyword in a different situation is a good way out of that issue

This is part of the confusion for me. Using const or any keyword in two different ways is strange to me and I can see it being a constant source of confusion. Making searching the web for answers around the feature very frustrating when results will include both cases.

As for using the decorator syntax. Would @const actually be a decorator? Or is it just similar syntax? Can it be used everywhere that a decorator can be used?

Personally I don't think this is the right approach because decorators have been in flux for quite sometime, having gone through multiple major reworks. I don't think this feature should be tied to the future of that syntax. Moreso, if it's not actually a decorator, I would avoid a similar syntax to avoid the confusion that it might be.

Just my thoughts as an outside developer.

vweevers commented 5 years ago

however adding a new keyword can have bad effects, notably parsing existing javascript

Could that be solved the same way as async/await, which doesn't clash with e.g. var async = foo?

ljharb commented 5 years ago

it kind of does, it being paired with otherwise invalid syntax if treated as an identifier - a normal or arrow function - allows it to work.

rricard commented 5 years ago

We decided for now to go with #{ } and #[ ] instead of a keyword or decorator based syntax. @rickbutton is working on it right now to have this reflected in the proposal.

We'll collect feedback once the examples are changed with that syntax.

jromero2k commented 5 years ago

So, using const to mean both final and constant would be too confusing, but using # to mean both private and constant is not? 🤔

getify commented 5 years ago

What will it look like if you create one of these values and assign it to a class private-field?

class Foo {
   #x = #[1,2,3] 
} 
kleinfreund commented 5 years ago

I dislike the # syntax for both private class fields and this proposal.

rricard commented 5 years ago

@jromero2k Given the context of uses: it doesn't feel too crazy, #<identifier> and #{ }/#[ ] are fairly different visually


@getify That's indeed what you'll get.


@kleinfreund I understand why you might not be a fan of # but the alternatives are not that clear either

I dislike the # syntax for both private class fields and this proposal.

You're not the only one to not like it for private fields but this has been discussed over and over again and no viable alternative to it has been found, I'll just link the FAQ: https://github.com/tc39/proposal-class-fields/blob/master/PRIVATE_SYNTAX_FAQ.md

It’s not clear to beginners what # means whatsoever.

That we will need to do a good job at explaining it and I'm sure we'll manage to get tons of good resources online by the time it gets into the language.

A keyword const, final, etc.

const was clearly rejected by the community, I'm not going to pursue it more. Adding any other keyword will make this proposal less likely to be accepted as existing identifiers in existing js across the web might clash with new keywords

Using it for two different concepts just increases this potential confusion.

Reusing it for two different concepts is actually an advantage, we don't need to reserve a new keyword or symbol in the language, since the language is getting pretty busy, we might need to reuse something that is already there, we tried to reuse const for the same reason

getify commented 5 years ago

How will you take a regular array already in a variable and turn it into this "immutable" form? Is it like?

x = [1,2,3]; // regular
y = #x;  // "casting" as immutable?

Or will it be with the with keyword in some way?

rricard commented 5 years ago

@getify in this example, #x will do a type error: the private field #x does not exist

If you want to cast you can spread x:


x = [1,2,3]; // regular
y = #[...x];  // "casting" as immutable
rricard commented 5 years ago
rricard commented 5 years ago

I realize that we will never make everyone happy but nevertheless I'm open to alternatives as long as we do not need to invent new keywords

shannon commented 5 years ago

@rricard this really limits future additions to private fields. I (among others) really pushed for dynamic access in private fields. It was suggested that this could be a follow on proposal. With the most obvious approach being #['foobar'].

Could something like this work?

const x = {{ a: 1, b: 2 }};
const y = {[1, 2, 3]};
rricard commented 5 years ago

@shannon I have to check but I remember that private fields has been designed in such a way dynamic access is impossible to add later (at least I can see that being hard to do with the slot implementation in the engines) so I'm afraid that ship sailed. Regardless it's a good point.

I'm not knowledgeable enough in how one would parse what you proposed but I can keep it in mind next time I talk to someone who knows how to parse languages.

Another option we discussed with @littledan and @BrendanEich is {| |} and [| |] that is very Ocaml/flow-ish. I'd like to hear from someone in the flow team to know how much of a disruption it would be however...

ljharb commented 5 years ago

In flow i think that’s a closed type; doesn’t it error if you try to access a nonexistent property on it?

kleinfreund commented 5 years ago

@rricard

const was clearly rejected by the community, I'm not going to pursue it more.

I pointed const out as an example for a keyword and was not advocating for it. My point was about keywords in general and that they are easier to grasp by beginners with regard to what they mean.

Adding any other keyword will make this proposal less likely to be accepted as existing identifiers in existing js across the web might clash with new keywords

This is understandable, but should not a reason to flat-out dismiss considerations for new keywords. I’m aware that there exist cases where a new keyword (e.g. fixed) might class with an identifier (e.g. for a tagged template literal function). I’m also not very familar with how easy or not it is to identify how much existing code clashes in such a way, but there may be a keyword that only introduces little breakage.

Reusing it for two different concepts is actually an advantage, we don't need to reserve a new keyword or symbol in the language, since the language is getting pretty busy, we might need to reuse something that is already there, we tried to reuse const for the same reason

From the perspective of proposal work and language design, sure, this is understandable. What is more important, in my opinion, is the value for developers. Reusing existing syntax like that is not a direct advantage for developers.

rricard commented 5 years ago

@ljharb

In flow i think that’s a closed type

Yes, so it's a very different concept to what we want to do.

doesn’t it error if you try to access a nonexistent property on it?

That's an excellent question we might need to answer! I don't know, I'd say yes but I'd have to think more about it.

rricard commented 5 years ago

@kleinfreund

I agree that developer experience is important but unfortunately js has a lot of baggage that we can't discard. I have been working with tc39 members to make that proposal realistically implementable into the language and the proposal has much better chance advancing if we reuse.

but there may be a keyword that only introduces little breakage.

Even one breakage is a no-go: that means that the first browser implementing the feature will have to explain to the users that the obscure website they used to be able to access why now it does not work. For most users unfortunately, the website did not respect the standards is not a good excuse.


I'm gonna be honest my goal is to ship the feature and the concept itself, the syntax does not matter much in itself to me. We can bikeshed on it (with good reasons, developer ergonomics are a good reason) but in the end we're going to choose the option that is not rejected by most developers and that plays nice with the legacy of the language. But I have to part ways with the idea that the syntax will make everyone happy.

silverwind commented 5 years ago

I would still prefer a keyword. Does any of the available reserved ones fit?

ljharb commented 5 years ago

static might, but also has a definition reuse problem (and probably an issue in class bodies somehow).

rricard commented 5 years ago

It has to be one of the reserved keywords if we do keywords. Otherwise passing the proposal will be an order of magnitude harder...