M12i.

学術書・マンガ・アニメ・映画の消費活動とプログラミングについて

Erlangチュートリアルを読む (1)

落ち着きのない性分を自覚しつつ、Erlangチュートリアルを読んでみました。

内容的には、やはりC言語ベースの構文やJavaベースのモジュール論に親しんでいると理解しにくいものもあります。

反対にHaskellの入門書などを読んでいると、逐次的なプログラムを解説する部分は問題なく理解できると思います。

また分散並列処理の解説は、ScalaなどでActorに関する概説を見ていても、やはりちょっとわかりづらいなぁ、という感じです。

まずは、逐次的プログラムの章の前半から。原典は、“Getting Started with Erlang User's Guide - Version 5.8.4 - May 24 2011”(2011/06/28取得)です。

******************************

2 逐次的なプログラミング

2.1 Erlangシェル

ほとんどのOSはコマンド・インタプリターもしくはシェルを持っています。UnixLinuxには多くの種類のそれが、またWindowsにはコマンドプロンプトがあります。Erlangはそこでコード断片を直接に記述し実行して動作確認できる自前ののシェルを持っています(shell(3)を参照)。(LinuxやUnitでは)Erlangシェルは、シェル〔ターミナル〕やコマンド・インタープリターを起動し、erlとタイピングすることで起動します。たぶん次のように…

% erl
Erlang (BEAM) emulator version 5.2 [source] [hipe]
Eshell V5.2  (abort with ^G)
1>

さて、下に示すように“2 + 5.”と入力してみましょう。

1> 2 + 5.
7
2>

Windowsでは、Erlangシェル・アイコンをダブルクリックして、シェルを起動します。

Erlangシェルは入力を待つナンバリングされた行を持っていることに気がついたでしょう。“2 + 5”と入力すると、正確に“7”と表示されました。さらに注目すべきことに、最前に実行したコードは、終止符“.”と復帰改行〔carriage return〕で終えられています。もしシェル上で入力中に間違ったら、多くのシェルと同様にバックスペースを使って内容を取り消すことができます。Erlangシェルにはこれ以外にも多くの編集コマンドが用意されています。(『ERTSユーザーガイド』の“tty - A command line interface”の章を参照してください。)

(注意:Shellでコードを記述したりテストしたりしているうちに、行番号はチュートリアルの本筋とは関係なしに大きな数字になっているかもしれません。)

さて、もう少し複雑な計算をしてみましょう。

2> (42 + 77) * 66 / 3.
2618.0

ここではブラケット“()”と乗算演算子“*”、そして除算演算子の“/”が使用されています。通常の数学と同様にはたらきます(『Erlangリファレンス・マニュアル』の“数学的な式表現”の章も参照してください)。

Erlangのシステムとシェルをシャットダウンするには、Ctrl + Cを入力します。次のような出力を見るでしょう:

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

“a”と入力するとErlangのシステムから抜けることができきます。

別の方法はhalt()を実行することです:

3> halt().
% 
2.2 モジュールと関数

シェルの上でコードを実行するだけでは、有用なプログラミングはできないでしょう。ここに小さなErlangプログラムがあります。適当なテキストエディタを使って作成されたtut.erlというファイルに記述されています。(このファイル名は重要です。このファイルはerlErlangシェル〕を起動したのと同じディレクトリにあることを確認してください。)もしかしたらあなたの使っているエディタには、Erlangモードが備わっているかもしれません。その場合、プログラムの入力と整形を簡単に行えるようにしてくれるでしょう。(『ツール・ユーザー・ガイド』の“EmacsErlangモード”の章を参照してください。)もっともそれなしでもコードの管理は行えます。ではコードを入力していきます:

-module(tut).
-export([double/1]).
double(X) ->
    2 * X.

このプログラム──任意の数字を倍する──について推測するのは難しいことではないでしょう。最初の2行については後ほど説明します。このプログラムをコンパイルします。Erlangシェルで下に示す方法で実行します:

