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 · 22 comments
Open

What about sets? #377

mlanza opened this issue May 6, 2023 · 22 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.

@k3nsei
Copy link

k3nsei commented Oct 18, 2024

But why? Order in sets doesn't matters. If you want to check if Set A and Set B are equal just do

const a = new Set([1, 2, 3]);
const b = new Set([3, 1, 2]);

const equal = a.intersection(b).size === a.size;

@ljharb
Copy link
Member

ljharb commented Oct 18, 2024

Sets are ordered in JS; order does matter.

@k3nsei
Copy link

k3nsei commented Oct 18, 2024

@ljharb it matters only if you converting them to array or other mapped structure, but then they are not Set anymore. For equality it isn't matter. If you care about order then you using wrong data structure and you should be using array instead.

@acutmore
Copy link
Collaborator

A key part of this proposal is equality of immutable values. If the proposal provides this equality check using === then it will cause complications for two immutable sets to be === equal if they also have observably different orders.

@mhofman
Copy link
Member

mhofman commented Oct 18, 2024

But why? Order in sets doesn't matters

I think I've answered that already.

iteration would be non deterministic, which the language has been avoiding.

The committee will not introduce any new non deterministic behavior. That means we'd need to specify a deterministic order based on the values so that 2 independently constructed but equal sets would iterate in the same order.

@demurgos
Copy link

demurgos commented Oct 18, 2024

it matters only if you converting them to array or other mapped structure, but then they are not Set anymore. For equality it isn't matter. If you care about order then you using wrong data structure and you should be using array instead.

@ljharb is 100% right in its answer; so just to push the point further: a Set in JS is not a mathematical set. Both concepts share the same name but they don't have the same structure. By the standard, Set objects in JS are ordered. It's just a fact. In particular, JS allows you to construct a total function f that behaves differently depending on if you pass new Set([1, 2, 3]) or new Set([3, 1, 2]) as the argument: e.g. "convert to array" as you said. The fact that they can exhibit different behavior proves that they are not "the same" in the general case (and you have to consider the general case when discussing language semantics).

The properties of a collection don't depend on what you care about but what the standard defines about them. The standard defines JS sets as ordered, therefore they are ordered and are not equivalent to mathematical sets. You can use them in your own code as building blocks to represent finite unordered sets if you restrict what you do with them; but it's not the same as assuming this restriction for all usages of Set.

But why? Order in sets doesn't matters

Because these are not mathematical sets, but ordered collections containing distinct elements according to SameValueZero semantics.

And for the reason why Set in JS was defined as such, see the reply by @mhofman: the standard committee wanted the JS language to be deterministic. Some platform APIs are non-deterministic, but those are not part of the core language. Collections defined in the core language are designed so it is deterministic.

@k3nsei
Copy link

k3nsei commented Oct 18, 2024

Technically thats is true, but as a developers we should limits ourselves to legal usecases, there are many ways to shot our own foot in JS. For ordered collections Arrays always were the way to go. To have arrays there with distinct values is also easy to achieve with help of small utilities and cache. So for me having records and tuples is enough and I wouldn't want to extend it with Set's.

@ljharb
Copy link
Member

ljharb commented Oct 18, 2024

Depending on the order of a Set is a legal use case, because it's part of the observable language. You certainly can choose to operate on a principle of "one should not care about the order of a Set", but the language simply can't operate that way.

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

No branches or pull requests

11 participants