Являются ли побочные эффекты в 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. Использую ли я (mis | 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, конечно.
Исаак

Ответы:

8

Если я правильно понимаю вашу точку правильно, вы , кажется, неправильно использовать или злоупотребления everyи , someно это немного неизбежно , если вы хотите изменить элементы ваших массивов непосредственно. Поправьте меня, если я ошибаюсь, но то, что вы пытаетесь сделать, это выяснить, имеет ли какой-то или каждый элемент в вашей последовательности определенное условие, а затем изменить эти элементы. Кроме того, ваш код, кажется, применяет что-то ко всем элементам, пока вы не найдете тот, который не проходит предикат, и я не думаю, что вы это делаете. В любом случае.

Давайте возьмем ваш первый пример (слегка измененный)

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

То, что вы делаете здесь, на самом деле идет вразрез с духом концепций «некоторые / каждый / карта / уменьшение / фильтр / и т.д.». Everyне предназначен для того, чтобы воздействовать на каждый элемент, который соответствует чему-либо, скорее он должен использоваться только для того, чтобы сообщать вам, есть ли каждый элемент в коллекции. Если вы хотите применить функцию ко всем элементам, для которых предикат оценивается как true, «хороший» способ сделать это

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), тем больше вы понимаете, как они связаны и как даже возможно эмулировать / реализовать одно с другими. Что важно в этих запросах и что действительно интересно, так это их семантика и то, как они действительно подталкивают вас к устранению вредных побочных эффектов в вашем коде.

РЕДАКТИРОВАТЬ (в свете комментариев): Допустим, вы хотите проверить, что каждый элемент является объектом, и преобразовать их в модель приложения, если они все действительны. Один из способов сделать это за один проход:

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. Тем не менее, большую часть времени ваш массив будет действительным (или я должен на это надеяться), поэтому в большинстве случаев вы выполняете один проход над вашим массивом и в итоге получаете пригодный для использования массив объектов модели приложения. Семантика будет соблюдаться, побочных эффектов не будет, и все будут счастливы!

Обратите внимание, что вы также можете написать свой собственный запрос, похожий на 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;
}

Хотя это изменило бы массив на месте.

Надеюсь, это поможет, было очень весело писать. Ура!

pwny
источник
Спасибо за Ваш ответ. Я не обязательно пытаюсь изменить элементы на месте за таковой; в моем реальном коде я получаю массив объектов в формате 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- практика.
Исаак
@ Исаак Хорошо, извините за задержку, теперь я понимаю вашу ситуацию более четко. Я отредактирую свой ответ, чтобы добавить кое-что.
pwny
Спасибо за отличный ответ; это было действительно полезно :).
Исаак
-1

Побочные эффекты не в условии if, а в теле if. Вы только определили, следует ли выполнять это тело в фактическом состоянии. Здесь нет ничего плохого в вашем подходе.

DeadMG
источник
Привет спасибо за ответ Извините, но либо я неправильно понял ваш ответ, либо вы неверно истолковали код ... все в моем фрагменте кода находится внутри ifусловия, только returnвнутри существа if; очевидно, я имею в виду пример кода, которому предшествует « то, что нужно делать, это; ...
Исаак
1
Извините, побочные эффекты @ Issac действительно в ifсостоянии.
Росс Паттерсон