Чи є побічні ефекти у Array "кожним" чи "деяким" поганими?


9

Мене завжди вчили, що побічні ефекти в ifстані - це погано. Що я маю на увазі, це;

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

... на відміну від;

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

... і я це розумію, і мої колеги раді, тому що я цього не роблю, і ми всі йдемо додому о 17:00 у п’ятницю, і всі мають веселі вихідні.

Тепер ECMAScript5 представив такі методи, як every()і some()до Array, і я вважаю їх дуже корисними. Вони чистіші, ніж for (;;;)', дають вам іншу область застосування та роблять елемент доступним змінною.

Однак під час перевірки введення я частіше за все не знаходжу себе за допомогою every/ someза умови перевірки введення, а потім використовую every/ some знову в тілі, щоб перетворити вхід у корисну модель;

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

... коли те, що я хочу робити, - це;

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

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

    return res;
})) {
    return;
}

... що дає мені побічні ефекти в ifстані, що погано.

Для порівняння, це код, який би виглядав зі старим for (;;;);

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

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

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

Мої запитання:

  1. Це, безумовно, погана практика?
  2. Чи я (неправильно | ab) використовую someі every(чи повинен я використовувати for(;;;)для цього?)
  3. Чи є кращий підхід?

3
Кожен, а також фільтр, карта та зменшення - це запити, вони не мають побічних ефектів, якщо ви їх зловживаєте.
Бенджамін Грюнбаум

@BenjaminGruenbaum: Тож чи це не робить їх беззубими частіше? 9/10, якщо я використовую some, я хочу зробити щось з елементом, якщо я використовую every, я хочу зробити щось з усіма цими елементами ... someі everyне дозволяти мені отримувати доступ до цієї інформації, тому я не можу використовувати їх, або я повинен додати побічні ефекти.
Ісаак

Ні. Коли я маю на увазі побічні ефекти, я маю на увазі всередині голови, якщо не тіла. Всередині корпусу ви можете змінювати його будь-яким способом. Просто не мутуйте об'єкт всередині зворотного дзвінка, який ви передаєте деякому / коли.
Бенджамін Грюнбаум

@BenjaminGruenbaum: Але це саме моя думка. Якщо я використовую someв моєму ifстані , щоб визначити , чи має певний елемент в масиві певного властивості, 9/10 Мені потрібно працювати на цьому елементі в моєму ifтілі; тепер, як someне підказує мені, який із елементів виявив властивість (просто "один зробив"), я можу або використовувати some знову в тілі (O (2n)), або я можу просто виконати операцію всередині умови if ( що погано, тому що його побічна дія всередині голови).
Ісаак

... те ж саме стосується everyі, звичайно.
Ісаак

Відповіді:


9

Якщо я правильно розумію вашу точку правильно, ви , здається, неправильно використовувати або зловживання everyі , someале це трохи неминуче , якщо ви хочете змінити елементи ваших масивів безпосередньо. Виправте мене, якщо я помиляюся, але те, що ви намагаєтесь зробити, це з’ясувати, чи є якийсь елемент чи послідовність у вашій послідовності певним умовою, а потім змініть ці елементи. Крім того, ваш код, здається, застосовує щось до всіх елементів, поки ви не знайдете той, який не проходить предикат, і я не думаю, що це ви хочете робити. Все одно.

Візьмемо ваш перший приклад (трохи модифікований)

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

Те, що ви тут робите, насправді трохи суперечить духу концепцій деяких / кожного / карта / зменшення / фільтра / тощо. Everyне призначений для використання для впливу на кожен предмет, який відповідає чомусь, скоріше, він повинен використовуватись лише для того, щоб повідомити, якщо кожен предмет колекції є. Якщо ви хочете застосувати функцію до всіх елементів, для яких предикат оцінюється як істинний, "хороший" спосіб зробити це

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

Крім того, ви можете використовувати foreachзамість карти для зміни елементів на місці.

