Os efeitos colaterais no Array são "todos" ou "alguns" ruins?

9

Eu sempre fui ensinado que ter efeitos colaterais em uma ifcondição é ruim. O que eu quero dizer é;

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

... em oposição a;

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

... e eu entendo isso, e meus colegas estão felizes porque eu não faço, e todos nós voltamos para casa às 17:00 na sexta-feira e todo mundo tem um fim de semana feliz.

Agora, o ECMAScript5 introduziu métodos como every()e some()para Array, e eu os acho muito úteis. Eles são mais limpos que for (;;;)os outros, oferecem outro escopo e tornam o elemento acessível por uma variável.

No entanto, ao validar a entrada, eu geralmente me encontro usando every/ somena condição de validar a entrada e depois uso every/ some novamente no corpo para converter a entrada em um modelo utilizável;

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

... quando o que eu quero fazer é;

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

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

    return res;
})) {
    return;
}

... o que me causa efeitos colaterais em uma ifcondição ruim.

Em comparação, esse é o código com o qual seria antigo for (;;;);

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

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

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

Minhas perguntas são:

  1. Definitivamente, é uma prática ruim?
  2. Estou (mis | ab) usando somee every( devo usar um for(;;;)para isso?)
  3. Existe uma abordagem melhor?
Isaac
fonte
3
Todas e algumas, além de filtrar, mapear e reduzir, são consultas, elas não têm efeitos colaterais, se você estiver abusando delas.
Benjamin Gruenbaum 9/02/2013
@BenjaminGruenbaum: Então, isso não os torna mais desdentados do que nunca? 9/10, se eu usar some, quero fazer algo com o elemento, se eu usar every, quero fazer algo com todos esses elementos ... somee everynão me permita acessar essas informações, para que não possa usá-los, ou eu tenho que adicionar efeitos colaterais.
Isaac
Não. Quando me refiro aos efeitos colaterais, quero dizer dentro da cabeça do corpo, se não do corpo. Dentro do corpo, você pode modificá-lo da maneira que desejar. Apenas não mude o objeto dentro do retorno de chamada que você passa para algum / quando.
Benjamin Gruenbaum 9/02/2013
@BenjaminGruenbaum: Mas esse é exatamente o meu ponto. Se eu usar somena minha ifcondição para determinar se um determinado elemento da matriz exibe uma determinada propriedade, 9/10 eu preciso operar com esse elemento no meu ifcorpo; agora, como somenão me diz qual dos elementos exibia a propriedade (apenas "um fez"), posso usar some novamente no corpo (O (2n)) ou executar a operação dentro da condição if ( o que é ruim, porque é um efeito colateral na cabeça).
Isaac
... o mesmo se aplica everytambém, é claro.
Isaac

Respostas:

8

Se eu entendo o seu ponto corretamente, você parece ser mis-usando ou abusando everye somemas é um pouco inevitável se você quiser mudar os elementos de suas matrizes diretamente. Corrija-me se estiver errado, mas o que você está tentando fazer é descobrir se algum ou todos os elementos da sua sequência exibem uma determinada condição e depois modifica-os. Além disso, seu código parece estar aplicando algo a todos os itens até você encontrar um que não passa no predicado e acho que não é isso que você quer fazer. Enfim.

Vamos dar o seu primeiro exemplo (ligeiramente modificado)

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

O que você está fazendo aqui é um pouco contrário ao espírito dos conceitos de alguns / todos / mapas / reduções / filtros / etc. Everynão deve ser usado para afetar todos os itens que estejam em conformidade com algo, mas deve ser usado apenas para informar se todos os itens de uma coleção são compatíveis. Se você deseja aplicar uma função a todos os itens para os quais um predicado é avaliado como verdadeiro, a maneira "boa" de fazer isso é

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.
});

Como alternativa, você pode usar em foreachvez do mapa para modificar os itens no local.

