Czy skutki uboczne „Array” są „każde”, czy „niektóre” złe?

9

Zawsze uczono mnie, że skutki uboczne w danym ifstanie są złe. Chodzi mi o to że;

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

... w przeciwieństwie do;

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

... i rozumiem to, a moi koledzy są szczęśliwi, ponieważ tego nie robię i wszyscy wracamy do domu o 17:00 w piątek i wszyscy mają wesoły weekend.

Teraz ECMAScript5 wprowadził metody takie jak every()i some()do Arrayi uważam je za bardzo przydatne. Są czystsze niż for (;;;), dają ci inny zasięg i sprawiają, że element jest dostępny przez zmienną.

Jednak podczas sprawdzania poprawności danych wejściowych częściej niż nie używam every/ somew warunku do sprawdzania poprawności danych wejściowych, a następnie używam every/ some ponownie w treści, aby przekonwertować dane wejściowe na użyteczny model;

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

... kiedy chcę to zrobić;

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

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

    return res;
})) {
    return;
}

... co daje mi skutki uboczne w ifstanie, który jest zły.

Dla porównania jest to kod wyglądający na stary for (;;;);

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

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

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

Moje pytania to:

  1. Czy to zdecydowanie zła praktyka?
  2. Czy używam (mis | ab) somei every( powinienem używać for(;;;)do tego?)
  3. Czy istnieje lepsze podejście?
Izaak
źródło
3
Wszystkie, a także filtr, mapa i redukcja są zapytaniami, nie mają żadnych skutków ubocznych, jeśli ich nadużywasz.
Benjamin Gruenbaum,
@BenjaminGruenbaum: Czy to nie czyni ich bezzębnymi częściej niż nie? 9/10, jeśli używam some, chcę coś zrobić z elementem, jeśli używam every, chcę zrobić coś z tymi wszystkimi elementami ... somei everynie pozwól mi uzyskać dostępu do tych informacji, więc albo nie mogę użyj ich, albo muszę dodać efekty uboczne.
Izaak
Nie. Kiedy mówię o skutkach ubocznych, mam na myśli wnętrze głowy, jeśli nie ciała. Wewnątrz ciała możesz go modyfikować w dowolny sposób. Po prostu nie mutuj obiektu w wywołaniu zwrotnym, które przekazujesz komuś / kiedy.
Benjamin Gruenbaum,
@BenjaminGruenbaum: Ale właśnie o to mi chodzi. Jeśli użyję somew moim ifstanie, aby ustalić, czy określony element w tablicy wykazuje określoną właściwość, 9/10 muszę działać na tym elemencie w moim ifciele; teraz, ponieważ somenie mówi mi, który z elementów wykazywał właściwość (tylko „jeden zrobił”), mogę albo użyć some ponownie w ciele (O (2n)), albo mogę po prostu wykonać operację wewnątrz warunku if ( co jest złe, ponieważ jest to efekt uboczny w głowie).
Izaak
... to samo dotyczy everyoczywiście.
Izaak

Odpowiedzi:

9

Jeśli dobrze rozumiem twój punkt widzenia poprawnie, to wydaje się być mis-using lub nadużywanie everyi someale to trochę nieuniknione, jeśli chcesz zmienić elementy swoich tablic bezpośrednio. Popraw mnie, jeśli się mylę, ale starasz się dowiedzieć, czy jakiś element w sekwencji ma określony warunek, a następnie zmodyfikuj te elementy. Ponadto twój kod wydaje się nakładać coś na wszystkie elementy, dopóki nie znajdziesz takiego, który nie przejdzie predykatu, i nie sądzę, że to właśnie chcesz zrobić. Tak czy inaczej.

Weźmy twój pierwszy przykład (nieco zmodyfikowany)

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

To, co tu robisz, jest trochę sprzeczne z duchem niektórych koncepcji / Every / Map / Reduce / Filter / etc. Everynie ma na celu wpływania na każdy element, który jest zgodny z czymś, ale powinien być używany tylko do informowania, czy każdy element w kolekcji ma taki efekt. Jeśli chcesz zastosować funkcję do wszystkich elementów, dla których predykat ocenia się na prawdziwy, „dobrym” sposobem na to jest

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

Alternatywnie możesz użyć foreachzamiast mapy, aby zmodyfikować przedmioty w miejscu.

