Re: Re: Array.prototype.remove(item)

classic Classic list List threaded Threaded
17 messages Options
Reply | Threaded
Open this post in threaded view
|

Re: Re: Array.prototype.remove(item)

Man Hoang

There are cases that you only want to remove the first occurrence of an item (because you know there is at most one occurrence of that item) and there are cases that you want to remove more than one item. Therefore, I propose adding the following methods (written in TypeScript here for static typing support), optimizing for each use case.

 

class Array<E> {

    /**

     * Removes the first occurrence of [item] from this array.

     *

     * Returns `true` if [item] was in this array and `false` otherwise.

     */

    remove(item: E): boolean {

        const i = this.indexOf(item);

        if (i >= 0) {

            this.splice(i, 1);

            return true;

        }

        return false;

    }

 

    /**

     * Removes all items that satisfies the predicate [test].

     * @returns The number of removed items.

     */

    removeWhere(test: (item: E) => boolean): number {

        let count = 0;

        for (let i = this.length - 1; i >= 0; i--) {

            if (test(this[i])) {

                this.splice(i, 1);

                count++;

            }

        }

        return count;

    }

}

 

References


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Re: Array.prototype.remove(item)

Jordan Harband
What's the benefit of adding more array mutator methods? The general idioms of the community have moved towards approaches that create a new object, rather than changing the old one - ie, .filter would be appropriate here.

On Tue, Oct 9, 2018 at 7:10 PM Man Hoang <[hidden email]> wrote:

There are cases that you only want to remove the first occurrence of an item (because you know there is at most one occurrence of that item) and there are cases that you want to remove more than one item. Therefore, I propose adding the following methods (written in TypeScript here for static typing support), optimizing for each use case.

 

class Array<E> {

    /**

     * Removes the first occurrence of [item] from this array.

     *

     * Returns `true` if [item] was in this array and `false` otherwise.

     */

    remove(item: E): boolean {

        const i = this.indexOf(item);

        if (i >= 0) {

            this.splice(i, 1);

            return true;

        }

        return false;

    }

 

    /**

     * Removes all items that satisfies the predicate [test].

     * @returns The number of removed items.

     */

    removeWhere(test: (item: E) => boolean): number {

        let count = 0;

        for (let i = this.length - 1; i >= 0; i--) {

            if (test(this[i])) {

                this.splice(i, 1);

                count++;

            }

        }

        return count;

    }

}

 

References

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

RE: Re: Array.prototype.remove(item)

Man Hoang

The benefits are

- efficient (memory & speed)

- clear intent

- concise

 

There are always use cases where you want to mutate arrays.

 

How would you rewrite the following `deselect` method using `filter`?

 

``` js

export class Selector<T> {

    private _values: T[] = [];

 

    get values(): ReadonlyArray<T> {

        return this._values;

    }

 

    /**

     * Removes [value] from the list of selected items.

     *

     * Returns `true` if [value] was previously selected, `false` otherwise.

     */

    deselect(value: T): boolean {

        if (this._values.remove(value)) {

            this.selectionChanged([], [value]);

            return true;

        }

        return false;

    }

 

    /**

     * Adds [value] to the list of selected items.

     *

     * Returns `true` if [value] was not previously selected, `false` otherwise.

     */

    select(value: T): boolean {

        if (this._values.pushIfAbsent(value)) {

            this.selectionChanged([value], []);

            return true;

        }

        return false;

    }

 

    protected selectionChanged(addedValues, removedValues) {

        // Do something such as firing an event.

    }

}

```


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Array.prototype.remove(item)

Claude Pache
For that specific example, I think that a Set is more appropriate:

``` js
export class Selector<T> {
    private _set = new Set;
 
    get values(): ReadonlyArray<T> {
        return Array.from(this._set);
        // although it might be better to return an iterator: return this._set.values();
    }

    deselect(value: T): boolean {
        if (this._set.delete(value)) {
            this.selectionChanged([], [value]);
            return true;
        }
        return false;
    }

    // etc.
}
```

More generally, each time you are tempted to use an add()/remove()/toggle()/contains()/etc() method on an Array, you should ask yourself if it would not be better (more efficient, clearer intent, more concise, ...) to use a Set instead.

—Claude


Le 10 oct. 2018 à 08:30, Man Hoang <[hidden email]> a écrit :

The benefits are
- efficient (memory & speed)
- clear intent
- concise
 
There are always use cases where you want to mutate arrays.
 
How would you rewrite the following `deselect` method using `filter`?
 