Ця ж логіка стосується some, в основному:

  • Ви використовуєте everyдля тестування, чи всі елементи масиву проходять тест.
  • Ви використовуєте someдля тестування, якщо хоча б один елемент масиву проходить якийсь тест.
  • Ви використовуєте mapдля повернення нового масиву, що містить 1 елемент (що є результатом функції на ваш вибір) для кожного елемента вхідного масиву.
  • Ви можете використовувати , filterщоб повернути масив довжиною 0 < length< initial array lengthелементів, все що містяться в вихідному масиві і все проходить перевірку доданого предиката.
  • Ви використовуєте, foreachякщо хочете карту, але на місці
  • Ви використовуєте, reduceякщо хочете об'єднати результати масиву в один результат об'єкта (який може бути масивом, але не повинен).

Чим більше ви їх використовуєте (і чим більше ви пишете код LISP), тим більше ви усвідомлюєте, як вони пов’язані між собою і як навіть можливо емуляцію / реалізацію одного з іншими. Те, що справді цікаво для цих запитів, і що насправді цікаво - це їх семантика та те, як вони насправді підштовхують вас до усунення шкідливих побічних ефектів у вашому коді.

EDIT (з урахуванням коментарів): Отже, скажімо, що ви хочете перевірити, що кожен елемент є об'єктом, і перетворити їх у прикладну модель, якщо всі вони дійсні. Один із способів зробити це за один прохід:

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
}

Таким чином, коли об’єкт не проходить перевірку, ви все одно продовжуєте ітерацію через весь масив, що буде повільніше, ніж просто перевірити every. Однак більшу частину часу ваш масив буде дійсним (або, я сподіваюся, що так), тому в більшості випадків ви виконаєте один прохід над своїм масивом і в кінцевому підсумку використовуєте масив об'єктів Application Model. Семантика буде дотримуватися, побічних ефектів уникати, і всі будуть раді!

Зауважте, що ви також можете написати власний запит, подібний до foreach, який застосував би функцію до всіх членів масиву і повертав true / false, якщо всі вони пройдуть тест предиката. Щось на зразок:

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

Хоча це змінило б масив на місці.

Сподіваюся, це допомагає, писати було дуже весело. Ура!


Дякую за вашу відповідь. Я не обов'язково намагаюся змінювати елементи на місці ; у своєму фактичному коді я отримую масив об’єктів у форматі JSON, тому я спочатку перевіряю вхід if (input.every()), щоб перевірити, чи кожен елемент є об'єктом ( typeof el === "object && el !== null) тощо, потім, якщо це підтверджує, я хочу перетворити кожен елемент у відповідну модель програми (яку зараз ви вже згадуєте, map()я міг би використовувати input.map(function (el) { return new Model(el); });, але не обов'язково на місці .
Ісаак

.. але бачте, що навіть якщо map()мені доведеться двічі перебирати масив; один раз для перевірки, а інший для перетворення. Однак, використовуючи стандартну for(;;;)петлю, я міг би зробити це з допомогою однієї ітерації, але я не можу знайти спосіб застосувати every, some, mapабо filterв цьому сценарії, і виконати тільки один прохід, без небажаного побічного -побочних ефектів або іншого введення bad- практика.
Ісаак

@Isaac Добре, вибачте за затримку, я зрозумів вашу ситуацію чіткіше зараз. Я відредагую свою відповідь, щоб додати деякі речі.
pwny

Дякую за чудову відповідь; це було дуже корисно :).
Ісаак

-1

Побічні ефекти не в стані if, вони є в організмі if. Ви лише визначили, чи слід виконувати цей орган у фактичному стані. Тут немає нічого поганого у вашому підході.


Привіт, дякую за вашу відповідь. Вибачте, але я неправильно зрозумів вашу відповідь, або ви неправильно інтерпретували код ... все, що знаходиться в моєму фрагменті коду, знаходиться в ifумові, лише returnістота знаходиться в ifтілі тіла; Очевидно, я говорю про зразок коду, який передував " те, що хочеться робити, це ... ;
Ісаак

1
Вибачте, побічні ефекти @ Issac дійсно в ifстані.
Росс Паттерсон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.