Ta sama logika dotyczy w somezasadzie:

  • Służy everydo testowania, czy wszystkie elementy tablicy przeszły jakiś test.
  • Służy somedo testowania, jeśli przynajmniej jeden element w tablicy przejdzie jakiś test.
  • Użyć map, aby powrócić nową tablicę zawierającą 1 pozycja (co jest wynikiem funkcji wyboru) dla każdego elementu w tablicy wejściowej.
  • Używać filterpowrotu tablicę o długości 0 < length< initial array lengthelementów, wszystkie zawarte w początkowym tablicy, a wszystkie przechodzą dostarczonego testu źródłowe.
  • Używasz, foreachjeśli chcesz mapę, ale w miejscu
  • Używasz, reducejeśli chcesz połączyć wyniki tablicy w wynik jednego obiektu (który może być tablicą, ale nie musi).

Im częściej ich używasz (i im więcej piszesz kod LISP), tym bardziej zdajesz sobie sprawę, jak są one powiązane i w jaki sposób można nawet emulować / implementować jeden z innymi. Mocną stroną tych zapytań i naprawdę interesującą jest ich semantyka oraz to, jak naprawdę popychają cię w kierunku eliminacji szkodliwych efektów ubocznych w kodzie.

EDYCJA (w świetle komentarzy): powiedzmy, że chcesz sprawdzić, czy każdy element jest obiektem i przekonwertować go na model aplikacji, jeśli wszystkie są poprawne. Jednym ze sposobów na zrobienie tego w jednym przejściu byłoby:

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
}

W ten sposób, gdy obiekt nie przejdzie sprawdzania poprawności, nadal iterujesz przez całą tablicę, co byłoby wolniejsze niż zwykłe sprawdzanie poprawności every. Jednak przez większość czasu twoja tablica będzie poprawna (lub mam taką nadzieję), więc w większości przypadków wykonasz pojedyncze przejście nad tablicą i skończysz na użytecznej tablicy obiektów modelu aplikacji. Semantyka będzie przestrzegana, uniknie się skutków ubocznych i wszyscy będą szczęśliwi!

Zauważ, że możesz również napisać własne zapytanie, podobne do foreach, które zastosuje funkcję do wszystkich elementów tablicy i zwróci wartość prawda / fałsz, jeśli wszystkie przejdą test predykatu. Coś jak:

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

Chociaż to zmodyfikowałoby tablicę na miejscu.

Mam nadzieję, że to pomaga, pisanie było bardzo fajne. Twoje zdrowie!

pwny
źródło
Dzięki za odpowiedź. Ja nie koniecznie próbuje modyfikować elementy w miejscu per se; w moim rzeczywistym kodzie otrzymuję tablicę obiektów w formacie JSON, więc najpierw sprawdzam poprawność danych wejściowych if (input.every()), aby sprawdzić, czy każdy element jest obiektem ( typeof el === "object && el !== null) itp., a następnie, jeśli to się sprawdza, chcę przekonwertować każdy element na odpowiedni model aplikacji (który teraz wspominasz, map()że mógłbym użyć input.map(function (el) { return new Model(el); });; ale niekoniecznie na miejscu .
Isaac
.. ale zobacz, że nawet jeśli map()muszę dwa razy iterować tablicę; raz, aby potwierdzić, a drugi do konwersji. Jednak stosując standardową for(;;;)pętlę, mogę to zrobić za pomocą jednej iteracji, ale nie mogę znaleźć sposób, aby zastosować every, some, maplub filterw tym scenariuszu i wykonać tylko jedną przepustkę, bez niepożądanych skutków ubocznych, lub w inny sposób wprowadza złe- ćwiczyć.
Izaak
@Isaac W porządku, przepraszam za opóźnienie, teraz lepiej rozumiem twoją sytuację. Zmodyfikuję swoją odpowiedź, aby dodać kilka rzeczy.
pwny
Dzięki za świetną odpowiedź; to było naprawdę pomocne :).
Izaak
-1

Efekty uboczne nie są w stanie if, są w ciele if. Określiłeś tylko, czy wykonać to ciało w rzeczywistych warunkach. W tym podejściu nie ma nic złego.

DeadMG
źródło
Cześć, dzięki za odpowiedź. Przepraszam, ale albo źle zrozumiałem twoją odpowiedź, albo źle zinterpretowałeś kod ... wszystko w moim fragmencie kodu jest w ifstanie warunkowym, a tylko returnistota jest w ifciele; oczywiście mówię o próbce kodu poprzedzonej przez „ to, co chcemy robić, to: ...
Isaac
1
Niestety, skutki uboczne @ Issac są rzeczywiście w ifstanie.
Ross Patterson