47
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

言語バトル時には秘匿しておきたい Elixir の急所

Last updated at Posted at 2019-12-01

この記事は、fukuoka.ex Elixir/Phoenix Advent Calendar 2019 の2日目です。昨日は @Yoosuke さんのGraphCMSからAbsintheをりようして作るElixirで体験的にGraphQLSeverを作る「ポエム」でした。

これを読んでいるみなさんは Elixir が大好きで読みに来ているのだと思います。そんなところで大変恐縮ではございますが、今日は Elixir の「なんで?」を集めてみました。これさえ乗り切れば Elixir の素晴らしい性質しか見えなくなります。心してクリアしましょう。

atom について

Elixir じゃ atom と呼びますね。これ意外と説明が難しい。Careless Lisper にとっては「シンボルと同じだよ」と言いたくなるのですが、それでは説明になりません。聞かれたら「とにかく使って慣れてみて」という言い方しかしにくいのです。ところがこれあまりキレイな空間にあるような気がしないんです。使ってみてあちこちでハマるんじゃないかと少なからず不安になります。

フローティングコロン

はい、では、みなさん、前コロンと後コロン、どこでどう使い分けるかちゃんと文法を理解していますか。混乱しますよね。例えば以下を iex で入れてごらんなさい。

l = [{:a, 1}, {:b, 2}]
r = [a: 1, b: 2]
l === r

l にバインディングした1行目の返り値で分かってしまいますが、これ3行目の結果は true ですよ。1行目と2行目がなんで同じやねん。それも === レベルの同じさです。でね、中身を取り出すには

iex(4)> l[:a]
1
iex(5)> r[:b]
2

ですわ。そこでなぜコロンは前なの、みんなここは前って分かってるの?!
そしてリストなもんだからと、中身を hd/1tl/1 で取り出すとこうですわ。

iex(12)> l
[a: 1, b: 2]
iex(13)> l |> tl
[b: 2]
iex(14)> l |> tl |> hd
{:b, 2}
iex(15)> [l |> tl |> hd]   
[b: 2]

もうね、明日の私は覚えてないよ、これ。

大文字で始まる Atom

まあえいですわ。そういうこともありましょう、前コロン、後コロン。でもね、コロンなしでも atom になるって変な仕掛けはなんで入ったんですか。モジュール名にはコロン付けたくなかったんですか、え?そうなんでしょう。

iex(16)> Afo        
Afo
iex(17)> is_atom(Afo)
true
iex(18)> Elixir.Afo 
Afo
iex(19)> Elixir.Afo === Afo
true

なんスカ、これ。アッパーキャメルケースは特別扱いで、前コロンなしでも atom なんですか。スネークケースへの不当な差別じゃないですか。それも勝手に Elixir とか補完されてるじゃないですか。だれがそんなことをお願いしたんですか。

:math で Erlang のライブラリ

まあね、それでもね、いいんですよ。アッパーキャメルケースを特別扱いしたって。別にそう混乱するわけでもないから。Elixir. って前方補完されてるとか知らなくたって困りはしないんですよ。大文字で始まるなら特別扱い、わかりました。

でもね、小文字だけの世界なら美しいかと思いきや、前コロンで小文字で始めてるのに特別扱いされることがあるじゃないですか。

iex(25)> :math
:math
iex(26)> is_atom(:math)
true
iex(27)> :math.pow(2,3)
8.0

なんで勝手に :math に特別な意味付けてんですか。何も知らないアルケミストが :math が Erlang のモジュール呼び出しだってどうして分かり得るんですか? Erlang のモジュール名と衝突すると気持ち悪いからと敢えて避けようとするなら Erlang の標準モジュールを全部覚えろってんですか。知っている人たちしか使えないんですか。ギルドですか。そんな大して Erlang の関数をバシバシ使ったりしないんだから Erlang.math とか Erl.math とか書くことにすれば良いじゃないですか。

演算子

いや、ちょっと興奮してしまいました。atom は好きですよ。いろんなプログラミング上の事情があったり過去の経緯があるのも分かります。シンボルに変わる文法上の要素がないからそこにいろいろ仕掛けが入るのはわからんではないです。はい、じゃ次、演算子に行ってみましょう。

除算

ここで文句言ってもはじまらないのは分かってて書きます。Elixir が悪いんじゃない、Erlang が悪いんです。でも一言書いておきたい。

整数同士の / の結果がなんで float なんじゃあ

