エルビスよ、aかbかcかdから肯定値を取り出せ
新しいPHPとかGroovyとか次のJavaとか、三項演算子の省略でElvis演算子(?:)なるものがあるそうで、どのぐらいおいしいのかを考えました。
エルビス演算子へのニーズって、三項演算子(cond ? a : b)はやっぱり評価されるべきものだよね、ってことを意味してるんじゃないのかな?
三項演算子の是非について、ちょっと前提を。
http://blogs.wankuma.com/rti/archive/2008/06/25/145486.aspx
http://blogs.wankuma.com/ma2/archive/2008/06/26/145583.aspx
http://d.hatena.ne.jp/NyaRuRu/20080626/p1
例えば、先日聞いた話ではコーディング規約に三項演算子すら禁止している会社があるらしい。
理由は、多くの社員が、そんなものを使わないから。
こんな規則なんて、「新しいことを学習せずに読めるコード」なんていうノンプロSEの勝手な幻想じゃーねーの? 知識最小原則ならぬ、学力最低原則(w バカのくせに社員のスキルアップを妨害するんじゃねーよ。
…と、同調してネットで笑い飛ばすのは簡単だけど、本当にそうなのかを考えたい。
課題
aかbかcかdの順に、とにかく肯定的な値(言語によるが、少なくとも非NULL値)のものをどれか取り出す関数someofを作れ。
さっそくエルビスでやってみる。言語はGroovyで。
def someof(a, b, c, d) { a ?: b ?: c ?: d; }
いいね。というか、これならむしろ、関数を定義するだけ無駄で、関数の中身そのまま展開して書いたほうがわかりやすいかも。
これに対して、三項演算子がない言語だと仮定して、if文でやってみる。言語はJavascript。
function someof(a, b, c, d) { if(a) { return a; } else if(b) { return b; } else if(c) { return b; } else { return d; } }
冗長。コード圧縮ツールの効率悪そうだなぁ。しかも、単一式じゃないから、呼び出し元にインライン展開できないじゃん。ifが式なOCamlとかなら話は別だけど、多くの言語で、ifは文だよね。
いっぽうで、三項演算子だけで書くとどうなるか?
function someof(a, b, c, d) { return a ? a : b ? b : c ? c : d; }
function someof(a, b, c, d) { return a ? a : b ? b : c ? c : d; }
コード圧縮効率としては理想的。もしC++だったら間違いなくinlineつける。ていうか、型あいまいなままのほうがいいから、#defineかな。古いCからでも使えるし。
ただ、やっぱりここで、演算子の結合規則上これでいいことが理解できないとだめで、自信がない人はやたら括弧をつけたがり、結果、こんどは括弧の対応に悩まされることになる。しかもこれ、PHPにもっていくと意図に反した動きになる。PHPの三項演算子は、他の中括弧族とは異なる結合優先度だから。
http://d.hatena.ne.jp/pasela/20080524/ternary
(たぶんパーサのバグで、でも、このバグを取ると、こんどは既存のコードが動かなくなるから、できないんだろうな)
実際の有用性はどうあれ、たしかに、(本物の)プログラマー以外に、この「徹底して"式"であり続けてくれる非線形な演算」の価値は、認知されていなさそうです。
あんなに多くの(平均的)プログラマーを抱えるPHPコミュニティですら、この有様なんだもん。どんなクズがいるかわからないこの業界全体において、正しく評価されることはないでしょうね。で、まあ、たぶん本当にクズなところは、容易に「ビジネスロジックで正規表現禁止」「あんな暗号みたいなものは嫌だ、フローチャートで表せないじゃないか」とか平気で言うんだろうな。
(あ…ってことは、PHPの開発チームは本物のプログラマーじゃないという意味になるか、彼らはプログラマーだけど、PHPを言語とみなして開発していないという意味になるか、二択だね)
目の前の学習コストの支払いを避け、結果、無自覚に損していても(式になれるのに文で甘んじていても)、その事実にすらおそらく気づけない。式であるありがたみは、三項演算子を使うのが自覚への近道だから。で、逐次コマンド実行という発想から、式と文と関数という発想(PascalからCへの発展)へのシフトが起こらない。組織的に理解されないから、さらにその中にいる個人の認知が遅れる。もし「三項演算子禁止令」が出たら、この泥沼を思い出そうっと。
応用
さらに進んで、可変長引数に発展させることを考えます。引数が4つ固定じゃ使い道が限られるから。
function someof() { if(arguments.length == 0) { return undefined; } for(var i = 0; i < arguments.length - 1; i++) { if(arguments[i]) { return arguments[i]; } } return arguments[arguments.length - 1]; }
ややこしくなってきたので、Pythonでやります。
def someof(*args): if args == (): return None for a in args[:-1]: if a: return a return args[-1]
面白い実装としては、こんなのも。
def someof(*args): if args == (): return None else: return args[0] or someof(*args[1:]) # Lisp では car と cdr ね
Pythonの三項演算子、-if-else- で書くと、こんなことに。
def someof(*args): return (args[0] or someof(*args[1:])) if args != () else None # または2.4以下なら def someof(*args): return args!=() and (args[0] or someof(*args[1:])) or None
Pythonだとランタイムの都合で非効率的だけど、末尾再帰最適化が期待できる言語エンジンで動く場合を考えると、むしろこれが大正解。
結論
と、これだけやってみて、someof関数は意外と高度な話になってくることがわかった。もし仕事用にAPIを設計しているのなら、どの段階でやめておくかで、見積もりが大きく変わる要因だ。
ところで、そもそもこんなsomeof関数を作る必要がないぐらい、もとの言語機能が充実してたら、誰も、どれぐらい高度なsomeof関数を書くか、あるいは三項演算子を使うか、なんて、変なところで迷うことがなくなり、見積もり精度が上がることでしょう。
関数型言語の学者先生にはピンと来ないかもしれないけど、これは、言語理論じゃなくて生産性の問題。生産性の安定は工学のテーマ。なるほど、次のJavaがエルビスを選んだ動機に納得いく。
つまり、
text = someof(a, b, c, "nothing").toString();
なんて回りくどいことはやめて、
text = (a ?: b ?: c ?: "nothing").toString();
でいいじゃん。逆に、言語機能にあるものを使えばいいから、むしろ、someofみたいな関数自作は禁止、ってこと。
あ、そうそう、ちなみにPythonは昔から、プレスリーのスマイリーみたいな変な記号なんか使わなくても、
text = str(a or b or c or "nothing")
でOKでした。OR演算の結果が、論理値に化けず、元の値を保持するので。
テンプレートエンジンだと、ZopeとかPHPTALとかのTAL系の式がそれ。とってもPythonic。
<p tal:content="a | b | c | default">nothing</p>
a か b か c かデフォルトの "nothing" か、どれか
あれ?
そう考えると、PHPはかつてあんなに三項演算子をいい加減に扱っておきながら、こんどは急にエルビス演算子を導入、なんてことやって、言語基盤は大丈夫なんだろうか?
http://d.hatena.ne.jp/pasela/20080524/ternary
http://d.hatena.ne.jp/uskz/20080903/p6
http://d.hatena.ne.jp/tanakahisateru/20090827/1251379409
http://d.hatena.ne.jp/tanakahisateru/20090828/1251440513
三項演算子と等価書き換えできない演算子になってなきゃいいけど。あれ?てか、そうなると、それはそれで評価順序が直感に反するような…。だったら、三項演算子のほうを変更するのかな?それとも、矛盾しててもいいのかな。