Set `.toggle` method

This would carry roughly the same kind of semantics as DOMTokenList, but also returning a boolean based on whether it previously existed for convenience and utility, something like this:

Set.prototype.toggle = function (value, include = this.has(value)) {
    if (include) {
        return this.delete(value);
    } else {
        const prev = this.size;
        this.add(value);
        return this.size !== prev;
    }
};

I've personally had as much use for it outside the DOM as in, so it'd be very useful to have. Also, engines could optimize this to always look up the value and then afterwards make the determination on whether to add or remove the value, to save a bunch on branches and code size, resulting in far less overhead.

5 Likes

I would love to see .toggle() added to Set.

I've had a lot of places where I either need a straight toggle (toggle the current value) or a force toggle (set the presence to a computed boolean), and a built-in .toggle() would make that code a lot nicer to read.

what does β€œtoggle the current value” mean for a non-boolean value?

If the element is present, remove it. If the element is not present, insert it.

The basic "toggle" behavior (with no second parameter) is reasonable though I'm not likely to champion it myself.

But for the second parameter, how is "force toggle" any different from set[include ? 'add' : 'delete'](element)? I would prefer reading that rather than figuring out what this optional second boolean parameter is doing.

DOMTokenList: toggle() method - Web APIs | MDN was my cited precedent, and I proposed (near) identical semantics.

OT: I almost forgot this suggestion even existed. :sweat_smile:

The two-parameter form isn't any different, but from an implementation perspective, both simple toggling and toggling by presence are extremely similar:

Set.prototype.toggle = function (value, force) {
    // The only difference between the two methods
    const forceByExistence = arguments.length < 2

    if (!forceByExistence) force = Boolean(force)

    const location = getEntryLocation(this, value)

    if (forceByExistence) force = !hasValue(location)

    if (force) {
        if (!hasValue(location)) {
            insertAtLocation(this, location, value)
            return false
        } else {
            return true
        }
    } else {
        if (hasValue(location)) {
            removeAtLocation(this, location)
            return true
        } else {
            return false
        }
    }
}

(I've implemented a few raw hash tables before. This is basically how they all work.)