3> c(tut).
{ok,tut}

“{ok,tut}”というのはコンパイルが成功したサインです。もし“error”と表示されたら、あなたの記述したコードに何らかの誤りがあったということです。間違った箇所をあなたに示唆するエラーメッセージが表示されるので、それを頼りにしてコードを修正し、しかる後に再度上述の手順を実行します。

では、プログラムを実行してみましょう。

4> tut:double(10).
20

期待したとおり、10の倍数、20が得られました。

さて、プログラムの最初の2行に目を移しましょう。Erlangプログラムはファイルに記述されます。それぞれのファイルは、Erlangモジュールと呼ばれるものを含みます。1行目でモジュール名が宣言されています(『Erlangリファレンス・マニュアル』の“モジュール”の章を参照)。

-module(tut).

この一文は、モジュールが“tut”という名前であることを宣言しています。行末の“.”〔ドット〕に注意してください。モジュールを格納するファイルは、モジュール名に拡張子“.erl”をつけたものである必要があります。今回のケースでは、tut.erlになります。異なるモジュールの関数を使用する場合、module_name:function_name(arguments) という構文を使います。ですから、

4> tut:double(10).

上記の一文は、tutモジュールのdouble関数を引数10を添えて呼び出すことを意味しています。

さて2行目では:

-export([double/1]).

tutモジュールがdoubleという名前の、引数(例のなかではXという名前の)を一つとる関数を含み、しかもその関数はtutモジュールの外側から呼び出すことができる、ということを宣言しています。詳しくは後述します。ここでも行末が“.”で終わっています。

今度はより複雑な例を見てみましょう。階乗を求める関数です。(例えば4の階乗は4 * 3 * 2 * 1です。)次のコードをtut1.erlという名前のファイルに書き込みます。

-module(tut1).
-export([fac/1]).
fac(1) ->
    1;
fac(N) ->
    N * fac(N - 1).

ファイルをコンパイルし、

5> c(tut1).
{ok,tut1}

実際に4の階乗を求めてみましょう。

6> tut1:fac(4).
24

tut1の最初の部分を見てみます:

fac(1) ->
    1;

1の階乗は1である、と宣言しています。この部分の終わりが“;”〔セミコロン〕で終えられていることに注意してください。この関数〔の定義〕がさらに続くことを示しています。第2の部分に進みます:

fac(N) ->
    N * fac(N - 1).

Nの階乗は、NそのものとN-1の階乗の乗じた数であると宣言しています。この部分の終わりは“.”〔ドット〕で終わっています。この関数〔の定義〕がこれでおしまいであることを示しています。

もっと多くの引数をとる関数も定義してみましょう。2つの数を乗じる取るに足らない関数で、tut1モジュールを拡張します。

-module(tut1).
-export([fac/1, mult/2]).
fac(1) ->
    1;
fac(N) ->
    N * fac(N - 1).
mult(X, Y) ->
    X * Y.

ここで、-exportの行も拡張しなくてはいけないことに注意してください。2つの引数をとる関数multiの情報を追加します。

コンパイルし:

7> c(tut1).
{ok,tut1}

実行してみます:

8> tut1:mult(3,4).
12

上の例では、関数に与えられた数値は整数で、関数定義の中の引数──N、X、Yは変数〔variables〕と呼ばれます。変数は大文字から始めなくてはいけません(『Erlangリファレンス・マニュアル』の“変数”の章を参照)。変数名の例としてはNumber、ShoeSize、Ageなどなどです。

2.3 アトム

アトム〔atoms〕は、Erlangにおけるもう一つのデータ形式です。アトムは小文字からはじまります(『Erlangリファレンス・マニュアル』の“アトム”の章も参照)。例えば、charles、centimeter、inchといった具合に。アトムは単純な名前で、それ以上の何かではありません。変数とちがって、何かしらの値を持つということがありません。

