1. HirofumiTamori

    Posted

    HirofumiTamori
Changes in title
+[翻訳] Elixir 1.2の`with`を使う(使いすぎる)
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,219 @@
+Dave Thomas[^0] さんの2016年2月23日付けのブログ記事、[(Over)using `with` in Elixir 1.2](http://pragdave.me/blog/2016/02/23/over-using-%60with%60-in-elixir-1-dot-2/)の翻訳です。
+以前[Elixirのwithを学ぶ](http://qiita.com/HirofumiTamori/items/2fc0bc36e3a178af6b71)にも別の翻訳記事を書きましたがあちらは主に「複数のマッチ式を扱える」という説明でした。
+この記事では「ローカルスコープの変数が使える」という点にもポイントを置いて説明されています。なるほど…!
+
+[^0]: "Programming Elixir"の著者。
+
+-----
+
+Elixir 1.2で表現型`with`が導入されています。新しすぎてこのブログで使っているシンタックスハイライト機能が知らないぐらいです[^1]。
+
+[^1]: Qiitaのシンタックスハイライト機能も未対応です。早く対応してください。
+
+`with`は他の関数型言語の`let`にローカルスコープの変数を定義するという点で少し似ています。こんな感じに書けるということです。
+
+```ex
+owner = "Jill"
+
+with name = "/etc/passwd",
+ stat = File.stat!(name),
+ owner = stat.uid,
+do:
+ IO.puts "#{name} is owned by user ##{owner}"
+
+IO.puts "And #{owner} is still Jill"
+```
+
+`with`式は2つの部分からできています。最初の部分は式のリスト、後の部分は`do`ブロックです。初期化式が順番に評価され続いて`do`ブロック内のコードが評価されます。`with`の内側で初めて出てきた変数は全てその`with`のローカル変数となります。この例の場合、`owner = stat.uid`の行は新しく変数を生成し、同じ名前[^2]の外側のスコープの変数のバインドを変更しません。
+
+[^2]: 1行目の`owner = "jill"`
+
+そして、大きな利点ですが、パイプラインに素直に食わせられないような複雑な関数呼出しのシーケンスを自然に分解してくれます。おおまかに言えばテンポラリ変数を使えるようになったということです。それによってコードを読むのも楽しくなります。
+
+例として、私が1年前に書いたコードを挙げましょう。これは[Earmark マークダウンパーサー](https://github.com/pragdave/earmark)のコマンドラインオプションを処理する部分です:
+
+```ex
+defp parse_args(argv) do
+ switches = [
+ help: :boolean,
+ version: :boolean,
+ ]
+ aliases = [
+ h: :help,
+ v: :version
+ ]
+
+ parse = OptionParser.parse(argv, switches: switches, aliases: aliases)
+
+ case parse do
+
+ { [ {switch, true } ], _, _ } -> switch
+ { _, [ filename ], _ } -> open_file(filename)
+ { _, [ ], _ } -> :stdio
+ _ -> :help
+ end
+end
+```
+
+ベタベタですね!このコードをよく見て何回`switches`変数が関数内で使われているか当ててみてください。じっくり構文解析してみないとわからないと思います。また末尾にある汚い`case`式も大したことない、というわけでもありません。
+
+では次に今朝私がこれをどう書き直したか見てみましょう:[^3]
+
+[^3]: Githubを見てみましたが3月2日時点ではまだ書き直したものはコミットされていないようです
+
+```ex
+defp parse_args(argv) do
+ parse =
+ with switches = [ help: :boolean, version: :boolean ],
+ aliases = [ h: :help, v: :version ],
+ do:
+ OptionParser.parse(argv, switches: switches, aliases: aliases)
+
+ case parse do
+ { [ {switch, true } ], _, _ } -> switch
+ { _, [ filename ], _ } -> open_file(filename)
+ { _, [ ], _ } -> :stdio
+ _ -> :help
+ end
+end
+```
+
+今度は`switches`と`aliases`のスコープは明確です。`case`の中で使われていないということもはっきりわかります。
+
+それでもまだ`parse`という変数があります。これはネストした`with`で何とかすることもできますが関数が読みにくくなりそうです。代わりにこれを2つのヘルパー関数を使うようにリファクタリングするほうがよさげです:
+
+```ex
+defp parse_args(argv) do
+ argv
+ |> parse_into_options
+ |> options_to_values
+end
+
+defp parse_into_options(argv) do
+ with switches = [ help: :boolean, version: :boolean ],
+ aliases = [ h: :help, v: :version ],
+ do:
+ OptionParser.parse(argv, switches: switches, aliases: aliases)
+end
+
+defp options_to_values(options) do
+ case options do
+ { [ {switch, true } ], _, _ } -> switch
+ { _, [ filename ], _ } -> open_file(filename)
+ { _, [ ], _ } -> :stdio
+ _ -> :help
+ end
+end
+```
+
+ぐっとよくなりました。読みやすいし、テストしやすいし、そして変更しやすい。
+
+ところで、ここに来てどうして私が`with`式を`parse_into_options`関数に残したのか訝しがられるかもしれません。いい質問ですね。それについては`with`の2つめの使い方について述べた後にお答えしましょう。
+
+## withとパターンマッチング
+
+前の節ではコマンドライン引数のパースをやりました。ではそれを(ちょっとだけ)変更して関数間で受け渡されるオプションの確認について見てみましょう。
+
+私はGitLab、つまりGitHubのオープンソースのそっくりさんのElixirインタフェースを書いている真っ最中です。それは単純ですが少なくとも数十から数百個の幅広いJSON REST API[^4]呼び出しです。それらのAPIのほとんどが名前付きパラメータのセットとともに呼び出されます。パラメータのいくつかは必須ですしオプショナルなパラメータもあります。例えばユーザーを生成するためのAPIは4つの必須パラメータ(email, name, password, username)に加えてオプショナルなパラメータの山(bio, Skype及びTwitterのハンドル、などなど)を持ちます。
+
+[^4]: こんな感じです(http://doc.gitlab.com/ce/api/README.html)
+
+私のインタフェースコードで渡されるパラメータがGitLab APIの仕様に合うかどうか確認したかったので簡単なオプションチェックライブラリを書きました。どのように使うかのヒントをいくつか示します:
+
+```ex
+@create_options_spec %{
+ required: MapSet.new([ :email, :name, :password, :username ]),
+ optional: MapSet.new([ :admin, :bio, :can_create_group, :confirm,
+ :extern_uid, :linkedin, :projects_limit,
+ :provider, :skype, :twitter, :website_url ])
+}
+
+def create_user(options) do
+ { :ok, full_options } = Options.check(options, @create_options_spec)
+ API.post("users", full_options)
+end
+```
+
+オプションの仕様は2つのキー`:required`と`:optional`を持つマップです。これを`Options.chek`に渡すとAPIに渡すオプションが全ての必須の値が含まれていてオプショナルな値がoptionalセットに入っているかを確認します。
+
+以下にチェッカーの最初の実装を示します:
+
+```ex
+def check(given, spec) when is_list(given) do
+ with keys = given |> Dict.keys |> MapSet.new,
+ do:
+ if opts_required(keys, spec) == :ok && opts_optional(keys, spec) == :ok do
+ { :ok, given }
+ else
+ :error
+ end
+end
+```
+
+与えられたオプションからキーを抽出し、全ての必須の値が含まれているか及びオプショナル値のリストに何かキーがあるかを確認する2つのヘルパーメソッドを呼び出します。どちらもチェックが通れば`:ok`を、ダメなら`{:error, msg}`を返します。
+
+このコードで動くんですがコンパクトさを保つためにエラーメッセージを犠牲にしています。どちらかのチェック関数が`:ok`を返せなければ処理を中断して`:error`を返します。
+
+ここで`with`が光ります。`with`と`do`の間の式のリストの中で新しい**条件パターンマッチ演算子**`<-`を使うことができます[^5]。
+
+[^5]: 公式ドキュメントの[with](http://elixir-lang.org/docs/stable/elixir/Kernel.SpecialForms.html#with/1)参照。with式専用です。
+
+```ex
+def check(given, spec) when is_list(given) do
+ with keys = given |> Dict.keys |> MapSet.new,
+ :ok <- opts_required(keys, spec),
+ :ok <- opts_optional(keys, spec),
+ do:
+ { :ok, given }
+end
+```
+
+`<-`演算子は`=`のようにパターンマッチを行います。もしマッチングが成功するとこれら2つの効果は全く同じです-必要であれば左辺の変数が値とバインドされ処理が続行します。
+
+`=`と`<-`はマッチングが失敗した場合に異なる挙動をします。`=`演算子は例外を投げます。しかし`<-`はちょっとずるい動きをします: `with`式の実行を終了しますが例外を投げません。代わりに`with`式はマッチできなかった値を返します。
+
+このオプションチェッカーではもし必須及びオプショナルのチェックが両方共`:ok`を返した場合、処理はずーっと流れていって`with`は`{:ok, given}`のタプルを返します。
+
+しかしどちらかが失敗したら、それは`{:error, msg}`を返します。そうなると`<-`演算子はマッチしないので処理は早いうちに`with`節を抜けます。その値はエラーのタプルで、この関数の戻り値になります。
+
+### こじつけ気味の要点
+
+新しく追加された`with`式はあなたに2つの素晴らしい機能をきちんと箱詰めして提供します:レキシカルスコープと失敗時の早期離脱です。
+
+これでコードがよくなりますね。
+
+使いましょう。
+
+どんどん。
+
+## 私と Joséの違うところ
+
+[Elixir Fountain](https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/245652921&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false&visual=true)でJohnny Winnが数週間前に José[^6] にインタビューしています。
+
+[^6]: José Valim, Elixirの作者。
+
+話はElixir 1.2の新機能に及び、Joséは`with`について述べていました。結局、なんというか彼は控えめで、あんまり使う必要はないかもしれない、でも使ったならばすごく有益なんだけど、としか語りませんでした。Elixirのソースコードではたしか数回しか使われていないとも。
+
+私が思うに`with`はそんなもんじゃないです。確かに必要に迫られることはめったにないかもしれませんが、使うことで何度も利益を得られるでしょう。私は関数レベルのローカル変数を作るときには常に`with`を使うという実験をしています。
+
+そしてわかったのは、この訓練によって私はよりシンプルで単機能の関数を書くようになったということです。もしある関数が`with`によって簡単にローカルスコープに何かを閉じ込めることができなかったら、それを2つに分割できないかと考える時間を取るようになりました。そのように分割すると常に私のコードは改良されるのです。
+
+それが`with`を先ほどの`parse_into_options`関数に残しておいた理由です[^7]。
+
+[^7]: ここは分割しないで`with`式を使うほうがうまいやり方ということでしょう。
+
+```ex
+defp parse_into_options(argv) do
+ with switches = [ help: :boolean, version: :boolean ],
+ aliases = [ h: :help, v: :version ],
+ do:
+ OptionParser.parse(argv, switches: switches, aliases: aliases)
+end
+```
+
+やれ、という訳ではないですが、私は関数の2つの部分の輪郭を描くやり方が好きです。そうすると何がおまけの部分で何が本質かクリアになるからです。私の頭の中ではそういうコードは単純で直線的なコードには欠けているストーリー性を持った構造があるのです。
+
+まあ、これは根拠のない意見に過ぎません。ただあなたもこの技術的な難敵で数週間実験してどんな風に役立つか知りたくなったのではありませんか?
+
+
+-----