``` js
export class Selector<T> {
    private _values: T[] = [];
 
    get values(): ReadonlyArray<T> {
        return this._values;
    }
 
    /**
     * Removes [value] from the list of selected items.
     * 
     * Returns `true` if [value] was previously selected, `false` otherwise.
     */
    deselect(value: T): boolean {
        if (this._values.remove(value)) {
            this.selectionChanged([], [value]);
            return true;
        }
        return false;
    }
 
    /**
     * Adds [value] to the list of selected items.
     * 
     * Returns `true` if [value] was not previously selected, `false` otherwise.
     */
    select(value: T): boolean {
        if (this._values.pushIfAbsent(value)) {
            this.selectionChanged([value], []);
            return true;
        }
        return false;
    }
 
    protected selectionChanged(addedValues, removedValues) {
        // Do something such as firing an event.
    }
}
```
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

RE: Array.prototype.remove(item)

Man Hoang

The problem with `Set` is that its `add` method returns `this` instead of `boolean`. If `Set.prototype.add` returned `boolean`, I would have used `Set`.

 

That’s why in the `select` method of my sample code, I use a custom defined method named `pushIfAbsent`. The actual type of `_values` is not `Array` but a subclass of `Array`.

``` js

export class MyArray<E> extends Array<E> {

    /**

     * Adds [item] to the end of this array if it's not already in this array.

     *

     * Returns `true` is [item] was added, `false` otherwise.

     */

    pushIfAbsent(item: E): boolean {

        if (!this.includes(item)) {

            this.push(item);

            return true;

        }

        return false;

    }

 

    /**

     * Removes the first occurrence of [item] from this array.

     *

     * Returns `true` if [item] was in this array, `false` otherwise.

     */

    remove(item: E): boolean {

        const i = this.indexOf(item);

        if (i >= 0) {

            this.splice(i, 1);

            return true;

        }

        return false;

    }

}

```


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Array.prototype.remove(item)

kai zhu
hi Man, i don’t have strong opinion on Array.prototype.remove, but i have strong opinion against your use-case to subclass/extend Array.  from a product-development perspective, you're creating unnecessary integration-headaches by having the client <-> server system pass around <MyArray> instances instead of plain JSON arrays.

i remain unconvinced of any real-world benefit to subclassing Array, that justifies the cost-added to already painful task of debugging end-to-end communication-issues between client <-> server.  your web-project will have a less-painful integration/qa process, if you stick with plain JSON arrays employing static-functions instead:

```javascript
/*jslint devel*/
(function () {
    "use strict";
    var myArray1;
    function arrayRemoveItem(array, item) {
    /**
     * This static-function will:
     * Remove the first occurrence of [item] from this array.
     * Return `true` if [item] was in this array, `false` otherwise.
     */
        var i = array.indexOf(item);
        if (i >= 0) {
            array.splice(i, 1);
            return true;
        }
        return false;
    }
    myArray1 = [1, 2, 3, 4]; // [1, 2, 3, 4]
    console.log(arrayRemoveItem(myArray1, 2)); // true
    console.log(myArray1); // [1, 3, 4]
}());
```


On 10 Oct 2018, at 3:01 PM, Man Hoang <[hidden email]> wrote:

The problem with `Set` is that its `add` method returns `this` instead of `boolean`. If `Set.prototype.add` returned `boolean`, I would have used `Set`.
 
That’s why in the `select` method of my sample code, I use a custom defined method named `pushIfAbsent`. The actual type of `_values` is not `Array` but a subclass of `Array`.
``` js
export class MyArray<E> extends Array<E> {
    /**
     * Adds [item] to the end of this array if it's not already in this array.
     * 
     * Returns `true` is [item] was added, `false` otherwise.
     */
    pushIfAbsent(item: E): boolean {
        if (!this.includes(item)) {
            this.push(item);
            return true;
        }
        return false;
    }
 
    /**
     * Removes the first occurrence of [item] from this array.
     * 
     * Returns `true` if [item] was in this array, `false` otherwise.
     */
    remove(item: E): boolean {
        const i = this.indexOf(item);
        if (i >= 0) {
            this.splice(i, 1);
            return true;
        }
        return false;
    }
}
```
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Array.prototype.remove(item)

Claude Pache


> Le 10 oct. 2018 à 23:17, kai zhu <[hidden email]> a écrit :
>
> hi Man, i don’t have strong opinion on Array.prototype.remove, but i have strong opinion against your use-case to subclass/extend Array.  from a product-development perspective, you're creating unnecessary integration-headaches by having the client <-> server system pass around <MyArray> instances instead of plain JSON arrays.