iex(1)> 1+2
3
iex(2)> 1-2
-1
iex(3)> 1*2
2
iex(4)> 1/2
0.5
iex(5)> div(1,2)
0

そういうものだと言われればそうしますよ。でもね Elixir じゃ言語仕様が大きすぎるからってんで、例えば入門用とか組込み用に micro Elixir って文法を考えるとするじゃないですか。「bignum1 は扱うの大変だからとりあえず整数は符号付き4バイト限定ね」とか仕様を決めるでしょ。CPUが非力とか実装が面倒とかでね。そのときの文法の説明はこういう風になるんですよ。気持ち悪くないですか。
- 加算:1 + 2
- 減算:1 - 2
- 乗算:1 * 2
- 除算:div(1, 2)

いいんです。Elixir は悪くないんです。元凶は Erlang ですからね。Erlang だとこうなります。

1> 1+2.
3
2> 1-2.
-1
3> 1*2.
2
4> 1/2.
0.5

黎明期には Elixir では意味を変えてみたらって議論もあったかもしれないですね。しらんけど。

剰余

まあね、被除数・除数が整数で / の結果が浮動小数でもいいんです。でもね、剰余の演算子がないってどういうことですか。

iex(1)> 1 % 2
** (SyntaxError) iex:1: syntax error before: '%'
iex(1)> rem(1,2)
1

なんですかこれは。そんなに整数の除算が嫌いですか。/ で商を浮動小数にしたから余りなんて概念は不要になりましたか。
そんなに整数同士の商を整数にしたくなかったら有理数型を用意して有理数が返るようにすれば良いじゃないですか。そこは日和って突如浮動小数ですか。IEEE754 がそんなに好きですか。インテル 8086 で浮動小数の計算をプログラムでやってた時代に 8087 浮動小数コプロセッサが出たのがそんなに嬉しかったですか。

<>

あ、8086 とか書くとエイジがばれちゃいますね。はい、そうです、私はそんな生い立ちです。8086 どころか最初に触ったのは 8080A でございます2。それもアセンブリ言語でもなくてマシン語ですよ、マシン語、機械語。アセンブリ言語でプログラム書いたって鉛筆と紙で自分で手アセンブルですからね。

マシン語・アセンブリ言語と来て、最初に触った高級言語は BASIC です。高級風でしょ。そうですよ高級風なんです。何が高級風って、例えば数値の比較したいときには IF X < Y THEN とか書くだけでインタプリタかコンパイラがよろしく処理するんです。

        MOV a, [x] # X の値をアキュムレータへ
        MOV b, [y] # Y の値を b レジスタへ
        SUB b      # a - b をキャリーフラグを無視して実行
        JC  THEN # キャリーフラグが立ったら THEN の実行をすべくジャンプ
ELSE:            # キャリーフラグが立たなかったら ELSE の実行をここからやる

こんな風に引き算を書いたりしなくても済むんです。こんな低級なことをしなくても良いのです。なにが低級って、こんなの書いているとよくバグるわけです。条件を逆にしちゃったり等しいときに逆の方に飛んでいったりしてね。比較演算子を使えるというのはありがたいことなんです。

BASIC の場合、比較演算子には他の後発の言語同様 $<, =, >$ というのがあります。この他に $\le, \ge$ もあります。これはダイレクトにASCII文字では書けないですから <= とか >= とかにします。まあよくある話ですね。これは「< か =」の意味と「> か =」ということです。2つの比較演算を or で結んでいます。

で BASIC で一つ美しいのは「等しくない」を <> と書くことかと思ってます。上の例と整合性が取れてます。なぜなら「> か <」という意味になりますから。整数や実数の空間ならこれを満たすのは等しくない場合だけです。うまいこと考えますよね。

Elixir でもやってみましょう。

iex(1)> a = "aaa"
"aaa"
iex(2)> a <> a
"aaaaaa"
iex(3)> "xxx" <> "yyy"
"xxxyyy"

なんですかこれは。"aaa" 同士が等しくないか問うているのですから最初の <> は false でしょ。"xxx""yyy" の比較は当然 true でしょ。だれが <> で文字列を連結してほしいなんて言ったんですか。そんなに自然ですかこの演算子は。

エレベータでドアが閉まりかけてるところで誰か来て <> ボタンと >< が並んでいるときに、あなたはどっちを押せばドアが開くかすぐに分かるんですか? 今どきのエレベータは <> ボタンだけデカくして < > とかにして、その上に緑色で光らせたりしてますよね。それぐらいわかりにくいんですよ <> は。

