Closed littledan closed 2 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.
This language feature is worth the wait!
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.
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.
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`
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
?
@zenflow
Indeed, even these are ambiguous:
immut [1,2,3]
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
@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).
@getify I'm not sure but it might be worth opening another issue for that topic
What about regex literals? regexes are supremely mutable, what with .compile, and it’d be great to have one that wasn’t.
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.
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?
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).
I honestly do not like any new symbols / words in js. It turns js into haskel or perl.
const arr = const [1,2,3]
means constant reference to an immutable data
const arr = [1,2,3]
means constant reference to a mutable data
let arr = const [1,2,3]
means mutable reference to an immutable data
This will be easy to understand peoples from C/C++.
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.
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..
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).
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?
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.
@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.
@getify fantastic idea. I'll add an example of why introducing a new keyword is a hazard, just as you have there.
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)
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.
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.
@arxpoetica it's pretty impossible, because JS won't break the web.
@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?
@ryan-codingintrigue it is still valid now, just not in an async function
. nothing is important enough to break the web.
@ljharb Ahhh. That makes sense - I was wondering why this hadn’t been brought up before. thanks!
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).
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.
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 aObject.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.
@silverwind deep freeze is great proposal in itself!
Although, we need some kind of immutable data structure with persistence.
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.
What do people here think about the ergonomics of #{ }
#[ ]
vs {| |}
[| |]
?
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.
@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.)
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).
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...
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.
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.
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.
@littledan I prefer {| |} [| |]
.
looks like a "bayan"... That will be the first language with bayan 🤣 [|||||||]
I like {| |} [| |]
...can someone point to where this has been done before, or is this a totally new expression / invention?
It's in both Flow and OCaml, though in OCaml those constructs mean something completely different (extended string quotation for {| |}
, and arrays for [| |]
)
In flow i believe it also means exact, not immutable (ie, sealed, not frozen)?
I also prefer {| |}
and [| |]
syntax. #
is already using in:
As if we did not have to rename JavaScript to HashScript soon =)
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.
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.
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.