2012-05-26
■[elixir][erlang]Elixir v0.5.0 releasedの超意訳
はじめのお断り
- これはPlataformatec社のコンテンツを勝手に翻訳したものです。
- 追記: twitterで翻訳した旨伝えたところ、怒られなかったです
- TOEICの成績がヤバすぎる私の超意訳なので、突っ込み歓迎。
- 文中の「私」は作者のJosé Valimを指します。
では行くぞ!
Elixir v0.5.0 relesed!
ついにElixir v0.5.0をリリースした! これは言語が書き直されてから最初のリリースをマークする。このブログのポストではこの間に成し遂げたものと次のステップを検討する!
これらを気にしないのなら、Getting Started guideへまっすぐ行く事もできる。そうでなければ読み続けてほしい!
Looking back
私は2011年の始めにElixirの仕事を始めた。その年の4月ごろ、自身のプロジェクトで使い始めるのに充分に安定だったv0.3.0をリリースした。しかし、二つのプロジェクトで使用した後、すぐに初期にされた設計上の決定に満足でなかった。
そのときElixirはかなりErlangから決別しようとしていて、それがすぐに悪い設計上の決定をもたらした。つまり、どんなErlangモジュールでも使用するために、最初にElixirでラッパを提供しなければならないというものだった。
あたらしいErlangリリースのすべてのモジュールと関数はまずElixirでラップされなければならず、それはErlangへのキャッチアップを常に行わなければならない事を意味した。
そのElixirバージョンで充分生産的であると感じなかったのち、新旧、新興の言語から学ぶためにElixirから休むことにした。チャレンジは、言語としてErlangを再発明しない(しかし、Erlangとの互換性を100%保持し、期待する生産性と柔軟性を提供する)ことだった。
San Franciscoでの短い滞在の間に、Yehuda Katz氏の助けを借りたElixirの現在のバージョンの基盤に追いついたのは2011年10月頃だった。2011年の年末に新しいElixirのバージョンの開発をスタートさせ、新年になってもしっかりと続いた。
その年の2月頃、充分に言語の進化の方向性(その点に関して初期に作成したベンチマーク)を充分に確信して、私の会社(Plataformatec)にEixirを投げ、会社はElixirを受け入れスポンサーになった。彼らの助けでElixirはより早く開発され、そして、それは次に見ようとしているものだ。
Where we are
我々が決めたゴールの一つは、次の公式リリースの前に良いウェブサイトとドキュメンテーションをすることになっていた。Plataformatecチームの助けを借りて、Elixirの為にロゴを作成して、このウェブサイトを運営している。
同時に、我々はpygmentsサポート、ドキュメント生成ツールと多くに取り組んだ。すぐに、GithubはElixir構文ハイライトが有効になり、APIドキュメントもオンラインになった。
同時に、人々が#elixir-lang irc.freenode.net チャネルの周りに集まり始め、Elixirで遊び始め、彼らのプロジェクトやチュートリアルを始めた。
最初のリリースが2012年4月まで予定されていたが、そのような初期の開発者からのフィードバックは若干のデザインと構文決定を見直す事を強制した。それは今日の言語を形作る上でとても重要であった。
ついにv0.5.0では、構文と基本的な標準ライブラリの安定性がコミットされている。リリースの最後の何日か前において、ドキュメンテーションの合理化と、Mac, Linux, WindowsマシンでのElixirの動作を確実にした!
Looking forward
まだまだ多くの、多くのことがある! 来月には、我々のコミュニティ、会談と他のドキュメント素材の成長に取り組み続ける。 この領域を主導しているAlexi Sholikには巨大に感謝している。
我々は、Erlangシステムの構築することについてのより良いインテグレーションとドキュメンテーションにも取り組んでいる。Erlangは分散アプリケーションを構築するための多くのツールを提供するOpen Telecom Platformとともに出荷されている。v0.5.0では既にすべてのこれらのツールを利用できるが、ビルドプロセスをよりシンプルにしたい。
並行して、今後十分安定したらコアに多分合併されるであろうドキュメンテーション生成ツールとビルドツールを改善する。
最後に、標準ライブラリを改善し続ける。Elixirのゴールは可能な限りErlangに依存する事ではあるが、Elixirのセマンティクスをより良く利用する小さな標準ライブラリを提供したい。今後数週間、IOとFile操作モジュールの改善に集中する。例えば、私の心に浮かんでいるrangeのような新しいデータタイプが現れるかもしれない。
詳しくはGetting started guideと我々のホームページをチェックしてほしい。
Welcome aboard and grab a cup of Elixir, because you are certainly going to enjoy the ride!
2012-05-25
■[elixir] elixir-0.5.0 リリース
Release 0.5.0 ¥o/ ? 6052352 ? elixir-lang/elixir ? GitHubによると、0.5.0がリリースされた。0.5.0-devからの主な違いは、
- new syntax:
- match: が->になり、cond 式が追加。
- else:、after:などが else, afterなどになり、より普通の文法に。
- @file属性の追加でモジュールファイル名の指定が可能に。
- defoverridableなどの追加
など。いろいろあって、くわしくは
What’s New in Elixir #4 - Elixir
などを参照の事。
2012-04-22
■[erlang][elixir] elixirはプログラマの万能薬になるか その4
今回はマクロプログラミングをelixir自身を例題にしていこうと思うが、マクロをプリプロセッサと勘違いしている人も多いので、まずは入門から。
マクロ入門
Elixirのマクロは、コンパイルプロセス中において、構文解析後のツリーを入力として、別のツリーを返すフックとして機能する。そして、そのフックはElixir自身を用いて記述することができる。この記述のしやすさと、ElixirのクロージャサポートによりLispなみの拡張性を持っている*1。
| フェーズ | 処理内容 | |
|---|---|---|
| (プリプロセッサ) | (文字列-->文字列) | |
| 1 | 字句解析 | 文字列-->トークン列 |
| 2 | 構文解析 | トークン列-->タプルによる構文ツリー |
| 3 | マクロ | 構文ツリー-->構文ツリー |
| 4 | コード生成 | 構文ツリー-->beamバイナリ生成 |
ということで、詳細は前回のエントリを参照していただきたい。
マクロプログラミングの実際
elixirではデリゲーションを行うためにdefdelegateが提供されている。それとよく似たマクロ delegate [{name, arity}|t], do: target を考えてみよう。名前以外はdefdelegateと同じ機能を持ち、あるモジュールの関数群を他のモジュールに委譲したい場合に使う事を目的としている。
Erlang標準のリストモジュールを元にMyListを作ろうとしているとしよう。reverseやmemberはもとと同じにしたいので、
defmodule MyList do delegate [reverse: 1, member: 2], to: Erlang.lists end
のように書いておきたい。そしてこれをこう展開したい。
defmodule MyList do def reverse(arg1) do apply Erlang.lists, reverse, [arg1] end def member(arg1, arg2) do apply Erlang.lists, member, [arg1, arg2] end end
MyMacroモジュールに実装するとして、コアはこんな感じとなるだろう。
defmodule MyMacro
defmacro delegate [{fname, arity}|t], to: func
args = makeargs(arity)
quote do
def unquote(fname).(unquote_splicing(args)) do
apply unquote(func), unquote(fname), [unquote_splicing(args)]
end
end
end
end
makeargs/1は指定された数だけの仮引数として使用できるアトムリストを返す関数であり、例えばこんな感じで実装できる。
def makeargs_1(arity) do
Erlang.lists.map(fn(x) ->
list_to_atom(List.flatten(Erlang.io_lib.format("arg~p", [x]))) end,
Erlang.lists.seq(1,arity))
end
このmakeargs_1を実行してみると、
iex> MyMacro.makeargs_1(2) [:"arg1",:"arg2"] iex>
いい感じで動いていそうだ。これをquoteされたリストにどう変換するか考える。まず、通常の関数の引数がどのようにquoteされているかを調べてみる。
iex> quote do
...> defmodule M do
...> def func(arg1, arg2, arg3) do
...> true
...> end
...> end
...> end
{:defmodule,0,[{:__ref__,0,[:M]},[{:do,{:def,0,[{:func,0,[{:"arg1",0,:quoted},{:"arg2",0,:quoted},{:"arg3",0,:quoted}]},[{:do,true}]]}}]]}
iex>
つまり、{引数アトム, 0, :quoted} というタプルのリストになっている。このことから、makeargs/1は、makeargs_1/1を使い、
def makeargs(arity) do
arglist = makeargs_1(arity)
Erlang.lists.map(fn(x) -> {x, 0, :quoted} end, arglist)
end
と書けそうだ。makeargs_1自体もmapを使っているため、fnを置き換えて、
def makeargs(arity) do
Erlang.lists.map(fn(x) ->
arg = list_to_atom(List.flatten(Erlang.io_lib.format("arg~p", [x])))
{arg, 0, :quoted}
end,
Erlang.lists.seq(1,arity))
となる。makeargs/1を実行してみると、うまく動いていることが分かる。
iex> MyMacro.makeargs(3)
[{:"arg1",0,:quoted},{:"arg2",0,:quoted},{:"arg3",0,:quoted}]
iex>
次にdelegate本体だが、上のコアだけの定義だと最初のfnameタプルのみが展開される形になるため、残りを再帰で実装する感じで書いてみる。
defmacro delegate [{fname, arity}|t], to: func do
args = makeargs(arity)
e = quote do
def unquote(fname).(unquote_splicing(args)) do
apply unquote(func), unquote(fname), [unquote_splicing(args)]
end
end
case t do
match: []
[e]
else:
[e | delegate(t, to: func)]
end
end
最初の一要素分をquoteしたあとは、残りを再帰的に呼び出して変換していく。unquote_splicing/1は、リストの中身だけをその場に展開することをのぞけばunquote/1と同じである事に注意すると、素直な実装だが、よく見てみると、{fname,arity}リストを一対一で quote do: def fname リストに変換する形がハッキリと見えてきくる。Erlang.lists.mapで作り直してみる。
defmacro delegate2(tuplelist, to: func) when is_list(tuplelist) do
Erlang.lists.map(fn({fname, arity}) ->
args = makeargs(arity)
quote do
def unquote(fname).(unquote_splicing(args)) do
apply unquote(func), unquote(fname), unquote(args)
end
end
end, tuplelist)
end
elixir標準のEnum.map/2を使うともっと簡単に書けて、
defmacro delegate3(tuplelist, to: func) when is_list(tuplelist) do
Enum.map tuplelist, fn({fname, arity}) ->
args = makeargs(arity)
quote do
def unquote(fname).(unquote_splicing(args)) do
apply unquote(func), unquote(fname), unquote(args)
end
end
end
end
となる。
このようにElixir自身がElixir自身で拡張していけるようになっている。この機能を使って、ExUnit(ユニットテストフレームワーク)やレコードやプロトコル機能、果てはif文すらもマクロとして実装されている。
Paul GrahamはOn LispでLispは拡張可能なプログラミング言語であると書いているが、Elixirもその能力を持っている事がわかっていただけるだろう。
- 作者: ポールグレアム,野田開,Paul Graham
- 出版社/メーカー: オーム社
- 発売日: 2007/03
- メディア: 単行本
- 購入: 10人 クリック: 137回
- この商品を含むブログ (120件) を見る
2012-04-08
■[elixir][emacs] elixir-modeをハックしてみた
elixir向けのemacs メジャーモードとしてはsecondplanet/elixir-mode ? GitHubがあるのだが、0.4.0-dev以降のelixirには対応していない。elixir側も結構キーワードが変わっているし、作者氏も更新するのをやめてしまったみたいだし、インデントコマンドが無限ループするなど微妙なのだ。
ということで、forkしてクイックハックしてみた。
k1complete/elixir-mode ? GitHub
変更内容
ハイライトのキーワード追加
defmacro, defmodule, defrecordなどを追加。
制御変数を追加
| elixir-basic-offset | 8 | 通常のインデントの単位 |
| elixir-key-label-offset | 2 | caseでのmatchラベルやelseラベルのインデント単位。0だと、インデント無し |
バグ修正
インデントルール改善
使い方
.emacs.d/elixir-mode/にelixir-mode.elをおいて、
(add-to-list 'load-path "~/.emacs.d/elixir-mode") (require 'elixir-mode) (setq elixir-basic-offset default-tab-width) ;; 適当に8とか4とか (setq elixir-key-label-offset 2) ;; match:やelse:を変更しないなら0でよい
を.emacsへ追加する。
今後
まだsecondplanet氏のコードを全部理解している訳でもないし、メジャーモード作るのもはじめてなのでw 勉強しないとw
2012-03-23
■[erlang][elixir] elixirはプログラマの万能薬になるか その3
前回はrubyなところを主に説明してきたので、いよいよ今回はLispな所であり、個人的に最もエキサイティングだと感じているメタプログラミングについて記述する。
メタプログラミング
プログラムを書くプログラムを書く事をメタプログラミングと呼ぶ。Cのプリプロセッサや、yacc等のコード生成系もメタプログラムの範囲に含める場合があるようだが、Lispが最も有名であり、徹底されている。LispはプログラムがLispのS式で表現されているため、Lispの全能力使ってメタプログラミングが行える。それ故に、Lispは他の言語とは次元の違う強力さを持っている。プログラム言語がデータ構造として表現できる事(homoiconic)と、構文解析とコード生成の間にマクロの層があることがこの強力さの源になっている。
一方、elixirはというと、ruby風味のシンタックスであるにも関わらず、Lisp並のメタプログラミング機能を手に入れている。
では、どうやっているのか。これから見てみよう。カギはquoteとdefmacroだ。
elixirにおける構文のhomoiconic表現
elixirはhomoiconic言語、つまり、任意のelixirプログラムはelixirのデータ構造を使用して表現できる。リストの長さを返すlength/1で試してみよう。quote do:を使う。
iex> length([1,2,3])
3
iex>
iex> quote do: length([1,2,3])
{:length,0,[[1,2,3]]}
この3要素のタプルがlength/1に対応する表現だ。一般的には
{ Tuple | Atom, Integer, List | Atom }
elixirは後で記述する5種類のリテラルをのぞくと、すべては関数である。タプル自身、演算子や代入、do -- endブロックでさえもだ。さあ let's quote do!
iex> quote do: { 1,2,3 }
{:"{}",0,[1,2,3]}
iex> quote do: 1 + 2
{:"+",0,[1,2]}
iex> x=1
1
iex> quote do: x
{:x,0,:quoted}
iex> quote do: x = 2
{:"=",0,[{:x,0,:quoted},2]}
iex> quote do
...> 1+2
...> 2*3
...> end
{:__block__,0,[{:"+",0,[1,2]},{:"*",0,[2,3]}]}
iex>
最後のブロックの例のように、タプル表現はいくらでも入れ子になってelixirの式を表現する。
そしてquoteしてもそれ自身を返すという意味でリテラルとなるのは、以下のとおり。
iex> quote do: :sum # アトム :sum iex> quote do: 1 # 数値 1 iex> quote do: 2.0 # 数値 2.00000000000000000000e+00 iex> quote do: [1,2] # リスト [1,2] iex> quote do: "bin" # バイナリ "bin"
defmacroとunquote
マクロはモジュール中でdefmacroで作成する。ありきたりだが、ifとよく似たunlessを作ってみよう。マクロは自身が定義されたモジュールの外からしか利用できないことに注意して、
iex> defmodule M do
...> defmacro unless(clause, options) do
...> quote do: if !unquote(clause), unquote(options)
...> end
...> end
{:unless,2}
iex> x = 1
1
iex> require M; M.unless x > 0, do: true
nil
iex> require M; M.unless x > 3, do: true
true
iex>
なんとなくそれっぽく動いているようだ。unquoteは、引数をquoteされた式中にそのままのコンテキストで埋め込むマクロである。unquoteがないとコンテキストが分断されてしまう。
iex> x = 1
1
iex> quote do: x + 1 + 1
{:"+",0,[{:"+",0,[{:x,0,:quoted},1]},1]}
iex> quote do: unquote(x + 1) + 1
{:"+",0,[2,1]}
iex>
これだけだと、わかりにくいかもしれないので、unlessでunquoteしないで定義してみよう。
iex> defmodule M2 do
...> defmacro unless(clause, options) do
...> quote do: if !clause, options
...> end
...> end
nofile:2: variable clause is unused
nofile:2: variable options is unused
{:unless,2}
iex> require M2; M2.unless x > 0, do: true
** (::FunctionClauseError) no function clause matching: ::Elixir::Builtin.if({:"!",1,[{:clause,1,:quoted}]}, {:options,1,:quoted})
lib/elixir/builtin.ex:830: ::Elixir::Builtin.if({:"!",1,[{:clause,1,:quoted}]}, {:options,1,:quoted})
nofile:1: ::Elixir::Builtin.if/2
src/elixir_dispatch.erl:104: :elixir_dispatch.dispatch_macro/6
src/elixir_dispatch.erl:111: :elixir_dispatch.dispatch_macro/6
lists.erl:1278: :lists.mapfoldl/3
lists.erl:1279: :lists.mapfoldl/3
src/elixir.erl:66: :elixir.eval_forms/3
src/elixir.erl:49: :elixir.eval/5
iex>
::Elixir::Builtin.if({:"!",1,[{:clause,1,:quoted}]}, {:options,1,:quoted})
となっているとおり、変数clauseとoptionsとしてそのままquoteされている。そうではなく、unlessに渡したclauseとoptionsの中身をそのまま置き換えてほしかった筈だ。それをしてくれるのがunquoteになる。
言い換えると、unquoteはquoteされたツリーに式を組み込むメカニズムで、メタプログラミングの本質だ。
マクロの再帰
実用的ではないが、フィボナッチ数を計算するマクロを実装してみよう。
defmodule Self do defmacro fibm(0) do quote do: 0 end defmacro fibm(1) do quote do: 1 end defmacro fibm(x) do quote do: unquote(fibm(x-1)) + unquote(fibm(x-2)) end def fibmm(x) do fibm(x) end
Self.fibmm/1関数はfibmマクロを呼び出し、置き換え結果を返す。モジュールの内部の関数からマクロを呼び出すと、展開結果を評価しないのだ。評価されてしまうと、マクロの再帰ができなくなってしまうため、このようになっている。これを使ってマクロがどうquoteされていくかを観察できる。
上記のコードをiexに読み込ませて
iex> Self.fibmm(4)
{:"+",0,[{:"+",0,[{:"+",0,[1,0]},1]},{:"+",0,[1,0]}]}
iex> require Self; Self.fibm(4)
3
iex>
まとめ
defmacroでquoteした式にunquoteして引数を組み込むという仕組みはシンプルだが強力だ。
Doug HoyteはLET OVER LAMBDA*1で「他のすべての言語が単にlispに皮をかぶせたにすぎない」と指摘している。elixirは他のBlub言語*2と異なり、その皮をいつでもquoteで剥がし、Lispと同様にそのナマの構造をelixirで操作することができることが示せたと思う。これこそがプログラマにとっての万能薬の秘密だ。
次回は、マクロをさらに掘り下げていきたい。