A mesma lógica se aplica some, basicamente:

  • Você usa everypara testar se todos os elementos de uma matriz passam em algum teste.
  • Você usa somepara testar se pelo menos um elemento de uma matriz passa em algum teste.
  • Você usa mappara retornar uma nova matriz contendo 1 elemento (que é o resultado de uma função de sua escolha) para cada elemento em uma matriz de entrada.
  • Você usa filterpara retornar uma matriz de comprimento 0 < length< initial array lengthelementos, todos contidos na matriz original e todos passar no teste predicado fornecido.
  • Você usa foreachse quiser mapa, mas no local
  • Você pode usar reducese quiser combinar os resultados de uma matriz em um único resultado de objeto (que pode ser uma matriz, mas não precisa).

Quanto mais você os usa (e quanto mais escreve código LISP), mais percebe como eles estão relacionados e como é ainda possível emular / implementar um com os outros. O que é poderoso nessas consultas e o que é realmente interessante são as semânticas e como elas realmente o pressionam para eliminar efeitos colaterais prejudiciais no seu código.

EDIT (à luz dos comentários): digamos que você queira validar que cada elemento é um objeto e convertê-los em um Modelo de Aplicativo, se todos forem válidos. Uma maneira de fazer isso em uma única passagem seria:

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
}

Dessa forma, quando um objeto não passa na validação, você continua repetindo toda a matriz, o que seria mais lento do que a validação every. No entanto, na maioria das vezes sua matriz será válida (ou eu espero que sim), portanto, na maioria dos casos, você executará uma única passagem sobre sua matriz e terminará com uma matriz utilizável de objetos do Modelo de Aplicativo. A semântica será respeitada, os efeitos colaterais serão evitados e todos serão felizes!

Observe que você também pode escrever sua própria consulta, semelhante ao foreach, que aplicaria uma função a todos os membros de uma matriz e retornará verdadeiro / falso se todos passarem em um teste 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;
}

Embora isso modifique a matriz no lugar.

Espero que isso ajude, foi muito divertido escrever. Felicidades!

pwny
fonte
Obrigado pela sua resposta. Não estou necessariamente tentando modificar os elementos em si; no meu código real, eu estou recebendo uma matriz formatada em JSON de objetos, por isso estou primeira validar a entrada if (input.every()), para verificar que cada elemento é um objeto ( typeof el === "object && el !== null) etc, então se que valida, eu quero converter cada elemento em o respectivo modelo de aplicativo (que, agora que você mencionou map()que eu poderia usar input.map(function (el) { return new Model(el); });, mas não necessariamente no lugar .
Isaac
.. mas veja que, mesmo map()tendo de repetir a matriz duas vezes; uma vez para validar e outra para converter. No entanto, usando um padrão de for(;;;)circuito, que poderia fazer isso usando uma iteração, mas não consegue encontrar uma maneira de aplicar every, some, mapou filterneste cenário, e executar apenas uma passagem, sem ter-secundários indesejáveis efeitos ou de outro modo que introduz ruim- prática.
Isaac
@ Isaac Tudo bem, desculpe pela demora, eu entendo sua situação mais claramente agora. Vou editar minha resposta para adicionar algumas coisas.
Pwny
Obrigado pela ótima resposta; tem sido realmente útil :).
Isaac
-1

Os efeitos colaterais não estão na condição if, eles estão no corpo do if. Você determinou apenas se deve ou não executar esse corpo na condição real. Não há nada de errado com sua abordagem aqui.

DeadMG
fonte
Oi, obrigado pela sua resposta. Desculpe, mas entendi mal a sua resposta ou você interpretou mal o código ... tudo no meu trecho de código está dentro da ifcondição, apenas com o returnser dentro do ifcorpo do corpo; Obviamente, estou falando sobre o exemplo de código precedido por "o que queremos fazer é; ..."
Isaac
11
Desculpe, os efeitos colaterais de @ Issac estão realmente na ifcondição.
Ross Patterson