Arrayの「すべて」または「一部」の副作用は悪いですか?


9

私は常に、if状態に副作用があることは悪いことだと教えられてきました。つまり、

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

... とは対照的に;

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

...そして、私はそれを理解しています。私はそれをしなかったので私の同僚は幸せです、そして私たちはすべて金曜日の17:00に家に帰り、誰もが楽しい週末を過ごします。

今、ECMAScript5はto every()some()to 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. アムI(ミス| AB)を使用して、someおよびevery必要があります私が使用しているfor(;;;)。このために?)
  3. より良いアプローチはありますか?

3
クエリ、フィルター、マップ、リデュースのすべてと一部はクエリであり、悪用しても副作用はありません。
Benjamin Gruenbaum

@BenjaminGruenbaum:では、歯がなくなることが多いのではないでしょうか?9/10、私の使用があればsome、私がやりたい何かを私の使用があれば、要素とevery、私はこれらの要素のすべてに何かをしたい... someeveryどちらか私はできませんので、私はその情報にアクセスすることはできません。それらを使用する、副作用を追加する必要があります。
アイザック

いいえ。副作用について言及する場合、それは体ではないにしても頭の内側を意味します。本体の内部では、好きなように変更できます。いつか/に渡すコールバック内のオブジェクトを変更しないでください。
Benjamin Gruenbaum 2013

@BenjaminGruenbaum:しかし、それはまさに私の趣旨です。私が使用している場合はsome、私の中でif、配列内の特定の要素は、私が動作する必要がある特定の性質、9/10発揮するかどうかを判断するための条件私の中にその要素if本体と、今、としてsome私に教えてくれないそのプロパティを示した要素の(ちょうど「1がした」)、私はどちらかを使用することができsome 、再び体(O(2N))に、または私はちょうど内部の作業を行うことができる場合(条件頭の中での副作用のため、これは悪いことです)。
アイザック

... everyもちろん、同じことが当てはまります。
アイザック

回答:


8

私が正しくあなたのポイントを理解していれば、あなたは誤使用や乱用のように見える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配列内の少なくとも1つの要素が何らかのテストに合格するかどうかをテストするために使用します。
  • を使用mapして、入力配列のすべての要素に対して1つの要素(選択した関数の結果)を含む新しい配列を返します。
  • あなたが使用するfilter長さ0 <配列返すようにlength< initial array length元素供給述語テストを通過する元の配列に含まれるすべておよびすべて。
  • foreachマップが必要な場合はインプレースで使用します
  • reduce配列の結果を単一のオブジェクト結果に結合する場合に使用します(配列の場合もありますが、必須ではありません)。

それらを使用すればするほど(そしてLISPコードを作成するほど)、それらがどのように関連しているか、および他の1つをエミュレート/実装することさえ可能であることに気づくでしょう。これらのクエリの強力な点と本当に興味深いのは、そのセマンティクスと、コード内の有害な副作用の排除に向けてどのようにプッシュするかです。

編集(コメントに照らして):では、すべての要素がオブジェクトであることを検証し、それらがすべて有効である場合はアプリケーションモデルに変換するとします。シングルパスでこれを行う1つの方法は次のとおりです。

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

ただし、配列が変更されます。

これがお役に立てば幸いです。書くのはとても楽しかったです。乾杯!


ご回答有難うございます。私は必ずしも要素を変更しようとしていないよ場所でそれ自体は、私の実際のコードでは、私が最初だので、オブジェクトのJSON形式のアレイを受信してい検証入力をif (input.every())、各要素がいることを確認することである(オブジェクトtypeof el === "object && el !== nullなど)次にことを検証は、私はに各要素を変換する場合今、あなたが言及し、それぞれのアプリケーションモデル(map()私は使用することができinput.map(function (el) { return new Model(el); });、必ずしもではない場所で
アイザック

..しかしmap()、配列を2回反復する必要がある場合でも、1回は検証し、もう1回は変換します。しかし、標準使用してfor(;;;)ループを、私は一回の反復を使用してこれを行うことができますが、私は、適用する方法を見つけることができないeverysomemapまたはfilterこのシナリオでは、とのみ行う1パスを、望ましくない、副作用を持つか、そうでなければbad-を導入することなく、練習。
Isaac

@Isaac申し訳ありません。遅れて申し訳ありませんが、私はあなたの状況をより明確に理解しました。答えを編集して、いくつか追加します。
pwny 2013

すばらしい答えをありがとう; それは本当に役に立ちました:)。
アイザック

-1

副作用はif条件ではなく、ifの本体にあります。実際の状態でその本体を実行するかどうかのみを決定しました。ここでのアプローチに問題はありません。


こんにちは、回答ありがとうございます。申し訳ありませんが、あなたの答えを誤解したか、コードを誤って解釈したかのいずれかです...コードスニペットのすべてがif条件return内にあり、ifの本文内にのみ存在します。明らかに、 "の前にあるコードサンプルの前にあるコードサンプルについて話しています "
Isaac

1
申し訳ありませんが、@ Issacの副作用は実際にif状態にあります。
ロスパターソン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.