Not every object in JS is intended to travel between client and server, or to be stored in JSON. And if/when the class want to share some information, it can easily translate its data to and from a JSON-friendly format. (Sorry, I’m not motivated to read the rest of your e-mail.)

—Claude

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Array.prototype.remove(item)

Jordan Harband
In reply to this post by Man Hoang
Man: `add` doesn't need to return a boolean, because it always results in the item being in the collection after the fact. You could subclass Set, and make `.add` do that, though, if you like! Alternatively, you could use `.has` prior to calling `.add`, to get your boolean value.

On Wed, Oct 10, 2018 at 1:01 AM Man Hoang <[hidden email]> wrote:

The problem with `Set` is that its `add` method returns `this` instead of `boolean`. If `Set.prototype.add` returned `boolean`, I would have used `Set`.

 

That’s why in the `select` method of my sample code, I use a custom defined method named `pushIfAbsent`. The actual type of `_values` is not `Array` but a subclass of `Array`.

``` js

export class MyArray<E> extends Array<E> {

    /**

     * Adds [item] to the end of this array if it's not already in this array.

     *

     * Returns `true` is [item] was added, `false` otherwise.

     */

    pushIfAbsent(item: E): boolean {

        if (!this.includes(item)) {

            this.push(item);

            return true;

        }

        return false;

    }

 

    /**

     * Removes the first occurrence of [item] from this array.

     *

     * Returns `true` if [item] was in this array, `false` otherwise.

     */

    remove(item: E): boolean {

        const i = this.indexOf(item);

        if (i >= 0) {

            this.splice(i, 1);

            return true;

        }

        return false;

    }

}

```


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Array.prototype.remove(item)

kai zhu
In reply to this post by Claude Pache
Not every object in JS is intended to travel between client and server, or to be stored in JSON. And if/when the class want to share some information, it can easily translate its data to and from a JSON-friendly format.

hi Claude, agree to disagree.  its impossible to predict/design what should and shouldn’t travel between client <-> server in javascript product-development.  

the best you can do as an “architect” is to accept that *anything* of-consequence in javascript will eventually need to "leak" itself to the "client <-> server <-> db JSON/urlstring dance", over the normal-flow of UX-features getting added to a product;  and that much of the tech-debt in these projects arise from needless class constructor/serialization hacks to make that dance happen, when it wasn’t expected (and could’ve been avoided by sticking with plain JSON data-structures).

even in esoteric, seemingly non-web-related cases like using duktape in c++:  why would anyone want to have embedded js-capability in such scenario?  the only use-case that comes to mind is for manipulating JSON data-structures, and again, passing plain JSON-data between "c++ program" <-> “external web-system"


On 11 Oct 2018, at 5:14 AM, Claude Pache <[hidden email]> wrote:



Le 10 oct. 2018 à 23:17, kai zhu <[hidden email]> a écrit :

hi Man, i don’t have strong opinion on Array.prototype.remove, but i have strong opinion against your use-case to subclass/extend Array.  from a product-development perspective, you're creating unnecessary integration-headaches by having the client <-> server system pass around <MyArray> instances instead of plain JSON arrays.

Not every object in JS is intended to travel between client and server, or to be stored in JSON. And if/when the class want to share some information, it can easily translate its data to and from a JSON-friendly format. (Sorry, I’m not motivated to read the rest of your e-mail.)

—Claude



_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Array.prototype.remove(item)

Sanford Whiteman
You need to cite your sources for the claim that "most of the tech
debt" in JavaScript product development is due to accidentally using
types other than 20-year-old built-ins and having to figure out the
daunting task of JSON serialization.

—— Sandy

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

RE: Array.prototype.remove(item)

Ron Buckton
In reply to this post by Jordan Harband

That depends entirely on what the return value means. Returning Boolean from `add` doesn’t mean, “does the value now exist in the set”, but rather means “was the set modified as a result of this operation”.

 

To avoid any possible performance cost for calling `has` before `add`, I usually have to do something like:

 

```

function setAdd(set, value) {

  const size = set.size;

  set.add(value);

  return size !== set.size;

}

```

 

From: es-discuss <[hidden email]> On Behalf Of Jordan Harband
Sent: Wednesday, October 10, 2018 7:19 PM
To: [hidden email]
Cc: es-discuss <[hidden email]>
Subject: Re: Array.prototype.remove(item)

 