次のプログラムを作成しましょう(ファイル名はtut2.erlです)。このプログラムは、インチからセンチメートルへの変換とその反対の変換を行うものです:

-module(tut2).
-export([convert/2]).
convert(M, inch) ->
    M / 2.54;
convert(N, centimeter) ->
    N * 2.54.

コンパイルし実行してみましょう:

9> c(tut2).
{ok,tut2}
10> tut2:convert(3, inch).
1.1811023622047243
11> tut2:convert(7, centimeter).
17.78

ここで何の説明もなしにデシマル〔decimals〕(浮動小数点数)を導入していますが、問題なくご理解いただいていますよね?

〔第2引数のアトムとして〕centimeterinch以外のものを与えたら何が起きるでしょうか:

12> tut2:convert(3, miles).
** exception error: no function clause matching tut2:convert(3,miles)

変換関数の2つの部分は、それぞれ節〔clause。クローズ〕と呼ばれます。関数のなかに“miles”というアトムを引数としてとるものはありません。Erlangシステムは関数呼び出しが、その関数のどの節にもマッチしない場合、エラーメッセージfunction_clauseを投げます。Erlangシェルはエラーメッセージをきれいに整形して表示します。エラー情報のタプル〔後述のデータ形式の1つ〕は、Erlangシェルの履歴に保存され、シェル・コマンドv/1で出力することができます:

13> v(12).
{'EXIT',{function_clause,[{tut2,convert,[3,miles]},
                          {erl_eval,do_apply,5},
                          {shell,exprs,6},
                          {shell,eval_exprs,6},
                          {shell,eval_loop,3}]}}
2.4 タプル

ところでtut2プログラムは、よいプログラミング・スタイルをとっているとは言い難いところがあります。考えてみてください:

tut2:convert(3, inch).

この関数呼び出しは、3という数字がインチ単位であることを言っているのでしょうか? それとも3という数字はセンチメートル単位のもので、それをインチ単位の数字に変換するように言っているのでしょうか? Erlangは、ことをもっとわかりやすくするために、情報をグルーピングする方法を用意しています。それがタプル〔tuples〕です。タプルは、“{”と“}”によって囲われます。

例えば3インチのことを{inch, 3}と、また5センチのことを{centimeter, 5}と、それぞれ表すことができます。それでは、センチをインチに、またその逆へと変換するプログラムを新しく書いてみましょう。(ファイル名はtut3.erlです。)

-module(tut3).
-export([convert_length/1]).
convert_length({centimeter, X}) ->
    {inch, X / 2.54};
convert_length({inch, Y}) ->
    {centimeter, Y * 2.54}.

コンパイルし、実行します:

14> c(tut3).
{ok,tut3}
15> tut3:convert_length({inch, 5}).
{centimeter,12.7}
16> tut3:convert_length(tut3:convert_length({inch, 5})).
{inch,5.0}

シェル入力の16行目では、一度5インチをセンチ単位に変換したあと、確認のため再度元の値へと変換しています。すなわち、Erlangでは関数の引数として、別の関数の結果値を利用することができます。しばし立ち止まって、16行目で行っていることについて考えてみましょう。関数に引数として与えた{inch, 5}は、convert_length関数の最初の節の先頭部(“->”の前の部分です)──すなわちconvert_length({centimeter,X})──に対して照合されますが、{inch, 5}は適合しません。はじめの照合が失敗したので、続いて次の節の先頭部──すなわちconvert_length({inch,Y})との照合が行われます。これは成功します。そして変数Yは値として5をとります。

ここまで2つの要素からなるタプルを見てきましたが、タプルは必要に応じてより多くの要素を持つことができます。そしてErlangの表現として妥当なもの〔valid Erlang term〕であればいかなるものでも内包できます。例えば、世界中の都市の気温を表現するために、次のようにタプルを記述できます。

{moscow, {c, -10}}
{cape_town, {f, 70}}
{paris, {f, 28}}

