In this blog post, we examine the ECMAScript proposal “Set methods for JavaScript” by Michał Wadas, Sathya Gunasekara and Kevin Gibbons. It introduces new methods for Sets.
Set.prototype.union(other)
This method returns a Set that is the union of this
and other
.
Type signature:
Set<T>.prototype.union(other: SetReadOperations<T>): Set<T>
The type of other
, SetReadOperations
is discussed later. It means that other
provides all operations that the new methods need for their algorithms.
Example:
assert.deepEqual(
new Set(['a', 'b', 'c']).union(new Set(['c', 'd'])),
new Set(['a', 'b', 'c', 'd'])
);
Set.prototype.intersection(other)
This method returns a Set that is the intersection of this
and other
.
Type signature:
Set<T>.prototype.intersection(other: SetReadOperations<T>): Set<T>
Example:
assert.deepEqual(
new Set(['a', 'b', 'c']).intersection(new Set(['c', 'd'])),
new Set(['c'])
);
Set.prototype.difference(other)
This method returns a Set that is the difference between this
and other
.
Type signature:
Set<T>.prototype.difference(other: SetReadOperations<T>): Set<T>
Example:
assert.deepEqual(
new Set(['a', 'b', 'c']).difference(new Set(['c', 'd'])),
new Set(['a', 'b'])
);
Set.prototype.symmetricDifference(other)
This method returns a Set that is the symmetric difference between this
and other
. What does that mean? These are equivalent definitions of the symmetric difference:
this
− other
) ∪ (other
− this
)this
∪ other
) − (this
∩ other
)this
xor other
(exclusive OR)Type signature:
Set<T>.prototype.symmetricDifference(other: SetReadOperations<T>): Set<T>
Example:
assert.deepEqual(
new Set(['a', 'b', 'c']).symmetricDifference(new Set(['c', 'd'])),
new Set(['a', 'b', 'd'])
);
assert.deepEqual(
new Set(['a', 'b']).symmetricDifference(new Set(['c', 'd'])),
new Set(['a', 'c', 'b', 'd'])
);
Set.prototype.isSubsetOf(other)
This method returns true
if this
is a subset of other
and false
otherwise.
Type signature:
Set<T>.prototype.isSubsetOf(other: SetReadOperations<T>): boolean
Example:
assert.deepEqual(
new Set(['a', 'b', 'c']).isSubsetOf(new Set(['a', 'b'])),
false
);
assert.deepEqual(
new Set(['a', 'b']).isSubsetOf(new Set(['a', 'b', 'c'])),
true
);
Set.prototype.isSupersetOf(other)
This method returns true
if this
is a superset of other
and false
otherwise.
Type signature:
Set<T>.prototype.isSupersetOf(other: SetReadOperations<T>): boolean
Example:
assert.deepEqual(
new Set(['a', 'b', 'c']).isSupersetOf(new Set(['a', 'b'])),
true
);
assert.deepEqual(
new Set(['a', 'b']).isSupersetOf(new Set(['a', 'b', 'c'])),
false
);
Set.prototype.isDisjointFrom(other)
This method returns true
if this
is disjoint from other
and false
otherwise.
Type signature:
Set<T>.prototype.isDisjointFrom(other: SetReadOperations<T>): boolean
Example:
assert.deepEqual(
new Set(['a', 'b', 'c']).isDisjointFrom(new Set(['c', 'd'])),
false
);
assert.deepEqual(
new Set(['a', 'b', 'c']).isDisjointFrom(new Set(['x'])),
true
);
this
and other
For all of the new Set methods:
this
must be an instance of Set
.other
must implement the interface SetReadOperations
shown below.
interface SetReadOperations<T> {
/** Can be `Infinity` (see next section). */
size: number;
has(key: T): boolean;
/** Returns an iterator for the elements in `this`. */
keys(): Iterator<T>; // only method `.next()` is required
}
The .size
of other
can be Infinity
. That means we can work with infinite Sets:
const evenNumbers = {
has(elem) {
return (elem % 2) === 0;
},
size: Infinity,
keys() {
throw new TypeError();
}
};
assert.deepEqual(
new Set([0, 1, 2, 3]).difference(evenNumbers),
new Set([1, 3])
);
assert.deepEqual(
new Set([0, 1, 2, 3]).intersection(evenNumbers),
new Set([0, 2])
);
Only two methods don’t support other
being an infinite Set:
union
symmetricDifference
These are the rationales behind the API design (source):
Why does this
have to be a Set
?
this
which would have enabled us to use the Set methods generically. However, not doing so makes implementations simpler and faster.Why use an interface for other
?
other
can be a data structure other than a Set. It was chosen as a compromise between accepting only Sets and any iterable objects.Why is the full interface always enforced for other
?
Why was the method name .keys()
chosen for iterating over data structure elements?
Map
:
Symbol.iterator
doesn’t work because that Map method returns key-value pairs.'values'
doesn’t work because that Map method is not compatible with the Map method .has()
(which accepts keys, not values).Why are the new method names nouns and not verbs like .add()
?
this
, while noun methods return new data – for example: set.add()
and set.keys()
.I’m aware of the following two polyfills: