5 Erlang コードを Debian パッケージにしてみた

前田耕 平

PIC
____________________________________________________________________

5.1 入門のきっかけ

Erlang というプログラミング言語があります。 今回、 これを勉強してみようと思ったのは、 Erlang そのも のがきっかけではなく、 いつものように別のものがきっかけです。 今回は、 CouchDB という Apache Incubator*3 に あった*4 、 ドキュメント指向のデータベースを友人から教えてもらったのがきっかけでした。 CouchDB が Erlang と JavaScript で実装 されている、 というので、 「Erlangって何だろう?」 と興味を持ちました。

5.1.1 ちょっと脱線: CouchDB とは

CouchDB の特徴には以下のものがあります。

RDB のように事前にしっかりとスキーマの定義を行っておく必要はなく、 HTTP PUT でデータ(=ドキュメント) を作 成・更新し、 GET でデータを取得し、 DELETE でデータを削除する、 ちょっと変わったデータベースです。 RDB を置き換え るものではないので、 摘要範囲は正直まだよく分かりません。 ですが、 面白そうなので友人とこれを広めるべく、 目下水面下で 活動中です。

CouchDB のアーキテクチャについては、 CouchDB Project のホームページから転載した、 下記のシステム概念図をご覧下 さい。 これらの実装は主に Erlang によるものです。

また、 CouchDB については、 水面下の活動の一環で翻訳なども行っているので、 また別の機会にお話できればと思い ます。

5.1.2 閑話休題: Erlang の概要

本題の Erlang ですが、 CouchDB に興味を持ち、 さらにその中身をどうなっているのを知りたいと思ったのですが、 Erlang と いう言語を知りません。 関数型プログラミングで、 並列処理、 分散処理、 耐障害性を兼ね備えた、 並行指向プログラミングとい うのですが、 そもそも文法がまったく分からないので、 ソースコードだけを眺めて理解できるものではないな、 ということから 勉強を始めてみました。 また、 並列処理、 分散処理に興味を持っていたので、 興味がうまい具合に重なったのも、 きっかけとな りました。

Erlang の歴史
Erlang は、 Ericsson Computer Science Laboratory で、 分散化された環境、 耐障害、 ある程度のリアルタイム性、 無停止で 稼働する、 といった条件のシステムを構築できるように設計されたプログラミング言語です。 もともとは、 エリクソンの社内だ けで使われていましたが、 1998 年にオープンソースとして公開されました。 ライセンスは、 Erlang Public License*5 で、 Mozilla Public License の派生ライセンスです。 関数型で並列指向のプログラミング言語ということですが、 20 年を超える歴史と、 高度な信頼性が要求される通信機器の分野でも実績があり、 例えばエリクソンの AXD301*6 な どがあるそうです。

仕様
プログラミング Erlang という書籍を一通り読んで、 今 2 周目で実際に手を動かしている段階ですが、 その中で興味を持った特 徴を挙げます。

自分には馴染みが薄いものばかりです。 特に、 「変数の書き換えはできない」 なんて、 定数じゃないの?と思いました。 詳しくは 後述。

Erlang による最近の実装例
Erlang で実装されているソフトウェアの例です。 これらは deb パッケージになっており、 Lenny にも含まれてい ます。

Erlang の検証事例
数年前から、 一部では流行りらしいので、 検証事例もちらほら出ています。

一般的に普及していくのはまだこれからみたいな感じしますが、 あまり使われていないうちに遊んでおこうと思い ます。

5.2 環境を整える

Debian では数はまだ少ないものの Erlang と Erlang で書かれたソフトウェアのパッケージがあります。

$ apt-cache search erlang | wc -l
63

ちなみに、 例として挙げた CouchDB や YAWS も deb パッケージになっています。

Erlang を使うために最低限必要なパッケージは次の erlang-base パッケージです。 対話型ではなく、 ソース コードを書くのには emacs の Erlang 用のモードもあるので、 それも一緒にインストールしておくと良いで しょう。

$ sudo apt-get install erlang-base erlang-mode

ところで、 erlang-base パッケージではなく、 erlang パッケージをインストールすれば、 erlang ソースパッケージから分割さ れている関連パッケージも軒並みインストールされるので便利かもしれませんが、 この場合は依存関係も含めて関連するパッ ケージが 90 以上導入されます。 状況に応じて適宜選択してください。

$ sudo apt-get install erlang

5.3 対話型で使ってみる

対話型で Erlang を使うには、 erl コマンドを実行します。 すると、 Erlang shell(Eshell) が起動します。

$ erl
Erlang (BEAM) emulator version 5.6.5 [source] [64-bit] [smp:2] [async-threads:0] [kernel-poll:false]

Eshell V5.6.5  (abort with ^G)
1>

“1>” はプロンプトです。 Eshell を終了するには、 q(). と入力するか、

1> q().
ok
2> $

または、 ctrl+ c, a と入力します。

1>
BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
       (v)ersion (k)ill (D)b-tables (d)istribution
a
$

では、 変数を書き換えれない、 という件の特徴をみてみます。

1> A=10.
10
2> A=20.
** exception error: no match of right hand side value 20

見ての通り、 エラーになってしまいました。 Erlang の変数の書き換えはできない、 単一代入変数なのです。 もう一度代入の 仕方を変えて見てみましょう。

1> A1=1.
1
2> A2=2.
2
3> A3=A1+A2.
3
4> A3=3.
3
5> A3=A1+2.
3
6> A3=A1+4.
** exception error: no match of right hand side value 5
7> A1=A2+A3.
** exception error: no match of right hand side value 5
8> A1=A3-A2.
1

1,2,3 のように未代入の変数に対しては値を再代入できますが、 6,7 のように一度代入された変数に対しては違う値を代入し なおすことができないことが分かります。 しかし、 4,5,8 のように同じ値であれば再代入できるように見えます。 実は、 4,5,8 は変数を再代入しているわけではなく、 代入済みの変数に対し、 パターン照合を行っているの です。

代入済みの変数のことを束縛済み変数、 と言いますが、 未束縛状態の変数に対しては、 イコールは値の代入を行いますが、 束 縛済みの変数に対してはパターン照合を行う、 という訳です。

今までこんな仕様のプログラミング言語は見たことなかったのですが、 プログラミング言語ではなく、 実は数学の代数であれ ば同じような考え方ができることが分かります。 例えば、 x + y = 6,x-y = 2 という関数を考えてみます。 この状態では、 x, y の代数は x=4, y=2 以外の値にはなりません。 単一代入変数は、 数学の代数と同じようなものと考えれば、 良いのかもしれま せん。

5.4 コンパイルしてみる

次に、 ソースコードからコンパイルして実行する方法について説明します。 プログラムを実行する方法は、 次の 3 種類があり ます。

例としてはやはり、 Hello World を書いてみます。 これは引数なしのプログラムです。 引数ありとなしで はプログラム実行時に若干の違いが出てきます。 hello.erl というファイル名で次のプログラムを用意して下 さい。

-module(hello).
-export([start/0]).

start() ->
    io:format("Hello world on Erlang~n").

Erlang のプログラムはモジュールを基本単位とし、 どの関数もモジュールに属する構造になっています。 コードを 実行するには、 まずモジュールをコンパイルしなければなりません。 コンパイルしたモジュールは、 拡張子 beam*9 とい う名前のファイルが生成されます。 コンパイルには2つのやり方があります。

5.4.1 Eshell からコンパイルする方法

先ほどの hello.erl を Eshell からコンパイルし、 実行するには、 次の手順です。

1> c(hello).
{ok,hello}
2> he
heart    hello
2> hello:start().
Hello world on Erlang
ok
3>

c(hello). でコンパイルの実行を行います。 c() の引数が module(hello) および、 プログラムファイル名 hello.erl と一致するこ とに注意してください。 コンパイルすると、 hello.beam ファイルが生成されます。 これは Erlang VM 向けのバイナリコード です。

$ ls -lrt hello.*
-rwx------ 1 user user   90 2009-05-14 01:35 hello.erl
-rw------- 1 user user  552 2009-05-14 01:35 hello.beam
$ file hello.beam
hello.beam: Erlang BEAM file
5.4.2 シェルからコンパイルする

シェルからコンパイルする場合は、 erlc コマンドを使います。

$ erlc hello.erl
$ ls -lrt hello.*
-rwx------ 1 user user   90 2009-05-14 01:35 hello.erl
-rw------- 1 user user  708 2009-05-14 01:53 hello.beam

実行もシェルから行ってみます。

$ erl -noshell -s hello start -s init stop
Hello world on Erlang

無事、 実行できました。 なお、 各引数は次の意味があります。

5.4.3 escript で実行する

Erlang では escript という仕組みもあり、 これを使うとプログラムをコンパイルせずにスクリプトとして直接実行でき ます。

関数 hello:start を escript として実行するには、 次のようにファイルを書き換えます。 ファイル名は今回、 hello.esc としまし たが、 コンパイルするときと違い、 任意の拡張子で問題ありません。

#!/usr/bin/env escript

main(_) ->
    io:format("Hello world on Erlang~n").

これを実行権限をつけて実行してみます。

$ chmod 700 hello.esc
$ ./hello.esc
Hello world on Erlang
5.4.4 引数ありの関数をコンパイル、 実行する

今回の例は、 引数なしでしたが、 シェルで実行する場合のプログラムはどうやって引数を渡せば良いのでしょうか。 今度は引数 ありのプログラムを試してみます。

フィボナッチ数列を計算する関数を書いてみました。 まず、 Eshell で引数を渡す場合です。

-module(fib).
-export([fib/1]).

fib(0) -> 1;
fib(1) -> 1;
fib(N) ->
    fib(N-2) + fib(N-1).

実行するとこんな感じ。

1> c(fib)
1> .
{ok,fib}
2> fib:fib(10).
89
3> fib:fib(20).
10946
4> fib:fib(30).
1346269

シェルから実行する場合は、 書き方を変える必要があります。

-module(fib1).
-export([main/1]).

main([A]) ->
    I = list_to_integer(atom_to_list(A)),
    F = fib(I),
    io:format("fibonacci ~w = ~w~n", [I, F]),
    init:stop().

fib(0) -> 1;
fib(1) -> 1;
fib(N) ->
    fib(N-2) + fib(N-1).

これをシェルでコンパイル、 実行してみます。

$ erlc fib1.erl
$ erl -noshell -s fib1 main 10 -s init stop
fibonacci 10 = 89
$ erl -noshell -s fib1 main 20 -s init stop
fibonacci 20 = 10946
$ erl -noshell -s fib1 main 30 -s init stop
fibonacci 30 = 1346269

5.5 deb パッケージにしてみる

前述のとおり、 Erlang は beam ファイルからコードをロードするか、 escript で実行する必要があります。 Erlang のランタイ ムシステムは、 コード自動ロード機構を利用しています。 現在のロードパスは Eshell から code:get_path() 確認することがで きます。

1> code:get_path().
[".","/usr/lib/erlang/lib/kernel-2.12.5/ebin",
 "/usr/lib/erlang/lib/stdlib-1.15.5/ebin",
 "/usr/lib/erlang/lib/xmerl-1.1.10/ebin",
 "/usr/lib/erlang/lib/webtool-0.8.3.2/ebin",
 "/usr/lib/erlang/lib/typer-0.1.5/ebin",
 "/usr/lib/erlang/lib/tv-2.1.4.2/ebin",
 "/usr/lib/erlang/lib/tools-2.6.2/ebin",
 "/usr/lib/erlang/lib/toolbar-1.3.0.1/ebin",
 "/usr/lib/erlang/lib/test_server-3.2.4/ebin",
 "/usr/lib/erlang/lib/syntax_tools-1.5.6/ebin",
 "/usr/lib/erlang/lib/ssl-3.10/ebin",
 "/usr/lib/erlang/lib/ssh-1.0.2/ebin",
 "/usr/lib/erlang/lib/snmp-4.12/ebin",
 "/usr/lib/erlang/lib/sasl-2.1.5.4/ebin",
 "/usr/lib/erlang/lib/runtime_tools-1.7.3/ebin",
 "/usr/lib/erlang/lib/public_key-0.1/ebin",
 "/usr/lib/erlang/lib/pman-2.6/ebin",
 "/usr/lib/erlang/lib/percept-0.7.3/ebin",
 "/usr/lib/erlang/lib/parsetools-1.4.5/ebin",
 "/usr/lib/erlang/lib/otp_mibs-1.0.4.1/ebin",
 "/usr/lib/erlang/lib/os_mon-2.1.8/ebin",
 "/usr/lib/erlang/lib/orber-3.6.10/ebin",
 "/usr/lib/erlang/lib/odbc-2.10.3/ebin",
 "/usr/lib/erlang/lib/observer-0.9.7.4/ebin",
 "/usr/lib/erlang/lib/mnesia-4.4.7/ebin",
 "/usr/lib/erlang/lib/megaco-3.9.1.1/ebin",
 "/usr/lib/erlang/lib/inviso-0.6/ebin",
 [...]|...]

先頭に、 “.” が入っているため、 カレントディレクトリにある、 beam ファイルは自動的にコードがロードされるため、 実行 できたわけです。 コードパスは/usr/lib/erlang/lib が共通しています。 これは、 Erlang のデフォルトのライブラリディレクト リで、 code:lib_dir() で確認できます。

1> code:lib_dir().
"/usr/lib/erlang/lib"

先ほど作成した hello モジュールもこのライブラリディレクトリ配下に配置します。 しかし、 ここに配置すると自動的にロー ドパスに入るわけではないので、 実際に必要な場合は、 ちゃんと指定する必要があります。

さて、 hello-erlang-1.0 というディレクトリを作り、 hello モジュール関係は全てこのディレクトリに移します。

$ mkdir hello-erlang-1.0
$ mv hello.erl hello.esc

Makefile を用意します。

.SUFFIXES: .erl .beam .yrl

.erl.beam:
erlc -W $<

BINDIR?=/usr/lib/erlang/lib/hello-1.0/ebin

ERL = erl -boot start_clean

# compile erlang module list
MODS = hello

all: compile
${ERL} -noshell -s hello start -s init stop

compile: ${MODS:%=%.beam}

install:
install -d ${DESTDIR}${BINDIR}
install -m 644 hello.beam ${DESTDIR}${BINDIR}
install -m 755 hello.esc  ${DESTDIR}$/usr/bin/

clean:
rm -rf *.beam erl_crash.dump

dh_make を実行します。 今回は CDBS 形式にしてみます。

$ dh_make -c gpl -b --createorig
Maintainer name : Kouhei Maeda
Email-Address   : mkouhei@palmtb.net
Date            : Fri, 15 May 2009 00:36:24 +0900
Package Name    : hello-erlang
Version         : 1.0
License         : gpl3
Using dpatch    : no
Using quilt     : no
Type of Package : cdbs
Hit <enter> to confirm:
Skipping creating ../hello-erlang_1.0.orig.tar.gz because it already exists
Done. Please edit the files in the debian/ subdirectory now. You should also
check that the hello-erlang Makefiles install into $DESTDIR and not in / .

debian ディレクトリ以下を編集します。 まず、 不要なサンプルファイルを削除します。

$ cd debian
$ rm *.ex *.EX

changelog を編集します。

$ dch
hello-erlang (1.0-1) unstable; urgency=low

  * Initial releas

 -- Kouhei Maeda <mkouhei@palmtb.net>  Thu, 14 May 2009 22:56:51 +0900

control を編集します。 erlang-base パッケージが必要なので追記します。

Source: hello-erlang
Section: game
Priority: extra
Maintainer: Kouhei Maeda <mkouhei@palmtb.net>
Build-Depends: cdbs, debhelper (>= 7), erlang-dev
Standards-Version: 3.8.1
Homepage: http://www.palmtb.net/

Package: hello-erlang
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}, ${erlang-base:Depends}
Description: Hello World for Erlang.
 Hello World Erlang version.

copyright を編集します。

This package was debianized by:

    Kouhei Maeda <mkouhei@palmtb.net> on Thu, 14 May 2009 22:54:34 +0900

Upstream Author(s):

    Kouhei Maeda <mkouhei@palmtb.net>

Copyright:

    <Copyright (C) 2009 Kouhei Maeda>

License:
(以下略)

rules を編集します。 dh_make で生成された時は、 include 文しかないので、 他を追加します。 特に、 DEB_FIXPERMS_EXCLUDE で、 hello.beam を指定しておかないと、 インストール時に実行権限が付与されてしまいます。 *11beam ファイルは Erlang VM が読み込むだけですので、 実行権は必要ありません。

#!/usr/bin/make -f

DEB_MAKE_CHECK_TARGET :=
DEB_FIXPERMS_EXCLUDE := hello.beam

include /usr/share/cdbs/1/rules/debhelper.mk
include /usr/share/cdbs/1/class/makefile.mk

binary-arch binary-indep: build

DEB_MAKE_INSTALL_TARGET := install DESTDIR=$(DEB_DESTDIR)$(cdbs_curpkg)
# Add here any variable or target overrides you need.

以上が終わったら、 debuild を実行します。

$ debuild -us -uc
\end{commadline}

pbuilder を実行します。
\begin{commandline}
$ sudo pbuilder create --distribution sid
$ sudo pbuilder build hello-erlang_1.0-1.dsc

問題なければ、 最後にインストール、 アンインストールできるか確認しておきます。

$ sudo dpkg -i hello-erlang_1.0-1_amd64.deb
未選択パッケージ hello-erlang  を選択しています。
(データベースを読み込んでいます ...  現在 193995  個のファイルとディレクトリがインストールされています。 )
(hello-erlang_1.0-1_amd64.deb  から) hello-erlang  を展開しています...
hello-erlang (1.0-1)  を設定しています ...

$ dpkg -L hello-erlang
/.
/usr
/usr/bin
/usr/bin/hello.esc
/usr/share
/usr/share/doc
/usr/share/doc/hello-erlang
/usr/share/doc/hello-erlang/changelog.Debian.gz
/usr/share/doc/hello-erlang/README.Debian
/usr/share/doc/hello-erlang/copyright
/usr/sbin
/usr/lib
/usr/lib/erlang
/usr/lib/erlang/lib
/usr/lib/erlang/lib/hello-1.0
/usr/lib/erlang/lib/hello-1.0/ebin
/usr/lib/erlang/lib/hello-1.0/ebin/hello.beam
$ hello.esc
Hello world on Erlang
$ erl -noshell -pa /usr/lib/erlang/lib/hello-1.0/ebin -s hello start -s init stop
Hello world on Erlang
$ sudo apt-get remove --purge hello-erlang
パッケージリストを読み込んでいます...  完了
依 存 関 係 ツ リ ー を 作 成 し て い ま す
状態情報を読み取っています...  完了
以 下 の パ ッケ ー ジ が 自 動 で イ ン ス ト ー ルされましたが、 もう必要とされていません :
(snip)
これらを削除するには ’apt-get autoremove’  を利用してください。
以 下 の パ ッケ ー ジ は 「削 除 」 さ れ ま す :
  hello-erlang*
アップグレード: 0  個、 新規インストール: 0  個、 削除: 1  個、 保留: 17  個。
こ の 操 作 後 に  73.7kB  のディスク容量が解放されます。
続 行 し ま す か  [Y/n]?
(データベースを読み込んでいます ...  現在 194002  個のファイルとディレクトリがインストールされています。 )
hello-erlang  を削除しています ...

5.6 まとめ

今回、 Erlang の入り口を少しだけ眺めてみました。 まだまだ分からないことが多いのでさらに勉強していきます。 また、 最後 に記述したパッケージの作成については、 Erlang のコードパスは自動的にロードされないので、 パッケージにす る=パッケージにされたコードが便利に使える訳ではありません。 先月の勉強会で紹介された OCaml では、 dh-ocaml というパッケージ作成支援ツールがありますが、 Erlang にはありませんでした。 Erlang 独自に必要な 設定を支援する、 dh-erlang のようなツールがあると、 Debian でも Erlang はさらに便利になるなと思いま した。

5.7 参考

第 52 回東京エリア Debian 勉強会 2009 年 5 月
____________________________________________________________________________________________