Closed littledan closed 2 years ago
For now it does not seem to bug people I showed that to but we should try to get more feedback surely
How would @const
interact with decorators?
That's a good question, I should address that in the proposal text
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
?
I agree about not overloading const
. Need a new word.
Ok so here is what we've seen so far:
@const
- Pro: does not clash with existing syntax - Con: confusion about decoratorsconst
- Pro (to me): looks conceptually the same as c++ const pointer and const value type duality - Con: confusable with the existing const
immutable
- Pro: it's new it does not clash with existing - Con: it adds a new keyword is not necessary (using const
in declaration and expression is differenciable)#{}
/#[]
syntax - Pro: make it clear we manipulate an expression here - Con: might be confusing, especially with private fields/methodsI 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.
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.
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.
A new keyword would help in conversation (casual or educational):
Versus "I've got this immutable value".
That makes sense, I'll talk with my co-authors and see what we should do!
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.
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!
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.
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.
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 }
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
What about a final
keyword as in const point = final { x: 0, y: 10 };
? Is there a documented reason why this wouldn’t work?
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.
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).
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).
@getify wait, what's wrong with this?
var y = fixed `string`; // oops
@arxpoetica that already works by using the identifier “fixed” as a template tag.
oh oh yeah. hmm. so maybe
var y = @fixed `string`
?
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.
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.
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.
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:
@const
originally but could be @immutable
or @fixed
or @final
#
symbol as it would not clash with private field syntaxI'm going to rephrase the proposal so it is open ended between const
/#
/@const
/@immutable
/@fixed
/@final
.
Just updated the document to reflect that, consider @const
as a placeholder in the document for whatever we'll choose!
@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.
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
?
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.
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.
So, using const
to mean both final
and constant
would be too confusing, but using #
to mean both private
and constant
is not? 🤔
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]
}
I dislike the #
syntax for both private class fields and this proposal.
#
means whatsoever. A keyword like, for example, const
, final
, etc. atleast makes it clear that something is constant or immutable. The location of the keyword on the right-hand side of an assignment provides an additional clue what the keyword refers to.@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
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?
@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
#<identifier>
is private declaration or access#{ }/#[ ]
are immutable obj/arr expressionsI 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
@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]};
@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...
In flow i think that’s a closed type; doesn’t it error if you try to access a nonexistent property on it?
@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.
@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.
@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.
I would still prefer a keyword. Does any of the available reserved ones fit?
static
might, but also has a definition reuse problem (and probably an issue in class bodies somehow).
It has to be one of the reserved keywords if we do keywords. Otherwise passing the proposal will be an order of magnitude harder...
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:#{ }
/#[ ]
) might be too cryptic and confusing.@const
makes sense, since it's just like declaring a variable const, but it goes deeply through the data structure.It'd be good to continue collecting feedback to understand if these intuitions are widely shared.