そこを数学的に美しい統一性をもたせて <> は「等しくない」という記号にしようよ、と BASIC は提案していたのです。

なのになぜ Elixir では文字列の連結なんですか。C++ みたいに ++ だってよかったじゃないですか。List の連結に使った? バイナリの連結にも使った? あ、バイナリと文字列は別だから別の演算子が必要。バイナリと文字列が別の型で便利でしょって、はぁ、そうですか。でなんで <> なのですか。連結の概念が直感的に反映されるような演算子は考えつかなかったんですか。

|>

いや、もう疲れました。慣れればよいのですよね。Elixir ならではの素敵なプログラミングワールドがあるのですから、こんな些細なことで目くじら立てるものではありません。さあ、Elixir での気持ち良いプログラミングに戻りましょう。

Elixir の何が気持ち良いかと言ってパイプですよね。処理して処理してってつないでいくのは可読性が高くて本当に気持ちが良いです。ある種の postfix な演算記法ですね。他でも postfix な記法って使いやすいですよね。

  • Postscript, PDF
    • 中身を直接見たことない? 平テキストですから普通にテキストエディタで読めますよ。座標 (100, 100) に行くのは 100 100 moveto とか書いてあります
  • HP の関数電卓での逆ポーランド記法
    • 昔のエンジニアには受けが良かったですね。1 + 2 を計算するときは 1 enter 2 enter + ってキーを押すんです。モロにスタックマシンでした。
  • 日本語の「目的語」<>「動詞」
    • 1 + 2 を「1と2を足す」って言いますよね。これぞ postfix 記法。
  • 携帯電話の電話番号と発信ボタン

高知は後30年で南海トラフ地震が発生する確率が70%とか言われていますから、子どもたちにイザというときように公衆電話の使い方を教えてみたりもしたんですよ。なかなか難しいです。最初にオフフックしてから電話番号押しますから、その感覚に慣れないといけないのです。これは「関数名(引数列)」の構造ですから Lisp 同様にある種の prefix 記法です。携帯電話は番号押してから最後に「発信」を押すので postfix 記法で、この差が慣れない一つの原因かなと思ってます。

ちなみに携帯電話では発信ボタン押すまでは網側は何も負荷がありませんが、固定電話でオフフックしたらその瞬間に電話交換局の交換機がいろんな準備した上で次のアクション待ちに入るので、それだけで負荷がかかります。一説にはオフフックしただけで数万行のプログラムが実行されるという話も聞いたことがあります。Erlang が産まれた背景はズバリ交換機ということなので、みなさん固定電話でオフフックするときにはちょっと交換機にも思いを馳せてみてください。

さてその Erlang をベースにして Elixir ができたということで、Elixir に戻ります。

同じリスト使う関数型言語でも Elixir が LISP なんかより使いやすいのは、パターンマッチと postfix 記法での関数適用ができることがかなり大きいんじゃないかなと思ってます。それぐらい先にデータがあってそれの処理を後に書く記法が思考と整合性が取れるということかと。

ではちょっと Elixir でパイプ使ってみますか。Enum.at/1 の引数のとり方は0オリジンでしたかね。1オリジンでしたかね。こう疑問に思ったらちょいとすぐ試せるのが気持ちが良いです。

iex(1)> 0..9 | Enum.at(0)
** (CompileError) iex:1: misplaced operator |/2

The | operator is typically used between brackets as the cons operator:

    [head | tail]

where head is a single element and the tail is the remaining of a list.
It is also used to update maps and structs, via the %{map | key: value} notation,
and in typespecs, such as @type and @spec, to express the union of two types

えっと何が悪んでしたかね。こんなのでエラーしますかね。演算子の場所が悪いと。グチャグチャ出てきているけど何が悪いんでしょうね。こんなに簡単な行で怒られると困っちゃいますね。あ、またやってしまいました。パイプは | じゃなくて |> でしたね。

でもね、パイプって言ったら sh のパイプを想像しませんか。エラーメッセージには「| はよく角括弧と一緒に cons で使うから」って、そんなことはあんたに言われなくても知ってます。
でね、あなたね、どこの世界に 0..9Enum.at(0) を cons したい人 が居るんですか。エラーの傾向で言ったらそこは「パイプ演算子 |> を使いたかったんじゃないですか」とか言うのが親切ってもんじゃないですか。いやねエラーメッセージが親切まくりな Rust や Haskell と比較して Elixir にケチつけようなんてこれっぽっちも思ってませんよ。でももうちょっとユーザに優しいエラーメッセージの出し方したっていいんじゃないんですか。ひょっとして「ププ、またやってるよ、こいつ。覚えねぇな」とか思ってるんじゃないでしょね。

