Fat Old Sun RSSフィード

2012-05-26

[][]Elixir v0.5.0 releasedの超意訳

はじめのお断り

では行くぞ!

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サポート、ドキュメント生成ツールと多くに取り組んだ。すぐに、GithubElixir構文ハイライト有効になり、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-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

[][] 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

ではこれを実装するマクロdelegateを考えていこう。

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 GrahamOn LispLisp拡張可能なプログラミング言語であると書いているが、Elixirもその能力を持っている事がわかっていただけるだろう。

次回は、レコード機能プロトコル機能について説明予定。

On Lisp

On Lisp

*1リードマクロがあれば、構文解析前にもフックを入れることができるようになるのだが

2012-04-08

[][] elixir-modeをハックしてみた

elixir向けのemacs メジャーモードとしてはsecondplanet/elixir-mode ? GitHubがあるのだが、0.4.0-dev以降のelixirには対応していない。elixir側も結構キーワードが変わっているし、作者氏も更新するのをやめてしまったみたいだし、インデントコマンド無限ループするなど微妙なのだ

ということで、forkしてクイックハックしてみた。

k1complete/elixir-mode ? GitHub

においているので適当git cloneしてみてください。

変更内容

ハイライトキーワード追加

defmacro, defmodule, defrecordなどを追加。

制御変数を追加
elixir-basic-offset 8通常のインデント単位
elixir-key-label-offset 2caseでのmatchラベルやelseラベルのインデント単位。0だと、インデント無し
バグ修正

インデント動作で時々無限ループするバグの修正

インデントルール改善

match:などのインデント対応

使い方

.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

[][] elixirはプログラマ万能薬になるか その3

前回はrubyなところを主に説明してきたので、いよいよ今回はLispな所であり、個人的に最もエキサイティングだと感じているメタプログラミングについて記述する。

メタプログラミング

プログラムを書くプログラムを書く事をメタプログラミングと呼ぶ。Cのプリプロセッサや、yacc等のコード生成系もメタプログラムの範囲に含める場合があるようだが、Lispが最も有名であり、徹底されている。LispプログラムLispS式表現されているため、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 }
第一要素
アトム(関数名や変数名)か、他のタプル表現
第二要素
行番号
第三要素
関数への引数となるリストか、変数属性を表すアトム(nilあるいはquoted)

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で操作することができることが示せたと思う。これこそがプログラマにとっての万能薬秘密だ。

マクロ世界へようこそ。

次回は、マクロをさらに掘り下げていきたい。

ページビュー
167027