¿Son malos los efectos secundarios en "todos" o "algunos" de Array?

9

Siempre me han enseñado que tener efectos secundarios en una ifafección es malo. Lo que quiero decir es;

if (conditionThenHandle()) {
    // do effectively nothing
}

... Opuesto a;

if (condition()) {
    handle();
}

... y entiendo eso, y mis colegas están contentos porque no lo hago, y todos nos vamos a casa a las 17:00 un viernes y todos tienen un feliz fin de semana.

Ahora, ECMAScript5 introdujo métodos como every()y some()to Array, y los encuentro muy útiles. Son más limpios que for (;;;)los demás, le dan otro alcance y hacen que el elemento sea accesible mediante una variable.

Sin embargo, al validar la entrada, casi siempre me encuentro usando every/ someen la condición para validar la entrada, luego uso every/ some nuevamente en el cuerpo para convertir la entrada en un modelo utilizable;

if (input.every(function (that) {
    return typeof that === "number";
})) {
    input.every(function (that) {
        // Model.findById(that); etc
    }
} else {
    return;
}

... cuando lo que quiero hacer es;

if (!input.every(function (that) {
    var res = typeof that === "number";

    if (res) {
        // Model.findById(that); etc.
    }

    return res;
})) {
    return;
}

... lo que me da efectos secundarios en una ifcondición que es mala.

En comparación, este es el código con un viejo for (;;;);

for (var i=0;i<input.length;i++) {
    var curr = input[i];

    if (typeof curr === "number") {
        return;
    }

    // Model.findById(curr); etc.
}

Mis preguntas son:

  1. ¿Es definitivamente una mala práctica?
  2. ¿Estoy (mis | ab) usando somey every(¿ debería estar usando a for(;;;)para esto?)
  3. ¿Hay un mejor enfoque?
Isaac
fuente
3
Todos y algunos, así como filtrar, asignar y reducir son consultas, no tienen efectos secundarios, si lo hacen, está abusando de ellos.
Benjamin Gruenbaum
@BenjaminGruenbaum: ¿Entonces eso no los hace sin dientes la mayoría de las veces? 9/10, si lo uso some, quiero hacer algo con el elemento, si lo uso every, quiero hacer algo a todos esos elementos ... somey everyno me dejen acceder a esa información, así que tampoco puedo los uso, o tengo que agregar efectos secundarios.
Isaac
No. Cuando me refiero a los efectos secundarios, me refiero a la cabeza del cuerpo, si no al cuerpo. Dentro del cuerpo puede modificarlo de la forma que desee. Simplemente no mute el objeto dentro de la devolución de llamada que pasa a algún / cuándo.
Benjamin Gruenbaum
@BenjaminGruenbaum: Pero ese es exactamente mi punto. Si utilizo someen mi ifcondición para determinar si un determinado elemento de la matriz exhibe una cierta propiedad, 9/10 necesito para operar en ese elemento en mi ifcuerpo; ahora, como someno me dice cuál de los elementos exhibió la propiedad (solo "uno lo hizo"), puedo usarlo some nuevamente en el cuerpo (O (2n)), o simplemente puedo realizar la operación dentro de la condición if ( lo cual es malo, porque es un efecto secundario dentro de la cabeza).
Isaac
... lo mismo se aplica everytambién, por supuesto.
Isaac

Respuestas:

8

Si entiendo su punto correctamente, usted parece ser mis-consumo o abuso everyy somepero es un poco inevitable si desea cambiar los elementos de las matrices directamente. Corrígeme si me equivoco, pero lo que intentas hacer es averiguar si alguno o todos los elementos de tu secuencia exhiben una determinada condición y luego modificar esos elementos. Además, su código parece estar aplicando algo a todos los elementos hasta que encuentre uno que no pase el predicado y no creo que sea lo que quiera hacer. De todos modos

Tomemos su primer ejemplo (ligeramente modificado)

if (input.every(function (that) {
    return typeof that === "number";
})) {
    input.every(function (that) {
        that.foo();
    }
} else {
    return;
}

Lo que estás haciendo aquí en realidad va un poco en contra del espíritu de algunos / todos / mapas / reducir / filtrar / etc.conceptos. Everyno debe usarse para afectar a todos los elementos que se ajustan a algo, sino que solo debe usarse para decirle si cada elemento de una colección lo hace. Si desea aplicar una función a todos los elementos para los que un predicado se evalúa como verdadero, la forma "buena" de hacerlo es

var filtered = array.filter(function(item) {
    return typeof item === "number";
});

var mapped = filtered.map(function(item) {
    return item.foo(); //provided foo() has no side effects and returns a new object of item's type instead.  See note about foreach below.
});

Alternativamente, puede usar en foreachlugar de mapa para modificar los elementos en el lugar.

La misma lógica se aplica somebásicamente a:

  • Se usa everypara probar si todos los elementos de una matriz pasan alguna prueba.
  • Se usa somepara probar si al menos un elemento de una matriz pasa alguna prueba.
  • Se utiliza mappara devolver una nueva matriz que contiene 1 elemento (que es el resultado de una función de su elección) para cada elemento en una matriz de entrada.
  • Se utiliza filterpara devolver una matriz de longitud 0 < length< initial array lengthelementos, todos contenidos en la matriz original y todo pasar la prueba predicado suministrado.
  • Usas foreachsi quieres un mapa pero en el lugar
  • Se usa reducesi desea combinar los resultados de una matriz en un solo resultado de objeto (que podría ser una matriz pero no tiene que ser así).

Cuanto más los use (y más escriba el código LISP), más se dará cuenta de cómo están relacionados e incluso es posible emular / implementar uno con los demás. Lo que es poderoso con estas consultas y lo que es realmente interesante es su semántica y cómo realmente lo empujan a eliminar los efectos secundarios dañinos en su código.

EDITAR (a la luz de los comentarios): Entonces, supongamos que desea validar que cada elemento es un objeto y convertirlos en un Modelo de aplicación si todos son válidos. Una forma de hacer esto en una sola pasada sería:

var dirty = false;
var app_domain_objects = input.map(function(item) {
    if(validate(item)) {
        return new Model(item);
    } else {
        dirty = true; //dirty is captured by the function passed to map, but you know that :)
    }
});
if(dirty) {
    //your validation test failed, do w/e you need to
} else {
    //You can use app_domain_objects
}

De esta manera, cuando un objeto no pasa la validación, aún sigue iterando a través de toda la matriz, lo que sería más lento que simplemente validar con every. Sin embargo, la mayoría de las veces su matriz será válida (o eso espero), por lo que en la mayoría de los casos realizará una sola pasada sobre su matriz y terminará con una matriz utilizable de objetos del Modelo de aplicación. Se respetará la semántica, se evitarán los efectos secundarios y todos estarán felices.

Tenga en cuenta que también podría escribir su propia consulta, similar a foreach, que aplicaría una función a todos los miembros de una matriz y devolverá verdadero / falso si todos pasan una prueba de predicado. Algo como:

function apply_to_every(arr, predicate, func) {
    var passed = true;
    for(var i = 0; i < array.length; ++i) {
        if(predicate(arr[i])) {
            func(arr[i]);
        } else {
            passed = false;
            break;
        }
    }
    return passed;
}

Aunque eso modificaría la matriz en su lugar.

Espero que esto ayude, fue muy divertido escribir. ¡Salud!

pwny
fuente
Gracias por tu respuesta. No estoy necesariamente tratando de modificar los elementos en su lugar per-se; en mi código real, recibo una matriz de objetos con formato JSON, por lo que primero estoy validando la entrada if (input.every()), para verificar que cada elemento sea un objeto ( typeof el === "object && el !== null), etc., luego , si eso valida, quiero convertir cada elemento en el respectivo modelo de aplicación (que, ahora mencionas map(), podría usar input.map(function (el) { return new Model(el); });; pero no necesariamente en su lugar .)
Isaac
... pero veo que incluso con map()tener que iterar sobre la matriz dos veces; una para validar y otra para convertir. Sin embargo, utilizando un estándar de for(;;;)bucle, que podía hacer esto utilizando una iteración, pero no puedo encontrar una manera de aplicar every, some, mapo filter, en este escenario, y realizar sólo un paso, sin tener-secundarios indeseados efectos o la introducción de otro modo mal- práctica.
Isaac
@Isaac Muy bien, perdón por la demora, entiendo tu situación más claramente ahora. Editaré mi respuesta para agregar algunas cosas.
pwny
Gracias por la gran respuesta; ha sido realmente útil :).
Isaac
-1

Los efectos secundarios no están en la condición if, están en el cuerpo del if. Solo ha determinado si ejecutar o no ese cuerpo en la condición real. No hay nada malo con su enfoque aquí.

DeadMG
fuente
Hola, gracias por su respuesta. Lo siento, pero o no he entendido bien su respuesta, o ha malinterpretado el código ... todo en mi fragmento de código está dentro de la ifcondición, con solo el returnser dentro del ifcuerpo; obviamente estoy hablando de la muestra de código precedida por " lo que quiero hacer es ...
Isaac
1
Lo sentimos, los efectos secundarios de @ Issac están de hecho en la ifcondición.
Ross Patterson