|> って左右非対称なのもちょっと気に入りません。世界のどこかでまだ ASR-33 テレタイプライタとか IBM タイプライタみたいなガチな端末を使って Elixir を書いているユーザがいるかもしれないじゃないですか(いねえよ)。ゴルフボールの > ばっかり使ってそこだけ傷んでいきそうです。ゴルフボールは交換できるとは言え、ランニングコストに響きます。すぐに交換できなくて、パイカでプログラムを書いてたのに途中からエリートになっちゃったりして、あ~あって世界の隅で嘆いていることでしょう。

この非対称性、どっかで見たことあります。そうです、量子物理やっていると > ばっかりよく使いますよね。もともとは $< \phi\ |\ \psi >$ とあるけど、右のケットしか使わなくて $|\ \psi >$ しか式に出てこないとかです。どうもこれも引っかかってるんですよね。反対側のブラ部分はどこに行っちゃったんでしょうか。ブラックホールに吸い込まれたんでしょうか。いつの日かホワイトホールから $<\phi\ |$ が急に吹き出てきたりするのでしょうか。もう吹き出しているのに私が気づいてないだけなんでしょうか。

関数

atom と演算子にここまでケチつけたので本丸の関数にも文句を言わざるを得ません。

なぜカッコを省略させるのか

Elixir の関数の問題はこれに尽きます。これはすでに拙著 はじめてな Elixir(25) ライフゲームを作ってみる「それは関数か変数か」 にも書いたので、御覧ください。カッコを省略したために曖昧性が出てプログラマの想定しない解釈をされてしまう現象です。これで作ったバグはホンマわからんぞ。

全部無名関数でもいいじゃん

これは腹が立つような話でもないのですが、関数は全部無名関数にして、名前つけたかったら . をつけるようにしても良かったかなと言う気もしてます。人に教えるときもそっちが楽なような気もしています。そしてこれ、関数名に必ずピリオドがつくようにすると、上の曖昧性の問題がおそらく回避できます。関数名と変数名とが必ず区別できるからです。
ただこれは名前空間の設計ポリシーもあるし、第1階の要素をどの範囲にして、かつ高階な扱いをどこまで許すかというところにも踏み込みそうなので「こうしろ!」と言い切るまでの深い考察ができているわけではありません。そこ突っ込んでいくと純粋関数な言語への議論になってしまって Elixir な気持ちよさからむしろ遠ざかる気もするのですよね。そして、そこの議論に入ると結論がどうあっても誰かしら原理主義の方からクレームが付きそうですしね。
でもね、無名関数に名前つけたときと最初から名前つけた関数とで . 一文字しか変わらないというのがプログラム書いてていつもちょっと引っかかるんですよ。

おわりに

これで完全に文句、終了しました。もう申し上げることはございません。あとは Elixir で思う存分ハックしましょう。最後まで読んでくれた方、ありがとうございました。Elixir でプレゼントを差し上げます。

[77, 101, 114, 114, 121, 32, 0x58, 0o155, 0b01100001, 0x73, 33]

を iex で評価してみてください。以下、これに対する私からの最後のコメントです3

[21360, 23383, 21487, 33021, 12384, 12363, 12425, 12387, 12390, 25972, 25968,
12398, 12522, 12473, 12488, 12434, 21213, 25163, 12395, 25991, 23383, 21015,
12395, 12377, 12427, 12435, 12376, 12419, 12397, 12360, 12424] |> IO.puts

明日のfukuoka.ex Elixir/Phoenix Advent Calendar 2019 3日目の記事は, @torifukukaiou さんのElixirを書いていると将棋が強くなります(新しいことをはじめよう)です。こちらもお楽しみに!

参考文献

Elixir Official Document


  1. 任意長の整数のこと。これは Lisp 風の呼び方。Elixir でなんと呼ぶのかわからん。 

  2. 正確にはNECのi8080AセカンドソースのμPD8080AFです。TK-80EのCPU。 

  3. 作り方は @torifukukaiou さんの 12月3日なので、一二三、123ダーなElixirのこと などを参考にしてください。 

47
17
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
47
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?