これらのタプルは決まった数のデータを内包しています。このデータのことを要素〔element〕と呼びます。タプル{moscow,{c,-10}}の中には、第1要素として〔アトムである〕moscowと、第2要素として〔タプルである〕{c,-10}があります。“c”はセ氏温度〔Centigrade〕(セルシウス〔Celsius〕)を、“f”はカ氏温度〔Farenheit〕を意味することに決めました。

2.5 リスト

タプルがデータをまとめてグルーピングするのに対して、データの一覧〔リスト〕を表現したいときがあります。Erlangにおけるリスト〔lists〕は、“[”と“]”で囲われます。例えば、いろいろな都市の気温の一覧は次のような感じです:

[{moscow, {c, -10}}, {cape_town, {f, 70}}, {stockholm, {c, -4}},
 {paris, {f, 28}}, {london, {f, 36}}]

このリストは一行で表現するには長すぎました。しかし問題ありません。Erlangは、例えばアトム表記の途中であるとか、整数の表記の途中であるとかの場面を除く、“適切な場所”〔sensible places〕で改行することができます。

リストの要素を調べるときに非常に便利なのが“|”です。シェルを使って説明するのがよいでしょう:

17> [First |TheRest] = [1,2,3,4,5].
[1,2,3,4,5]
18> First.
1
19> TheRest.
[2,3,4,5]

ここではリストの最初の要素を、残りの要素から切り離すのに、“|”を使用しています。(変数Firstは値として1をとり、変数TheRest[2,3,4,5]をとっています。)

別の例もお見せしましょう:

20> [E1, E2 | R] = [1,2,3,4,5,6,7].
[1,2,3,4,5,6,7]
21> E1.
1
22> E2.
2
23> R.
[3,4,5,6,7]

ここでは“|”がリストの最初の2要素を取得するのに用いられています。もちろん対象のリストに含まれるより多い数の要素を取得しようとすれば、エラーが投げられることでしょう。ここで特殊ケースとして、要素を一つも持たないリスト[]があります。

24> [A, B | C] = [1, 2].
[1,2]
25> A.
1
26> B.
2
27> C.
[]

ここまで見てきたすべての例で、私はいつも新しい変数〔名〕を使用し、一度使用したものを再度使用するということをしませんでした:First、TheRest、E1、E2、R、A、B、C。なぜなら変数はそのコンテクスト(スコープ)の中で、一度しか値をとることができないからです。後ほど立ち返ることになるはずですが、これはそんなに奇妙なことでもないのです。

続く例は、リストの長さを求める方法を示したものです:

-module(tut4).
-export([list_length/1]).
list_length([]) ->
    0;    
list_length([First | Rest]) ->
    1 + list_length(Rest).

コンパイルし(ファイル名はtut4.erlです)、実行してみます:

28> c(tut4).
{ok,tut4}
29> tut4:list_length([1,2,3,4,5,6,7]).
7

順に説明していきます:

list_length([]) ->
    0;

空っぽのリストの長さはもちろん0です。

list_length([First | Rest]) ->
    1 + list_length(Rest).

第1要素としてFirstを、また残りの要素としてRestを持つリストの長さは、1 + Restの長さ となります。

(上級者の方へ:この関数の実装方法は末尾再帰的ではありません。この関数を記述するためのよりよい方法があります。)

一般に、他の言語において“レコード”〔records〕や“構造体”〔structs〕を使用する場面ではタプルを、そして(他の言語におけるリンク・リスト〔linked list〕のような)可変長のものを表現するためにはリストを使用します。

Erlangには文字列データ型は存在しません。その代わりに文字列はASCII文字のリストとして表現されます。したがってリスト[97,98,99]は“abc”と等価です。Erlangシェルはかしこくリストの種類を推測します。そしてもっともふさわしいと思われる形式で出力します。例えば次のように:

30> [97,98,99].
"abc"

******************************

原典は、“Getting Started with Erlang User's Guide - Version 5.8.4 - May 24 2011”(2011/06/28取得)です。