JavaScriptの for...of vs forEach

目次

TL;DR

forEachは案外悪くないです。for...of派からforEach派になりました。

ちなみにfor...ofを書き換える方法についてはこの記事では記述していません。

導入

eslint-config-airbnbをそのまま使っているとfor...of文に対しno-restricted-syntaxのエラーが出ます。

Airbnbのスタイルガイドには次のように書かれています。

11.1 Don’t use iterators. Prefer JavaScript’s higher-order functions instead of loops like for-in or for-of. eslint: no-iterator no-restricted-syntax Why? This enforces our immutable rule. Dealing with pure functions that return values is easier to reason about than side effects. Use map() / every() / filter() / find() / findIndex() / reduce() / some() / ... to iterate over arrays, and Object.keys() / Object.values() / Object.entries() to produce arrays so you can iterate over objects.

和訳

イテレータを使わない。for-inやfor-ofのようなループよりもJavaScript高階関数を優先する。eslint: no-iterator no-restricted-syntax 何故か?これは不変ルールを遵守する。値を返す純粋関数を扱う方が副作用より推論しやすい。 配列に対して繰り返すには map() / every() / filter() / find() / findIndex() / reduce() / some() / ... を使い、オブジェクトに対して繰り返すには Object.keys() / Object.values() / Object.entries()を使う。

たしかにその通りです。私も関数型のプログラミングを好みますし、可能ならmap() / every() / filter() / find() / findIndex() / reduce() / some() / ...を使います。 しかし、パフォーマンスなどの理由で副作用を起こしたいこともあります。副作用を起こすのであれば、関数型寄りの記述である高階関数ではなく手続き型のfor..of文で書いた方が自然なのではないか?と思えてしまいます。

気になって検索してみたところ、for...ofを禁止する理由について尋ねるairbnb/javascriptのIssueにたどり着きます。150以上のコメントのつく議論を読んでいて、やむを得ない場合forEachによる副作用もmake senseだと思え、興味深い議論だと思ったのでまとめました。

副作用を起こす場合for...ofよりforEachを使う論拠

主な論拠は下記の2つです。

  • map, every, filter,... を使わずforEachを使っていることで副作用の不吉な臭いを明示できる
  • for...ofに対するpollyfilが要求するregenerator-runtimeは重すぎる

forEachにより不吉な臭いを明示できる

この論拠はこのコメントに書かれています。

that's exactly the point. side effecty iterations SHOULD be a code smell.

和訳

まさにそれがキモです。副作用のある繰り返しは不吉な臭いがするべきです。

これはこのコメントへの返信です。

However, it is my opinion that side effects in array iteration is a bad practice and you should be explicit when you are looping over some iterator to produce side effects (e.g. appendChild, Array.push, whatever). In this case, for of has much less of a 'code smell' than Array.forEach.

和訳

しかし、配列の繰り返しにおける副作用はバッドプラクティスであり、イテレータに対し副作用(例: appendChild, Array.push, などなど)を及ぼす繰り返しをするなら明示的であるべきであるというのが私の意見です。このケースでは、for ofArray.forEachよりも「不吉な臭い」がとても少ないです。

このやりとりは私の疑問をまさに捉えており、forEachを使うべき理由として目から鱗でした。

forEachは副作用を起こす無名関数を受け取る高階関数なため、関数型のパラダイムではない中途半端で受け入れがたい存在として私はずっと避けてきました。 私は副作用を起こすなら手続き型のfor...of文を使うのが綺麗だとまさに信じていました。 そして、なんとこれはforEachを使うべき理由と両立するというのです。

副作用を及ぼすような関数型ではない書き方をするのであれば、忌々しいforEachを使うことでコードに「ここには副作用があり好ましくない」という不吉な臭いを残すべきという考え方です。

for...ofに対するpollyfilが要求するregenerator-runtimeは重すぎる

こちらの論拠も同じくこのコメントで言及されています。

for..of will not be allowed by the airbnb preset because it requires Symbols to exist, and Symbols can not truly be polyfilled - and regenerator-runtime is too heavyweight.

和訳

for...ofはSymbolが存在することが必要でそのSymbolは完全にはpolyfillできず、そしてregenerator-runtimeが重すぎるためfor...ofairbnbプリセットで許可されることはありません。

この論拠は単体ではあまり私には響きませんでした。 for...ofのpollyfillは実際に動き、パフォーマンスより可読性を重視したいので大した問題ではないと考えます。 しかし、前述の通り可読性の点でもforEachが優れているという考え方もでき、そうなるとこの論拠も含めてfor...ofを使う理由がないと言えると思います。