Man: `add` doesn't need to return a boolean, because it always results in the item being in the collection after the fact. You could subclass Set, and make `.add` do that, though, if you like! Alternatively, you could use `.has` prior to calling `.add`, to get your boolean value.

 

On Wed, Oct 10, 2018 at 1:01 AM Man Hoang <[hidden email]> wrote:

The problem with `Set` is that its `add` method returns `this` instead of `boolean`. If `Set.prototype.add` returned `boolean`, I would have used `Set`.

 

That’s why in the `select` method of my sample code, I use a custom defined method named `pushIfAbsent`. The actual type of `_values` is not `Array` but a subclass of `Array`.

``` js

export class MyArray<E> extends Array<E> {

    /**

     * Adds [item] to the end of this array if it's not already in this array.

     *

     * Returns `true` is [item] was added, `false` otherwise.

     */

    pushIfAbsent(item: E): boolean {

        if (!this.includes(item)) {

            this.push(item);

            return true;

        }

        return false;

    }

 

    /**

     * Removes the first occurrence of [item] from this array.

     *

     * Returns `true` if [item] was in this array, `false` otherwise.

     */

    remove(item: E): boolean {

        const i = this.indexOf(item);

        if (i >= 0) {

            this.splice(i, 1);

            return true;

        }

        return false;

    }

}

```


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Array.prototype.remove(item)

kai zhu
In reply to this post by Sanford Whiteman
You need to cite your sources

hi Sandy, sure hear are 2 sources:

1. (80% reduction in tech-debt from 5000 -> 1000 sloc) - commit to remove [class-based] Backbone, Handlebars, and other dependencies from fork of swagger-ui [1].  everything was eventually converted to use pure JSON data-structures, to make client <-> server JSON-serialization as painless as possible.

2. (90% reduction in tech-debt from 11,000 -> 1000 sloc) - 3 commits to remove class-dependencies from fork of nedb [2] and rely on pure JSON data-structures to allow easier import/export of persistent db-data

[1] github commit to remove class-dependencies from swagger-ui

[2] github commits to remove class-dependencies from nedb



On 11 Oct 2018, at 10:11 AM, Sanford Whiteman <[hidden email]> wrote:

You need to cite your sources for the claim that "most of the tech
debt" in JavaScript product development is due to accidentally using
types other than 20-year-old built-ins and having to figure out the
daunting task of JSON serialization.

—— Sandy



_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Array.prototype.remove(item)

Sanford Whiteman
> You need to cite your sources

> hi Sandy, sure hear are 2 sources:

So you believe these 2 patches prove "most of the tech debt" across
all JavaScript product development is due to this factor.

Huh.

Well, to each their own as far as how "proof" works. I prefer the
classical definition. You know, the one where you use the actual
population you're generalizing about.


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

RE: Array.prototype.remove(item)

Man Hoang
In reply to this post by Ron Buckton

### With `Array.prototype.remove(item)`

Code is clean and concise.

``` js

if (array.remove(item)) {

    // [item] was removed, do something here.

}

```

 

### Without `Array.prototype.remove(item)`

Code is verbose or duplicated (everyone has their own version of `removeArrayItem`) => **WASTE**.

``` js

// Using `indexOf` and `splice` directly.

 

const i = array.indexOf(item);

if (i >= 0) {

    array.splice(i, 1);

    // [item] was removed, do something here.

}

 

// Using a helper function.

 

if (removeArrayItem(array, item)) {

    // [item] was removed, do something here.

}

```

 

### With `Set.prototype.add` returning a boolean

Code is clean and concise. Together, `add` and `delete` are a perfect pair.

``` js

if (set.add(item)) {

    // [item] was added, do something here.

}

 

if (set.delete(item)) {

    // [item] was deleted, do something here.

}

```

 

### With `Set.prototype.add` returning `this` (almost, if not always, useless)

Code is verbose or duplicated (everyone has their own version of `addItemToSetIfAbsent`) => **WASTE**.

``` js

// Using `has` and `add`.

 

if (!set.has(item)) {

    set.add(item);

    // [item] was added, do something here.

}

 

// Using `size` and `add`.

 

const oldSize = set.size;

set.add(item);

if (set.size > oldSize) {

    // [item] was added, do something here.

}

 

// Using a helper function.

 

if (addItemToSetIfAbsent(set, item)) {

    // [item] was added, do something here.

}

```

 

