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.48k 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.

ljharb commented 5 years ago

That doesn’t mean it has to be - it might just mean that the work is required.

iow, the goal shouldn’t be to shoot for what’s easier to advance, it should be to shoot for the best thing.

vweevers commented 5 years ago

This language feature is worth the wait!

getify commented 5 years ago

Did we get any clarity on whether this feature (keyword or syntax) can be used against simple primitives like 42?

If so, and if it's not a reserved keyword, or using the @ symbol as a delimiter, I don't see how we can get around the ambiguity that I mentioned in an earlier message, regarding tagged template literals and regular expressions.

JustFly1984 commented 5 years ago

Why not calling arrays => lists, if we are calling objects => maps?

There is no tuples in JS yet.

How about immutable data structures in context of TypeScript and Set Theory? for me TypeScript is pretty much main requirement, for fairly big project.

There should be new primitive types, and api for those types should be compatible with existing types.

# sign already used for private members in classes, so it also could be confusing.

Javascript has Map and Set already, why not using the same approach as sadly unmaintained immutable.js takes? List has same methods on list as Array, and Map has same api as native JS Map has.

For me the main benefit of immutable data structures - persistence. Plain immutability has only protective features, but if it has no persistence, it has no performance benefit.

There is issues with immutable.js and typescript inference for setIn(['path', 'path']) and getIn(['path', 'path']) declarations, if this could be implemented natively in JS and TS, that could solve a lot of pain.

The immer.js approach is not supporting persistence at all, so that is not really good target for reference.

zenflow commented 5 years ago

Did we get any clarity on whether this feature (keyword or syntax) can be used against simple primitives like 42?

If so, and if it's not a reserved keyword, or using the @ symbol as a delimiter, I don't see how we can get around the ambiguity that I mentioned in an earlier message, regarding tagged template literals and regular expressions.

@getify What would be the point of using this feature with simple primitives like 42? Numbers, strings, booleans, etc, are already immutable.

Can we just have this feature apply only to literal arrays and objects? Then there is no ambiguity:

immut [1, 2, 3] // <-- immutable/constant array
immut {a: 1, b: 2, c: 3} // <-- immutable/constant object
immut `abc` // <-- tagged template literal, with tag function `immut`
zenflow commented 5 years ago

After writing the above message, I realize that even if this feature is applied only to array and object literals, there is another ambiguity that using a new keyword would introduce:

immut [0]

Is it an immutable array with 0 as it's only element? Or is it a reference to the first element of array immut?

getify commented 5 years ago

@zenflow

Indeed, even these are ambiguous:

immut [1,2,3]
getify commented 5 years ago

What would be the point...

