Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

What about sets? #377

Open
mlanza opened this issue May 6, 2023 · 14 comments
Open

What about sets? #377

mlanza opened this issue May 6, 2023 · 14 comments

Comments

@mlanza
Copy link

mlanza commented May 6, 2023

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]
@Slackwise
Copy link

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.

@errorx666
Copy link

Should sets be ordered or unordered? Does #![ 1, 2, 3 ] === #![ 3, 2, 1 ]?

@ljharb
Copy link
Member

ljharb commented Jun 25, 2023

@errorx666 JS Sets are ordered.

@acutmore
Copy link
Collaborator

acutmore commented Jun 25, 2023

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.

@mlanza
Copy link
Author

mlanza commented Jun 25, 2023

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.

@LongTengDao
Copy link

LongTengDao commented Nov 30, 2023

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).

@mlanza
Copy link
Author

mlanza commented Apr 26, 2024

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.

@0f-0b
Copy link

0f-0b commented Apr 26, 2024

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? Should Symbol() come before Symbol() or after Symbol()?
  • 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.

@mlanza
Copy link
Author

mlanza commented Apr 26, 2024

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.

@mhofman
Copy link
Member

mhofman commented Apr 26, 2024

Sets and Maps in JS are effectively ordered, by insertion order.

@mlanza
Copy link
Author

mlanza commented Apr 26, 2024

But how should the immutable counterparts compare and decide on equality?

@mhofman
Copy link
Member

mhofman commented Apr 26, 2024

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.

@mlanza
Copy link
Author

mlanza commented Apr 26, 2024

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.

@mhofman
Copy link
Member

mhofman commented Apr 27, 2024

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 What about sets? #377 (comment) 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
9 participants