さらに可読性の点については「ループ内で副作用を起こす稀なケースよりもpollyfillの問題が重要だ」と同コメントに書かれています。

とはいえfor..ofを使いたいケースもあるのでは?

つかれたので暇な時に別記事にまとめます。 むしろループの書き換えの方が需要がありそうですね。

まとめ

  • 関数型が好きならやむをえず副作用を起こす際はforEachで不吉な匂いをかもそう
  • for...ofのpollyfillは重い

Chainerをバックエンドにブラックボックス変分ベイズを実装する日記[2]

内容

  • 現時点で思う理想のAPIの雰囲気を書いておく
  • 雑にクラス設計をする

考えているAPI

つくるものとして近いものはEdwardやPyro。
PyroのAPIは結構わかりやすいと感じたのでベースとして真似したい。
EdwardのAPIをもはや覚えていない。
PyroのAPIをざっくりいうと「生成モデル(model)」と「変分事後分布(guide)」の生成過程を関数として定義して推論用オブジェクトに渡す感じになっている。
modelやguideは手続き的に生成過程を書いていけばいいのでかなり簡単。
PyroのAPIがなんとなくわかるドキュメント: SVI Part I: An Introduction to Stochastic Variational Inference in Pyro — Pyro Tutorials 0.3.1 documentation


だが、真似したくない部分があってそれがPoutineによるエフェクトハンドリング。
Poutine自体がよくできていることは確かだが、初見何が起こっているのかわからない。
一見独立して定義して互いに内部の実装を知り得ないmodelとguideがグローバルな状態の管理によって紐づいている(正確でなければすみません)。

エフェクトハンドラを使わずに生成モデルと変分事後分布(と条件付け)を紐づけるために、変数名をサンプリング時ではなく生成モデルの戻り値として定義するイメージをしている。

def model():
  x = Normal(0, 1) # Distributionの扱いは雑に書いています
  y = Normal(x, 0.5)
  return {"x": x, "y": y}

雑なクラス設計

上記のAPIになるように愚直にChainerのDistributionを使って実装しようとすると、推論時に条件付けや変分事後分布によるサンプルを上書きするような介入ができない問題がある。
PyroではPoutineがやってくれる部分なのでエフェクトハンドラをやめれば当然である。
modelを呼び出した時点では計算グラフだけが構築されていて実際のサンプリングは行われていないような状態にする必要がありそう。
PyroもそうだがChainerのDistributionは「分布」から「サンプル」が得られるAPIになっており、確率変数を作ろうとした際にサンプリングが行われてしまう。
そこで、「分布」と「サンプル」の間に「確率変数」クラスを設けてみてはどうかと考えた。
f:id:seiyab:20190421112430p:plain
クラス図とは言えない雑な図だが、分布(Distribution)の__call__()からは確率変数(StochasticVariable)が得られ、確率変数(StochasticVariable)のsample()をコールすることでVariable型のサンプルとして初めて値を持つ形を考えた。
modelを呼び出したときに得られるのはStochasticVariableを使った計算グラフで、推論オブジェクトがサンプリングしたり条件付けしたりするイメージ。
うまくいくかはわからないがこのまま実装に入る。

Chainerをバックエンドにブラックボックス変分ベイズを実装する日記[1]

この記事

Chainerでブラックボックス変分ベイズを実装するぞと言う宣言。意味はない。

導入

世の中には深層学習フレームワークを自動微分バックエンドとして使用した確率的プログラミングフレームワークがある。

知っている限りだとTensorFlowをバックエンドにしたEdward (BayesFlow)、PytorchをバックエンドにしたPyro。

勉強のために昔Edwardで遊び、最近Pyroで遊んだのですがそれらで作るものが特に思い浮かばない。

職がD&Sエンジニアではなく、趣味でも用途特に思いつかない、コンペに出るほどの熱意もないので。

それでもSVIについて理解は深めたいのでChainer使ってぼちぼち自前でSVIを実装していく。

進め方

雑に進めるが、以下のようにしたい

  • 雑でもいいので小さく動くものを作って拡張していく。
  • 雑でもいいのでテストを書きながら作る
  • 日記、作業ログを書く

作るもののAPIのイメージ

poutineみたいにグローバルな副作用を扱いたくない(poutine自体を否定するものではない、自分は取り入れない)

define by runっぽさは残したい