My point of view is JS and the web itself were not designed for app development. They have constantly been hacked to do so. If they had been, people wouldn’t have wasted too many resources creating workarounds (not solutions, not technologies) such as jQuery, Ember, Knockout, Polymer, Angular, React, Vue, GWT, CoffeeScript, TypeScript, and so on. No matter how we change JS, its inherent problems remain because we cannot introduce breaking changes. (Please do not argue about this as it will lead to nowhere)

 

Having said that, let's get back to the main point of this thread and this whole website: **proposing ideas to make JS better (or less terrible)**. For me, changes such as adding `Array.prototype.remove(item)` and modifying `Set.prototype.add` to return a boolean may seem small but would have a huge impact when multiplied by the number of usages.

 

**One small improvement repeated many times makes a big improvement.**

**Many small improvements combined make a big improvement.**


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Array.prototype.remove(item)

Andrea Giammarchi-2
TBH, for consistency sake with everything else in JS, I'd call the method `delete`, since it's the one that returns `true` or `false` in pretty much every other case.

On Thu, Oct 11, 2018 at 11:54 AM Man Hoang <[hidden email]> wrote:

### With `Array.prototype.remove(item)`

Code is clean and concise.

``` js

if (array.remove(item)) {

    // [item] was removed, do something here.

}

```

 

### Without `Array.prototype.remove(item)`

Code is verbose or duplicated (everyone has their own version of `removeArrayItem`) => **WASTE**.

``` js

// Using `indexOf` and `splice` directly.

 

const i = array.indexOf(item);

if (i >= 0) {

    array.splice(i, 1);

    // [item] was removed, do something here.

}

 

// Using a helper function.

 

if (removeArrayItem(array, item)) {

    // [item] was removed, do something here.

}

```

 

### With `Set.prototype.add` returning a boolean

Code is clean and concise. Together, `add` and `delete` are a perfect pair.

``` js

if (set.add(item)) {

    // [item] was added, do something here.

}

 

if (set.delete(item)) {

    // [item] was deleted, do something here.

}

```

 

### With `Set.prototype.add` returning `this` (almost, if not always, useless)

Code is verbose or duplicated (everyone has their own version of `addItemToSetIfAbsent`) => **WASTE**.

``` js

// Using `has` and `add`.

 

if (!set.has(item)) {

    set.add(item);

    // [item] was added, do something here.

}

 

// Using `size` and `add`.

 

const oldSize = set.size;

set.add(item);

if (set.size > oldSize) {

    // [item] was added, do something here.

}

 

// Using a helper function.

 

if (addItemToSetIfAbsent(set, item)) {

    // [item] was added, do something here.

}

```

 

My point of view is JS and the web itself were not designed for app development. They have constantly been hacked to do so. If they had been, people wouldn’t have wasted too many resources creating workarounds (not solutions, not technologies) such as jQuery, Ember, Knockout, Polymer, Angular, React, Vue, GWT, CoffeeScript, TypeScript, and so on. No matter how we change JS, its inherent problems remain because we cannot introduce breaking changes. (Please do not argue about this as it will lead to nowhere)

 

Having said that, let's get back to the main point of this thread and this whole website: **proposing ideas to make JS better (or less terrible)**. For me, changes such as adding `Array.prototype.remove(item)` and modifying `Set.prototype.add` to return a boolean may seem small but would have a huge impact when multiplied by the number of usages.

 

**One small improvement repeated many times makes a big improvement.**

**Many small improvements combined make a big improvement.**

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Array.prototype.remove(item)

T.J. Crowder-2
On Thu, Oct 11, 2018 at 10:59 AM Andrea Giammarchi
<[hidden email]> wrote:

> TBH, for consistency sake with everything else in JS, I'd call
> the method `delete`, since it's the one that returns `true` or
> `false` in pretty much every other case.

E.g., in Maps and Sets (and their weak cousins).

-- T.J. Crowder
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Array.prototype.remove(item)

Andrea Giammarchi-2
but also `delete obj[key]` ... not identical as operation, but with a boolean return.

That leaves room for eventual Array#drop(item) that returns the item after optionally mutating the array ^_^;;

On Thu, Oct 11, 2018 at 1:21 PM T.J. Crowder <[hidden email]> wrote:
On Thu, Oct 11, 2018 at 10:59 AM Andrea Giammarchi
<[hidden email]> wrote:

> TBH, for consistency sake with everything else in JS, I'd call
> the method `delete`, since it's the one that returns `true` or
> `false` in pretty much every other case.

E.g., in Maps and Sets (and their weak cousins).

-- T.J. Crowder

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss