2011年3月21日月曜日

プログラミング言語 efene について

今回はプログラミング言語 efene についてです

efene は2010年1月にバージョン 0.1 をリリースし、開発がスタートした新しい言語で、 Erlang が提供する、 ErlangVM 上で動作し、Erlang とほぼ 100% の互換性を持つことを目標として実装されています

ErlangVM 上で動く言語としては、 Ruby に似た文法を持つ、 reiaElixir、 LISP 文法の LFE(Lisp Flavoured Erlang) などがありますが、これらは直接 ErlangVM 用のバイトコードを出力するのに対して、 efene は一度 Erlang のソースコードに変換し、 Erlang のソースコードとしてコンパイルして、バイトコードを出力しており、そのため Erlang との高い互換性を実現できています

文法は "{...}" でブロックを表す JavaScript 、またはインデントでブロックを表す Python に似た 2種類の構文で記述が可能で、 C / C++ 、 Java や LL 言語の開発者にはちょっとクセのある Erlang の文法よりもなじみやすいと思います
公式のドキュメントでは JavaScript 形式を efene 、 Python 形式を ifene (idented efene) とし、各拡張子を fn、ifnとするようにしていますが、コンパイラレベルでは区別はないようで、1つのファイルに efene 、ifene の文法が混在していても、コンパイル可能なようです(そんな実装をすることはないと思いますが…)

まずは、efene のコンパイル、実行環境をインストールします
インストールには agner を利用します
(もちろん、 agner を使用せず、ソースから直接ビルドして利用することも可能です)
agner については、コチラの記事を参照ください
ターミナル等で下記コマンドを実行します
$ agner install efene
インストールが正常に完了したら、環境変数 FNPATH を設定します
(今回 .bashrc ファイルに環境変数を記述しますが、お使いの環境に合わせて適宜読み替えてください)
$ vi ~/.bashrc
内容は以下の通りとなります
(例では agner を使用してインストールしたものですので、直接ソースコードよりビルド、インストールした場合は読み替えて設定してください)
export FNPATH=/usr/local/agner/packages/efene-\@master
以上で、 efene を実行する準備が完了です

定番の "Hello World!" は efene では以下のように書きます
@public
run = fn () {
    io.format("Hello World!~n")
}
上記の内容を、ファイル名を "hellofn.fn" として保存します

簡単な文法の説明は以下の通りです
(細かい用語の説明は行っていませんが、ご了承ください)
1行目の "@public" は以降に定義される関数へのアクセス権を表すもので、モジュール外からアクセス可能であることを表します
2行目の "run = fn () {" は "run" という引数を持たない関数を定義しています
関数の実態(処理内容)は以降の行("{...}"ブロック内)に定義しています
3行目の " io.format("Hello World!~n")" は "io" モジュールの "format()" 関数に 表示する "Hello World!~n" という文字列を渡しています
文字列内の "~n" は改行を表します
4行目の "}" は関数の処理部分(ブロック)の終端となります
また、4行目の "}" 記述後に必ず、改行を入れてください
(改行がない場合は、コンパイルエラーとなります)

下記 fnc コマンドを実行し、コンパイルします
$ fnc hellofn.fn
コンパイル後、下記のように実行します
$ fnc -r hellofn run
実行後、下記のように表示されます
(以下の表示は実行したコマンドも含めて記述しています)
$ fnc -r hellofn run
Hello World!

ifene では以下のように書きます
(ファイル名は "helloifn.ifn" として保存します)
@public
run = fn ()
    io.format("Hello World!~n")

efene 版と処理内容の違いは全くないのですが、ブロックを "{...}" ではなく、インデントで表すため、 "{" と "}" がなくなっています
コンパイル、実行も efene 版と同様です
(コンパイル、実行、実行結果をまとめて以下に記述します)
$ fnc helloifn.ifn
Compiling helloifn.ifn
$ fnc -r helloifn run
Hello World!
文法に関する詳細は公式ドキュメントを参照頂ければと思います

次に、 Erlang の関数型言語としての面を紹介する際によく出てくる、 クイックソートというアルゴリズムの実装を紹介します
Erlang 版のクイックソートは以下のように書かれます
(Wikipedia の "Erlang" の項目より引用しています)
%% quicksort:qsort(List)
%% Sort a list of items
 -module(quicksort).
 -export([qsort/1]).
 
 qsort([]) -> [];
 qsort([Pivot|Rest]) ->
     qsort([ X || X <- Rest, X < Pivot]) ++ [Pivot] ++ qsort([ Y || Y <- Rest, Y >= Pivot]).
efene では以下のように記述することができます
@public
qsort = fn([]) {
  []
}

fn([Pivot : Rest]) {
  qsort([X for X in Rest if X < Pivot]) ++ [Pivot] ++ qsort([Y for Y in Rest if Y >= Pivot])
}
ブロックを明示的に記述している分、若干コードの量は増えていますが、ほぼ同様に書くことができます
また、 efene コマンドの機能として、"-t erl" オプションを指定すると Erlang 形式にて出力することが可能です
上記クイックソートのコードを Erlang 形式に出力した場合は以下のようになります
$ fnc -t erl quicksort.fn
-module(quicksort).

-export([qsort/1]).

qsort([]) -> [];
qsort([Pivot | Rest]) ->
qsort([X || X <- Rest, X < Pivot]) ++ [Pivot] ++ qsort([Y || Y <- Rest, Y >= Pivot]).

Erlang のプロセス間通信(非同期メッセージ転送)も使用することができます
@public
ping = fn(0, Pong_PID) {
  Pong_PID ! finished
  io.format("Ping finished~n")
}

fn(N, Pong_PID) {
  Pong_PID ! (ping, self())
  
  receive (pong) {
    io.format("Ping received pong~n")
  }
  
  ping(N - 1, Pong_PID)
}

@public
pong = fn() {
  receive (finished) {
    io.format("Pong finished~n")
  } else receive (ping, Ping_PID) {
    io.format("Pong received ping~n")
    Ping_PID ! pong
    pong()
  } 
}

@public
run = fn() {
  Pong_PID = spawn(pingpong, pong, [])
  spawn(pingpong, ping, [3, Pong_PID])
}
コンパイルし、実行すると、下記のように表示されます
$ fnc -r pingpong run
Pong received ping
Ping received pong
Pong received ping
Ping received pong
Pong received ping
Ping received pong
Ping finished
Pong finished
Erlang で書かれたライブラリをそのまま利用することも可能です
下記例では Riak という NoSQL データベースの Erlang 用クライアントライブラリ riak-erlang-client を使用する例です
(なお、Riak 、riak-erlang-client に関する詳細やインストール、利用方法などの説明は省略しています)
@public
run = fn () {
  (ok, Pid) = riakc_pb_socket.start_link("127.0.0.1", 8087)
  riakc_pb_socket.ping(Pid)
  Object = riakc_obj.new(<["groceries"]>, <["mine"]>, <["eggs & bacon"]>)
  riakc_pb_socket.put(Pid, Object)
  (ok, O) = riakc_pb_socket.get(Pid, <["groceries"]>, <["mine"]>)
   io.format("~p~n", [O])
}
コンパイルし、実行すると、下記のように表示されます
$ fnc -a ./riakc/ebin -a ./riakc/deps/protobuffs/ebin -r riakc run
{riakc_obj,<<"groceries">>,<<"mine">>,
<<107,206,97,96,96,224,202,96,202,5,82,108,205,73,140,156,46,143,50, 152,18,25,243,88,25,30,73,188,61,198,7,145,96,97,10,11,92,135,41, 12,84,207,254,242,132,56,84,34,58,227,61,92,61,219,145,174,61,152, 194,64,245,108,246,60,90,80,137,110,100,245,58,114,2,152,194,64, 245,172,12,115,68,161,18,223,196,127,195,213,179,79,121,84,140,77, 216,38,175,19,42,204,32,241,27,201,24,134,199,6,26,200,18,89,0>>,
[{{dict,2,16,16,8,80,48,
{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
{{[],[],[],[],[],[],[],[],[],[],
[[<<"X-Riak-VTag">>,87,51,74,53,119,109,67,70,90,50,86,
70,53,115,51,75,82,102,85,106,76]],
[],[],
[[<<"X-Riak-Last-Modified">>|{1300,667392,457396}]],
[],[]}}},
<<"eggs & bacon">>}],
undefined,undefined}
fnc コマンドにて "-s" オプションを付加することでシェルを起動することができます
このシェルでは計算や簡単な入出力が行えます
$ fnc -s
>>> 1+1
2
>>> A = "efene"
[101, 102, 101, 110, 101]
>>> io.format("Hello, ~s.~n", [A])
Hello, efene.
ok
シェルは "Ctrl+C" を押し、さらに "a" を押すことで停止することができます
なお、 Erlang のシェルと比較して、履歴機能がない、束縛した変数の表示や解放などの機能が実装されていなかったりとやや貧弱な面があります
(履歴に関しては、 rlwrap コマンドと一緒に使うことで擬似的に実現することは可能です)
また、簡単な使用には支障はないかと思いますが、表示部分にバグがあるようです
(上記 Riak クライアント利用もシェルにて実行可能なのですが、最後のデータ取得後の表示にてエラーが発生します)

以上のように、 efene は JavaScript / Python ライクな文法で Erlang の強力な機能を使用できる環境を提供してくれます
Erlang の文法に抵抗を感じていた人でも入りやすいと思います
しかし、やはり障害が発生した場合には最終的に Erlang のコードを読むことも多く、 efene を使えば、 Erlang に関しての知識が不要というわけではないので、ご注意ください

Erlang については Voluntas さんのブログ"Erlang に興味を持った人へ" がお勧めです
(Erlang での開発に関する貴重な最新情報が記載されています)

時間があるときに、(今更ですが)基礎文法最速マスターも書いてみたいなと思います

1 件のコメント:

luismarianoguerra さんのコメント...

I don't understand a thing, but thanks! :D