There's no functional reason for it... only one of consistency for mental models. We're making objects and arrays that behave like their primitive counter-parts (can't be changed, are compared by value instead of reference, etc).

It would be nice to be able to teach this with this sort of symmetry consistency:

var x = 42;
var y = #42;
var z = #[ 42 ];

As for not needing to support things which are "pointless", JS already has a lot of those (and I would argue, they're good for consistency of mental models), such as:

[] = [1,2,3];   // empty destructuring
({} = { a: 1, b: 2, c: 3});   // ditto

var x = null;
var y = { ...x };   // empty object spread

// etc
zenflow commented 5 years ago

@getify I see what you mean about "pointless" things. The empty destructuring example is a good one.

But I would argue that applying this feature to primitives would actually be introducing an inconsistency (but in our js code, not in the spec). Since we cannot change the spec so that primitives (which are always immutable) must always have the immutable symbol/keyword, we would then have two (inconsistent) ways of expressing the exact same (very simple) thing. E.g. 42 and #42. I think that would cause confusion. I think it would be better if this feature wasn't applied to primitives and people just understood that primitives are always immutable, because (a) consistency in the js code we read & write, and (b) people should understand anyways that a primitive literal like 42 (without the keyword/symbol) is immutable (or they will run into confusion in any case).

zenflow commented 5 years ago

@getify I'm not sure but it might be worth opening another issue for that topic

ljharb commented 5 years ago

What about regex literals? regexes are supremely mutable, what with .compile, and it’d be great to have one that wasn’t.

arxpoetica commented 5 years ago

Seeing y'all write out immut above in actual use cases, my vote now heavily slants in that direction for a word. The intent is readable and use case clear.

JustFly1984 commented 5 years ago

In this case we do not need special syntax like #{}

But how about Map and Set? What if programmer assigns Map or Set to ‘immut’? Or even basic types?

svieira commented 5 years ago

I'm a fan of # because the syntax can also be used for tagged constructors - Tuple#[1, 2, 3], Set#["not", "bad"], Map#{ key: value, pairs, [123]: "is a number" }, CustomTypes#["why", "not", "?"].

I would recommend adding global functions that also construct these types (ImmutableObject, ImmutableArray) so the parallels can be made clear (#{} may also be spelled ImmutableObject#{}, or even ImmutableObject({}) for those who want to spell it that way).

reklatsmasters commented 5 years ago

I honestly do not like any new symbols / words in js. It turns js into haskel or perl.

This will be easy to understand peoples from C/C++.

topaxi commented 5 years ago

As a developer I wouldn't care much about syntactic immutable things for a first iteration. Give me an immutable "object", "array" which create new objects on "mutation".

let immutableArray = ImmutableArray.of(1,2,3);
immutableArray = immutableArray.push(4);

or similar things for objects.

Also having operations as syntax make the usage of this proposal quite awkward with using higher order functions like map/filter and the like. I'd much prefer proper functions or methods.

bobrosoft commented 5 years ago

Again that # symbol... please don't... why we can't have beautiful, "easy to read" language, not a cryptic one? immutable/immut is my choice. I wondering how # ended up in final candidate for private methods instead of simple private, world goes nuts..

ljharb commented 5 years ago

Because the private keyword is much much less simple, because it carries lots of conceptual baggage from other languages.

Regardless of aesthetic preferences (i find # to be beautiful), i don’t think it’s appropriate to repeat past mistakes of creating more than one unrelated meaning for the same token (like + meaning addition but also string concatenation).

Ixrec commented 5 years ago

The "why is private field syntax # instead of private?" question is already covered in exhaustive detail over at https://github.com/tc39/proposal-private-fields/blob/master/FAQ.md and the issues on that repo.

We should avoid rehashing that debate here, not only for the obvious reason that it was highly contentious, but also because most of the constraints on private fields described in that FAQ simply do not apply to this const/immutable value types proposal.


On what the syntax for const/immutable value types should be, I have no strong opinion. immut does seem like the platonic ideal, but IIUC we simply aren't allowed to introduce any new keywords. # and const and so on all seem about equally underwhelming. Is an @immut decorator still on the table?

rricard commented 5 years ago

To everyone, we added a FAQ section on that question: https://github.com/rricard/proposal-const-value-types#why--syntax-what-about-an-existing-or-new-keyword

Let's now keep the thread focused on what is possible and if you want, propose alternatives in that realm. By that, I mean that trying to add a new keyword such as immut, even if that would be a great and nice-looking syntax, is not possible and I'd like to avoid wasting our collective time around this.


That being said, bringing back decorators on the table is an excellent idea @lxrec, I'll try to think about it. I think it is a possible option, but I can see some possible pushback though with the fact that it is a decorator and those have not been universally loved...

At this point though, I'm pretty sure that an universally loved solution is not possible! But we can try to reach the right balance.

getify commented 5 years ago

@rricard

To everyone, we added a FAQ section...

Thanks for adding that section. Could we possibly amend it to include why non-reserved keywords are an ambiguity no-go, as explained up thread?

For example:

var x = immut [4];

Does that mean an immutable array with 4 in it, or does it mean accessing index 4 on an array already named immut?

No matter what non-reserved keyword we pick, that ambiguity will always exist, and thus I think that's a non-starter. We either need a reserved keyword (none fit), a decorator-like syntax, or just pure punctuation syntax.

rickbutton commented 5 years ago

@getify fantastic idea. I'll add an example of why introducing a new keyword is a hazard, just as you have there.

Ixrec commented 5 years ago

Just to make sure our brainstorm covers all the possibilities... are keywords with sigils in them an option?

let x = #immut [4];
let x = immut# [4];
let x = @immut [4]; // magic decorator?
let x = immut@ [4];
let x = |immut| [4];
let x = %immut [4];

(obviously not all of these would be serious contenders even if they are theoretically allowed)

ljharb commented 5 years ago

Yes, anything that couldn't already be a valid identifier (or valid syntax in front of an object or array literal) would not create ambiguity.

arxpoetica commented 5 years ago

trying to add a new keyword such as immut, even if that would be a great and nice-looking syntax, is not possible...

I think you meant would be formidable. It's definitely possible. In that light, my vote is for doing the right thing, not necessarily something easier.

That said, I'll stand back and let the chips fall since I have less authority on what should happen.

ljharb commented 5 years ago

@arxpoetica it's pretty impossible, because JS won't break the web.

ryan-codingintrigue commented 5 years ago

@ljharb var await was valid until ES6 right? 🤔

Maybe I’m missing the point, but if something is important enough (and I genuinely think this proposal provides a substantial benefit to the language) why wouldn’t we at least consider adding it to future reserved words first?

ljharb commented 5 years ago

@ryan-codingintrigue it is still valid now, just not in an async function. nothing is important enough to break the web.

ryan-codingintrigue commented 5 years ago

@ljharb Ahhh. That makes sense - I was wondering why this hadn’t been brought up before. thanks!

bobrosoft commented 5 years ago

That's why I'm using TypeScript with private etc %) And hope there will be proper beautiful immut which transforms later during transpile in any ugly(sorry)-looking stuff which will be chosen here.... Actually there's readonly etc kind of stuff already, maybe even will not be truly needed (I know those are different things).

silverwind commented 5 years ago

there's readonly etc kind of stuff already, maybe even will not be truly needed

I'd say the closest relative would be Object.freeze, but that will only freeze shallow properties, not deep ones. Maybe we want to extend that line of thought and add a Object.deepFreeze or {deep: true} option? Not as elegant as new syntax but very compatible.

bobrosoft commented 5 years ago

I'd say the closest relative would be Object.freeze, but that will only freeze shallow properties, not deep ones. Maybe we want to extend that line of thought and add a Object.deepFreeze or {deep: true} option? Not as elegant as new syntax but very compatible.

Sounds better than to have some new ugly syntax stuff imho, really.

JustFly1984 commented 5 years ago

@silverwind deep freeze is great proposal in itself!

Although, we need some kind of immutable data structure with persistence.

JustFly1984 commented 5 years ago

Why can't we have new ImmutableMap() and new ImmutableList() the same way as we have new Map() and new Set() ? we could add new methods on Object and Array: {}.toImmutableMap() and [].toImmutableList()

no new syntaxis involved, no magic

Any operation on immutable Map or List, just returns new immutable data structure.

Also would be helpful to have Object.isImmutable() to check if data structure is immutable.

BTW Map, Set, WeakMap and WeakSet do not have short syntactic declaration.

typeof should return ImmutableMap{} and ImmutableList[] accordingly.

littledan commented 4 years ago

What do people here think about the ergonomics of #{ } #[ ] vs {| |} [| |]?

bobrosoft commented 4 years ago

With all that syntax changes I'm afraid JS to become new PERL in several years....

What about @JustFly1984 proposal above?

const map = new ImmutableMap({
  foo: 123,
  bar: 'baz'
});

const arr = new ImmutableList([
  'a',
  'b',
  'c'
]);

Crystal clear.

zenflow commented 4 years ago

@bobrosoft Crystal clear for newcomers to read, but not so convenient to write. If we used a syntax that was really quick & easy to write, I can imagine people would use it much more often, and maybe even default to using it, even in places they think it won't matter, or in early stages of a piece of code.

I'm not saying I am necessarily against the verbose approach you suggested, but there is a tradeoff, and it seems like we benefit more from a concise syntax here, since the downside (hard to just google syntax for people learning the language) affects people just one time, and can be overcome easily with a small amount of studying the langauge.

(p.s. I learned Perl on-the-job and remember frequently coming across syntax I didn't recognize. It wasn't that hard to go and find out what it meant.)

svieira commented 4 years ago

Both are ugly, but I prefer the prefix notation since it can mesh with tagged template literals (as I stated above, where Callable#[...] and Callable#{ ... } become possible, much like Callable`...`). It also is similar to the proposal for extensible numeric literals (_Callable). The second doesn't feel "native" to the existing syntax (IMO).

lifaon74 commented 4 years ago

If I could choose, in order of preferences (1 beeing the best) :

1) immutable keyword 2) Don't introduce a new syntax ! Since es6, a huge amount of new syntax has been added. WE SHOULD NOT ABUSE OF IT or in a few more years, js will be unreadable... => Why not using Record({a: 1, ... }) ? 3) new sytax with #{}, #[] => not a good idea for future generations...

hormesiel commented 4 years ago

It wasn't that hard to go and find out what it meant

Now just imagine if you hadn't need to do that... think about aallll that time you've spent Googling around that could have been spent doing something else. Because let's be honest most of our time as devs is spent finding out "what X means" or "how to do X". I think we'd all rather spend that time elsewhere, like actually coding.

zenflow commented 4 years ago

Now just imagine if you hadn't need to do that... think about aallll that time you've spent Googling

I meant it was easy and didn't take very much time. A few minutes to find a page. 30 seconds (being generous) each time I forgot what one was and had to open up the page and check. And there weren't SO MANY sigils/syntax that I had to go on using it as a constant reference. It was a short time to learn and remember the important & common ones. And I am definitely not a person with good memorization abilities.

svieira commented 4 years ago

The only problem (conceptually) with Record({a: 1, ... }) is that it requires introducing macros in the general case (e. g. making Map({x: 1, [y]: 2 }) be a Map { x: 1, [reference to y]: 2 } instead of Map { x: 1, "toString output of y": 2 }):

> function* objectIterator() { yield* Object.entries(this); }
> new Map({x: 3, [7]: 9, [Symbol.iterator]: objectIterator })
Map(2) {"7" => 9, "x" => 3} // Hoped-for output is Map(2) { 7 => 9, "x" => 3 }

Using syntax like Record#{ ... } lets JS avoid introducing generalized macros right now in favor of the macro-like decorator functionality already provided by tagged template literals and suggested for tagged numeric literals.

getify commented 4 years ago

@littledan I prefer {| |} [| |].

bobrosoft commented 4 years ago

looks like a "bayan"... That will be the first language with bayan 🤣 [|||||||]

arxpoetica commented 4 years ago

I like {| |} [| |]...can someone point to where this has been done before, or is this a totally new expression / invention?

svieira commented 4 years ago

It's in both Flow and OCaml, though in OCaml those constructs mean something completely different (extended string quotation for {| |}, and arrays for [| |])

ljharb commented 4 years ago

In flow i believe it also means exact, not immutable (ie, sealed, not frozen)?

MaxGraey commented 4 years ago

I also prefer {| |} and [| |] syntax. # is already using in:

  1. private fields
  2. and part of shebang decorator
  3. Will be possible in smart pipelines

As if we did not have to rename JavaScript to HashScript soon =)

noppa commented 4 years ago

I also quite like {| |} [| |].
AFAIK Flow is (or at least was a year ago) planning to move away from the {| |} syntax, to having sealed object types by default and special syntax for unsealed objects.

littledan commented 4 years ago

I want to suggest that, now that the record/tuple-ness needs to be noted at each level, {| |} [| |] makes more sense than #{ } #[ ]. To me (and this is totally subjective), the hash looks like it's modifying the whole nested literal, and the bar looks like it's modifying just that particular level.