パイプ演算子とは?

一部の関数型言語にはパイプ演算子(パイプライン演算子、pipe operator)というのがある。[^1]

[^1]: Elixir, R, F#, ECMAScript(stage1草案), LiveScript, Elm等。Haskellには無い。

x |> f |> g |> h

h(g(f(x)))

と解釈されるものだ。言語によっては、逆パイプ演算子(back pipe operator)もある。

h <| g <| f <| x

これは最初のパイプ演算子の例と同じ意味だ。関数型プログラミングが流行中の昨今、Rubyにもこのパイプ演算子はあったほうがいいであろうか?その場合は、どのような動作で、どのようなシンタックスであるべきだろうか?この記事は適当にそれを考察してみる。

Ruby版パイプ演算子仕様案

案1 パイプにはメソッド名の識別子を渡し、関数呼び出しを行う。

x |> f |> g |> h

h(g(f(x)))

と解釈されるという案だ。しかしこれにはいささかというかなり問題がある。文法的には次のようになるからだ。

式 |> メソッド名 |> メソッド名 |> メソッド名

そう、パイプの続きが式にならない。メソッド名の識別子がそのまま現れえる文法はdefによりメソッド定義、aliasによる別名定義、undefによる定義削除のみだ。また、関数呼び出し可能なメソッド名以外は使えない。Rubyの文法としてx.fという書き方は式として評価されないとそのメソッドの定義自体に行き着かないからだ。

よってこの案の採用は難しい。

案2 パイプにはメソッド名のシンボルを渡し、関数呼び出しを行う。

x |> :f |> :g |> :h

h(g(f(x)))

と解釈されるという案だ。シンボルにする事で、最初の案の識別子という問題をクリアしている。

問題は関数呼び出しに制限されると言うことだ。Rubyの関数呼び出しはself.fというメソッド呼び出しに過ぎないし、一部を除き、滅多に使わない。使う機会がほとんど無く、わざわざ文法として追加する価値がない。

よってこの案の採用は難しい。

案3 パイプにはメソッド名のシンボルを渡し、左辺をレシーバーにしてメソッド呼び出しを行う。

x |> :f |> :g |> :h

x.f.g.h

と解釈されるという案だ。ぶっちゃけて言えば、初めからそう書けば良い。

よってこの案の採用は難しい。

案4 パイプにはProc(またはProcに変換可能な)オブジェクトを渡し、左辺に対して呼び出す。

x |> f |> g |> h

h.to_proc.call(g.to_proc.call(f.to_proc.call(x)))

と解釈されるという案だ。to_procをするのはシンボルを渡すと、案3と同じ動作にするためだ。

これまでの案に比べると最も有力な使い方のようにも思える。2.6から追加された関数合成も使えば強力になるだろう。しかし、実は2.6では次のようにも書けてしまう。

x.then(&f).then(&g).then(&h)

thenは2.6からだ。2.5ではyield_selfを使う必要がある。2.4以下には存在しない。といっても、2.6ですでにRubyらしい書き方が出来てしまう。わざわざ文法を追加してまで、便利になるとは言い難い。

よってこの案の採用は難しい。

パイプ演算子は本当に必要なのか?

Rubyは関数が無い文化だ。そこに他の関数型言語の文法を無理矢理くっつけても、Rubyにおける関数型プログラミングがしやすくなるとは思えない。パイプ演算子は便利だからと単純に追加すれば良いという考えはあっていないのでは無いのかと思う。

オブジェクト指向でありながら、関数型プログラミングがどうしたらしやすくなるのか、まだどの言語も成功していない問題の一つだと思う。だからこそ、RubyはRubyで独自の道を探していけばいいと思う。

関連記事

この記事へのコメント

まだコメントはありません
+1
18
@raccyの詩集
このエントリーをはてなブックマークに追加