proposal-record-tuple
proposal-record-tuple copied to clipboard
What about sets?
JavaScript has objects, arrays, dates, and sets. In regard to immutables, with the advent of records and tuples, objects now map to records, arrays to tuples, dates to temporals (soon enough), but sets have no counterpart.
Any designs on implementing immutable sets as a follow-on proposal? When creating structured data to represent app state sets are handy and map to many real world (domain sensible) scenarios.
Having them as loosely built from tuples but with uniqueness enforcement would make dealing with such scenarios much friendlier (e.g. not having to regularly check a tuple before adding a value).
const tuple = #[1, 2, 2, 3];
const set = #![1, 2, 2, 3]; // => #![1, 2, 3]
const apples = #[1, 2], oranges = #[2, 3];
const fruit = #![...apples, ...oranges]; // => #![1, 2, 3]
Agreed. Sets are essential and can have better performance characteristics for many operations and contexts that we lean too heavily on Arrays/Tuples for, aside from their obvious utilities as mathematical sets. Coming from Clojure(Script), I find them to be something I desire to be ready at hand.
Should sets be ordered or unordered? Does #![ 1, 2, 3 ] === #![ 3, 2, 1 ]
?
@errorx666 JS Sets are ordered.
Order is one of the key issues that make Sets harder to add as primitives compared to records and tuples. Tuple entries are ordered by their index (numbers), and Record entires are ordered by their key (strings). But there isn't a global ordering of all primitives values in the language so they would most likely be sorted by insertion order like the existing Set
class is. Which would make their equality match the expressiveness of comparing tuples.
In part, adding the !
is meant to imply the added uniqueness constraint. In every other respect it might as well just follow what an array does (or what a JS Set does). For equality comparison purposes, same members is sufficient. Having to specify kind of set (e.g. Ordered Set) just complicates things. Beyond uniqueness everything is just a presentation (UI) concern, anyway.
const queue = #[ 'encodeURIComponent', 'encodeURIComponent', 'base64' ];
for ( const task of queue ) { input = process(input, task); }
queue===#[ 'encodeURIComponent', 'base64' ]; // false
const valueSet = #[ 123, 123, 456 ];
valueSet.includes(123); // includes (or any api same to this) will be internal optimized for performance.
valueSet==#[ 456, 123 ]; // true
const options = #{ limitedKey1: 'freeValue', limitedKey2: 'freeValue' };
options.limitedKey1;
const simpleSet = #{ limitedKey1, limitedKey2 };
simpleSet.limitedKey1; // default to true
simpleSet==#{ limitedKey2, limitedKey1 }; // true
const conditionTree = #{ import: 'path', require: 'path' };// node package.json condition exports rule use case
conditionTree===#{ require: 'path', import: 'path' };// false
I think there are different use cases for queue/set, valueSet/simpleSet, options/conditionTree.
Different use case shouldn't be differentiated while created, but should in using (because they are static, and any optimizing in engine side is acceptable).
As a follow on proposal, it might be interesting if sets also had their own literal syntax.
const s = new Set([1,2,1,3,2]); //1,2,3
Why not?
const s = ![1,2,1,3,2];
So that it has neat parity with its immutable counterpart:
const s = #![1,2,1,3,2];
The idea is if you're of a mind to begin with an array, then consider the uniqueness constraint momentarily later, you just insert the bang.
const x = Symbol();
const y = Symbol();
const a = #![x, y];
const b = #![y, x];
Are a
and b
the same value or not?
- If they are, they should have the same iteration order. That is,
#[...a, ...b]
is either equal to#[x, y, x, y]
or#[y, x, y, x]
. But which one? ShouldSymbol()
come beforeSymbol()
or afterSymbol()
? - If they are not, you might be interested in the array deduplication proposal, because then
#![ elements ]
is basically sugar for#[ elements ].uniqueBy()
.
Also #![]
and ![]
are already valid syntax and can never be changed to mean something else.
If a
and b
are ordered sets then no. If vanilla sets, yes. I would suggest the default is vanilla sets. But that begs another question about whether or not ordered sets are something else and deserve their own literal syntax.
I only offered an example. The desire is for some literal syntax for sets, whatever that may be.
Sets and Maps in JS are effectively ordered, by insertion order.
But how should the immutable counterparts compare and decide on equality?
I'm not sure where you're trying to get.
Unless you cannot iterate over entries, there has to be an order for collections. As @acutmore said, there is no global ordering of primitive values, so there can't be a different order than insertion order, or a user provided ordering predicate.
The question of equality is somewhat orthogonal. Order does not strictly have to be considered part of the equality for immutable sets. We could imagine that immutable set would be equal iff every item of the set exists in the other set. That would result in sets that are observably different, yet "equal". There is a precedent in the case of -0
and 0
, but this case seems harder to justify.
I am just reflecting today's earlier comment about equality.
const result = #![x, y] === #![y, x]; //true?
It's true if sets have no ordering semantic. If they do, e.g. OrderedSet, this would not be true.
any iterable collection must have ordering semantics, or their iteration would be non deterministic, which the language has been avoiding.
Can you clarify why you think the iteration order of an immutable set must affect its equality?
- on one hand, it may be surprising to some programmers to have the equality above when they can observably iterate over the 2 in a different order
- on the other hand, it would make such immutable sets a lot less useful as they would effectively just be deduplicated tuples. At that point a helper to create a deduplicated tuple might be sufficient (see https://github.com/tc39/proposal-record-tuple/issues/377#issuecomment-2079650227 mentioning
uniqueBy
)
Given the potential new direction of this proposal, the former might not be as surprising (the equality predicate would no longer be ===
, and "equal" records/tuples may not be ===
)
Btw, I am avoiding mixing any syntax concerns of how these immutable sets are created, as it's not relevant to the equality question.