M.Hiroi's Home Page

お気楽 Erlang プログラミング入門

Erlang の基礎知識


Copyright (C) 2011-2024 Makoto Hiroi
All rights reserved.

●使ってみよう

それでは、さっそく Erlang を使ってみましょう。Eshell を立ち上げてください。本稿では Eshell のプロンプトを > で表すことにします。終了する場合は halt(). または q(). と入力してください。プロンプトのあとに式を入力すると、Eshell は式を評価して結果を返します。

> 1 + 2 * 3.
7
> -3 * 4.
-12

対話モードで式を入力する場合、最後にピリオド ( . ) を入力してからリターンキーを押します。1 + 2 * 3 の結果を見ると、値が 7 であることがわかります。負の数を表す場合、Erlang は普通の数式と同じく - を使います。

●整数と実数

Erlang の場合、数には「整数 (integer)」と「浮動小数点数 (float)」の 2 種類があります。整数は 10 進数で表しますが、先頭に n# を付けると n 進数で表すことができます。

> 16#abcd.
43981
> 8#777.
511
> 2#1010.
10

Erlang の場合、多倍長整数をサポートしているので整数の範囲に制限はありません。Erlang の実数は IEEE754 形式という倍精度浮動小数点数で表されていて、範囲は絶対値で約 1.0e-323 から 1.0e+308 までになります。

●算術演算子

ここで、よく使われる算術演算子をまとめておきましょう。

一般のプログラミング言語と同様に、四則演算は演算子 +, -, *, / を使います。ただし、Erlang で整数同士の割り算を行うと、結果は実数になることに注意してください。整数の割り算で商を求める場合は div を、剰余を求める場合は rem を使います。

簡単な例を示します。

> 1.234 + 5.678.
6.912
> 1.234 * 5.678.
7.006652
> 1.234 - 5.678.
-4.444
> 1.234 / 5.678.
0.2173300457907714
> 10 / 5.
2.0
> 1 / 2.
0.5
> 10 div 5.
2
> 1 div 2.
0
> 10 rem 5.
0
> 10 rem 3.
1

●アトム (atom)

アトムは名前 (識別子) を表すデータ型です。プログラミング言語の場合、名前は英数字や特定の記号 (アンダーラインや @ など) を並べたものですが、Erlang の場合は英小文字から始めます。英大文字から始める場合や、空白などの記号を含めたい場合はクオート ( ' ) で囲ってください。

> foo.
foo
> bar.
bar
> baz.
baz
> foo_bar.
foo_bar
> 'foo bar'.
'foo bar'

Erlang の場合、アトムの値は自分自身になります。Eshell 上で foo, bar, baz を入力すると、入力したアトムがそのまま返されます。Lisp / Scheme のシンボル、Prolog のアトムと同様に、Erlang のアトムは記号として使うことができます。

●変数とパターンマッチング

Erlang では、半角英大文字またはアンダーライン ( _ ) から始まる名前を「変数」として扱います。関数型言語では、変数に値を割り当てることを「束縛 (binding)」といいます。純粋な関数型言語の場合、束縛された変数は値を書き換えることができません。手続き型言語は代入により変数の値を書き換えることができますが、純粋な関数型言語に代入操作はありません。ちなみに、Lisp / Scheme は不純な関数型言語なので、変数の値を書き換えることができます。

Erlang の場合、変数の束縛は「パターンマッチング」により行われます。そして、一度束縛された変数は、その後で値を参照することはできても、値を書き換えることはできなくなります。束縛されていない変数のことを「未束縛変数」もしくは「自由変数」といいます。未束縛変数はどのようなデータにでもマッチングします。この動作は Prolog の変数にそっくりです。

簡単な例を示しましょう。

> X = 10.
10
> X.
10
> X + 10.
20
> Y = X.
10
> Y.
10
> X = 20.
** exception error: no match of right hand side value 20
> X = 10.
10
> A = B.
* 1: variable 'B' is unbound
> B = foo.
foo
> A = B.
foo
> A.
foo

演算子 = はパターンマッチングを行います。最初、X は未束縛変数なので、X = 10. はマッチングに成功して X の値は 10 になります。その後、X の値を参照して X + 10 を計算したり、X の値を使って未束縛変数 Y とマッチングさせることができます。この場合、Y の値は 10 になります。

ただし、X = 20. のように、X の値を書き換えることはできません。この場合、X の値 10 と整数値 20 を照合しますが、値が等しくないのでマッチングは失敗してエラーになります。また、X = 10. は X の値 10 と整数値 10 を照合します。この場合、値が等しいのでマッチングは成功となります。A と B 両方が未束縛変数の場合、A = B. のマッチングはエラー [*1] になります。B = foo. で B と foo をマッチングし、そのあとで A = B. とすればマッチングは成功します。

Erlang において、パターンマッチングの動作はとても重要です。マッチングの成否により、条件分岐と同じ動作を行わせることができます。これはあとで詳しく説明します。

Eshell でマッチングした変数を自由変数に戻したい場合は f(Var) を使います。引数を省略すると、すべての変数が自由変数になります。

> X.
10
> f(X).
ok
> X.
* 1: variable 'X' is unbound
> Y.
10
> A.
foo
> B.
foo
> f().
ok
> Y.
* 1: variable 'Y' is unbound
> A.
* 1: variable 'A' is unbound
> B.
* 1: variable 'B' is unbound
-- note --------
[*1] Prolog の場合、自由変数同士のマッチングは成功します。Erlang の場合、演算子 = の右辺値に未束縛変数が含まれているとエラーになります。

●比較演算子

数値の大小を比較する場合は、次の演算子を使います。

数値を比較する演算子
演算子条件
Expr1 > Expr2Expr1 が Expr2 より大きい
Expr1 < Expr2Expr1 が Expr2 より小さい
Expr1 >= Expr2Expr1 が Expr2 より大きいかまたは等しい
Expr1 =< Expr2Expr1 が Expr2 より小さいかまたは等しい
Expr1 == Expr2Expr1 が Expr2 と等しい
Expr1 /= Expr2Expr1 が Expr2 と等しくない
Expr1 =:= Expr2Expr1 が Expr2 と厳密に等しい
Expr1 =/= Expr2Expr1 が Expr2 と厳密に等しくない

比較演算子は条件を満たせば true を、そうでなければ false を返します。Erlang の場合、真偽値は true と false というアトムで表します。簡単な例を示しましょう。

> 1 < 2.
true
> 1 > 2.
false
> 1 =< 2.
true
> 1 >= 2.
false
> 1 == 1.
true
> 1 == 1.0.
true
> 1 =:= 1.0.
false
> 1 /= 1.
false
> 1 /= 1.0.
false
> 1 =/= 1.0.
true

== と =:=, /= と =/= の違いはデータ型をチェックするところです。たとえば、整数と実数を比較する場合、値が等しければ == は真を返しますが、=:= はデータ型が異なれば値が等しくても偽を返します。なお、比較演算子は整数や実数だけではなく、他のデータ型にも適用することができます。

●論理演算子

Erlang には not, and, andalso, or, orelse, xor という論理演算子があります。

Erlang の and と or は短絡演算子ではなく、右辺式と左辺式の両方を評価することに注意してください。andalso は短絡演算子で、左辺式が偽ならば右辺式を評価せずに偽を返します。orelse は短絡演算子で、左辺式が真ならば右辺式を評価せずに真を返します。

簡単な例を示します。

> true and true.
true
> true and false.
false
> false or true.
true
> false or false.
false
> true xor true.
false
> true xor false.
true
> not true.
false
> not false.
true

●条件分岐

条件分岐は if を使います。if の構文を下図に示します。

if
    条件式1 -> 式1a, 式1b, ..., 式1n;
    条件式2 -> 式2a, 式2b, ..., 式2n;
          ・・・・・
    条件式n -> 式na, 式nb, ..., 式nn
end

Erlang では、すべてのデータをまとめて「項 (term)」と呼びます。整数、実数、アトムは項になります。また、あとで説明する「リスト (list)」や「タプル (tuple)」も項になります。条件分岐は if ... end の中に "条件式 -> 式, ..., 式" を記述します。これを「節 (clause)」と呼びます。

Erlang の場合、「式」は通常の計算式だけではなく、if などの構文や項も「式」になります。Erlang の if は式なので値を返します。また、if 式を入れ子にすることもできます。

複数の節を指定する場合はセミコロン ( ; ) で区切ります。if は節の条件式を順番に評価していき、結果が真となった節を選択します。-> の右辺で指定した式が評価されます。それ以降の節は評価されません。-> の右辺で複数の式を評価したい場合はカンマ ( , ) で区切ってください。if は最後に評価した式の値を返します。

Lisp / Scheme ユーザーであれば、Erlang の if は cond と同じと考えればよいでしょう。また、Prolog ユーザーであれば、セミコロンの意味を「選言 (OR)」、カンマの意味を「連言 (AND)」と考えてください。Erlang の場合、if 以外でもセミコロンやカンマを使います。

なお、if の条件式は比較演算子や論理演算子などのほかに、Erlang の組み込み関数 (Built In Functions : BIF) の中の一部の関数しか指定することができません。Lisp / Scheme の cond と違って Erlang の if には制限があることに注意してください。詳細は Erlang のリファレンスマニュアル "8.25 Guard Sequences" をお読みください。

簡単な例を示しましょう。

> if 10 rem 2 == 0 -> even; true -> odd end.
even
> if 11 rem 2 == 0 -> even; true -> odd end.
odd

最初の例では、10 rem 2 == 0 が真になるので、その節の右辺を評価して even を返します。次の例では、11 rem 2 == 0 が偽になるので、次の節を評価します。この節の条件式は true なので無条件に成功します。右辺の式を評価して odd を返します。

●関数

Erlang の関数定義はとても簡単です。次の図を見てください。

関数名(仮引数, ,,,) -> 式, ..., 式.

        図 : 関数定義

関数名のあとのカッコで仮引数を指定します。そして、-> の右辺に評価する式を記述します。複数の式を記述する場合はカンマで区切ってください。最後にピリオド ( . ) を付けます。最後に評価した式の値が関数の返り値になります。

簡単な例を示しましょう。数値を 2 乗する関数 square は次のようになります。

リスト : 数の二乗

-module(test).
-export([square/1]).

% 引数を二乗する
square(X) -> X * X.

Erlang の関数は「モジュール (module)」の中で定義します。-module はモジュールを宣言する文です。モジュールについてはあとで詳しく説明します。この場合、モジュール名は test で、ファイル名はモジュールと同じ名前 (test.erl) でなければなりません。それから、Erlang は % から改行までが「コメント」になります。

-export は公開する関数名をリストで宣言します。リストはあとで詳しく説明します。関数名のあとに「引数の個数 (arity)」を指定します。関数 square の引数は X しかないので、square/1 とします。Erlang の場合、引数の個数が異なれば同名の関数をいくつでも定義することができます。square の定義は簡単ですね。X * X を計算して返すだけです。

それでは実行してみましょう。

> c(test).
{ok,test}
> test:square(10).
100
> test:square(11).
121

Eshell 上で関数を呼び出す場合は "モジュール名:関数名(実引数, ...)." とします。test:square(10). とすれば 10 * 10 = 100 を求めることができます。同じモジュール内にある関数を呼び出す場合は "モジュール名:" を省略することができます。

●関数とパターンマッチング

Erlang の場合、関数は次のように複数の節に分けて定義することができます。

関数名(仮引数, ,,,) -> 式, ..., 式;

        ・・・・・

関数名(仮引数, ,,,) -> 式, ..., 式.

複数の節をセミコロンで区切るところは if と同じです。この場合、Erlang は関数の仮引数と実引数との間でパターンマッチングを行います。そして、マッチングに成功した節を選択し、-> の右辺に記述された式を評価します。

簡単な例を示しましょう。モジュール test に関数 foo を追加します。

リスト : 関数のパターンマッチング

foo(0) -> zero;
foo(1) -> one;
foo(2) -> two;
foo(X) -> others.

foo(0) は最初の節とマッチングするので zero を返します。同様に、foo(1), foo(2) は one, two を返します。最後の節で、関数を評価する前の仮引数 X は未束縛変数です。0, 1, 2 以外の値は X とマッチングするので、最後の節が選択されて others を返します。

なお、最後の X はどこからも参照されていません。このような場合、次のように書き換えることができます。

foo(_) -> others.

アンダーラインだけの変数を「無名変数 (anonymous variable)」と呼び、プログラム上でその変数の値が不用のときに使われます。

簡単な実行例を示します。

> test:foo(0).
zero
> test:foo(1).
one
> test:foo(2).
two
> test:foo(3).
others
> test:foo(-3).
others
> test:foo(bar).
others

ところで、節を定義するときにはその順番に気を付けてください。たとえば、最初に foo(_) -> others; を定義すると、引数が 0, 1, 2 の場合でも foo(_) とマッチングするので、他の節を選択することができなくなります。特定的な節から定義するように注意してください。

●Erlang の変数は局所変数

関数の引数は「局所変数 (local variable)」として扱われます。局所変数は「有効範囲 (scope : スコープ)」が決まっています。引数の有効範囲は、関数が定義されている式の中だけです。また、-> の右辺で初めて出現する変数も局所変数として扱われます。つまり、Erlang には「大域変数 (global variable)」がないのです [*2]

たとえば、2 点間の距離を求める関数 distance/4 を作ってみましょう。モジュール test に次のリストを追加します。

リスト : 2 点間の距離を求める

distance(X1, Y1, X2, Y2) ->
    Dx = X1 - X2,
    Dy = Y1 - Y2,
    math:sqrt(Dx * Dx + Dy * Dy).

2 点の座標は X1, Y1 と X2, Y2 で表します。x 座標の差分を変数 Dx に、y 座標の差分を変数 Dy に求めます。Dx = X1 - X2 の Dx は初めて出現する変数なので、局所変数 (未束縛変数) になります。Dx と X1 - X2 のマッチングは成功します。Dy = Y1 - Y2 の Dy も同様です。

あとは、√(Dx * Dx + Dy * Dy) を計算するだけです。Erlang の場合、数学関数はモジュール math に定義されています。Eshell 上で m(モジュール名). を実行すると、モジュールに定義されている関数の一覧が得られます。

> m(math).
Module: math
MD5: e590ec1ddb56403fce047355eb252842
Compiled: No compile time info available
Object file: c:/Program Files/erl10.1/lib/stdlib-3.6/ebin/math.beam
Compiler options:  [debug_info, ... 省略 ...]

Exports:
acos/1                        floor/1
acosh/1                       fmod/2
asin/1                        log/1
asinh/1                       log10/1
atan/1                        log2/1
atan2/2                       module_info/0
atanh/1                       module_info/1
ceil/1                        pi/0
cos/1                         pow/2
cosh/1                        sin/1
erf/1                         sinh/1
erfc/1                        sqrt/1
exp/1                         tan/1
                              tanh/1
ok

簡単な実行例を示しましょう。

> test:distance(0, 0, 2, 2).
2.8284271247461903
> test:distance(0, 0, 10, 10).
14.142135623730951
-- note --------
[*2] 大域変数の代わりになるデータ構造 (プロセス辞書) はありますが、できるだけ使わないほうが良いでしょう。

●タプル (tuple)

タプルは複数の項を格納したデータ構造です。タプルは複数の項をカンマ ( , ) で区切り、中カッコ { } で囲んで表します。次の例を見てください。

> A = {1, 2}.
{1,2}
> B = {10, 20.5}.
{10,20.5}
> C = {1, 2.5, foo}.
{1,2.5,foo}
> D = {1 + 2, 3 * 4}.
{3,12}

このように、タプルにはデータ型が異なる要素を格納することができます。また、最後の例のようにカッコの中に式を書くと、それを評価した値がタプルの要素になります。

タプルは入れ子にしてもかまいません。簡単な例を示します。

> E = {{1, 2}, {3, 4}}.
{{1,2},{3,4}}
> F = {1, {2, {3}, 4}, 5}.
{1,{2,{3},4},5}

タプルから要素を取り出すには、パターンマッチングを使うと簡単です。

> {X, Y} = A.
{1,2}
> X.
1
> Y.
2
> {X0, Y0} = E.
{{1,2},{3,4}}
> X0.
{1,2}
> Y0.
{3,4}
> {{X1, Y1}, {X2, Y2}} = E.
{{1,2},{3,4}}
> X1.
1
> Y1.
2
> X2.
3
> Y2.
4

パターン {X, Y} と左辺 A の値 {1, 2} を照合して、変数部分に対応する要素を取り出します。そして、変数をその値に束縛します。次の例のように、{X0, Y0} と {{1, 2}, {3, 4}} を照合すると、X0 は {1, 2} になり、Y0 は {3, 4} になります。

パターンは入れ子にしてもかまいません。{{X1, Y1}, {X2, Y2}} と {{1, 2}, {3, 4}} を照合すると、X1 = 1, Y1 = 2, X2 = 3, Y2 = 4 となります。このように、パターンを使ってタプルの要素を取り出すことができます。

ただし、マッチングに失敗した場合は次のようにエラーになります。

> {{Z0}, Z1} = {1,2}.
** exception error: no match of right hand side value {1,2}
> {{Z0}, Z1} = {{1},2}.
{{1},2}
> Z0.
1
> Z1.
2

簡単な例を示しましょう。座標をタプル {x, y} で表すことにすると、関数 distance/2 は次のようになります。

リスト : 2 点間の距離を求める (2)

distance({X1, Y1}, {X2, Y2}) ->
    Dx = X1 - X2,
    Dy = Y1 - Y2,
    math:sqrt(Dx * Dx + Dy * Dy).

この場合、distance の arity は 2 になります。そして、パターンマッチングでタプルから座標を取り出して距離を計算します。

簡単な実行例を示します。

> test:distance({0,0}, {2,2}).
2.8284271247461903
> X = {0,0}.
{0,0}
> Y = {10,10}.
{10,10}
> test:distance(X, Y).
14.142135623730951

また、Erlang のパターンマッチングでは、左辺式で同じ名前の変数を使うことができます。次の例を見てください。

> {Y, Y} = {2, 3}.
** exception error: no match of right hand side value {2,3}
> {X, X} = {2, 2}.
{2,2}
> X.
2

{Y, Y} と {2, 3} をマッチングする場合、Y は未束縛変数なので、最初に Y と 2 を照合して Y の値は 2 になります。次に、Y と 3 を照合しますが、Y は未束縛変数ではなく 2 に束縛されています。この値を使ってパターンマッチングを行うため、2 と 3 を照合することになり、マッチングは失敗します。{X, X} と {2, 2} をマッチングする場合は、最初に X と 2 を照合して X の値が 2 になり、次に 2 に束縛された X と 2 を照合するので、マッチングは成功します。

●リスト (list)

「リスト (list)」は複数の項を一列に並べたデータ構造です。Erlang で扱うリストは、Lisp / Scheme や Prolog のリストと同じです。リストの構造を図で表すと次のようになります。

┌─┬─┐    ┌─┬─┐    ┌─┬─┐
│・│・┼─→│・│・┼─→│・│・┼─→ [] (空リスト)
└┼┴─┘    └┼┴─┘    └┼┴─┘
  ↓            ↓            ↓
  1            2            3

        図 : リスト内部の構造

リストは貨物列車にたとえるとわかりやすいでしょう。車両に相当するものを「コンスセル (cons cell)」といいます。貨物列車には多数の車両が接続されて運行されるように、リストは複数のコンスセルを接続して構成されます。1 つのコンスセルには、貨物(データ)を格納する場所と、連結器に相当する場所があります。

上図では、コンスセルを箱で表しています。コンスセルの左側がデータを格納する場所で、右側が次のコンスセルと連結しています。この例では、3 つのコンスセルが接続されています。それから、最後尾のコンスセルには、リストの終わりを示す特別なデータが格納されます。要素が一つもないリストを「空リスト」といって、Erlang では [ ] で表します。一般に、リストの終端は空リストで表しますが、リスト以外のデータであれば何でもかまいません。

Erlang では、リストの両側を [ と ] で囲んで表し、項はカンマ ( , ) で区切ります。簡単な例を示しましょう。

[spring, summer, autumn, winter]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[X, Y, Z]
[[a, b, c], [d, e, f], [g, h, i]]

最初の例では、四季のデータを格納しています。次の例では数値を格納しています。そして、変数を格納することもできますし、リストの中にリストを格納することもできます。このように、リストに格納する要素に制限はありません。100 個でも 1000 個でも、Erlang システムが許容する範囲内であればいくつでも格納することができます。

リストの長所は要素の追加や削除が簡単にできるところです。Erlang の場合、パターンマッチングを使って柔軟にリストを操作することができます。その動作は Prolog のリストにそっくりです。次の例を見てください。

> [A, B, C, D] = [spring, summer, autumn, winter].
[spring,summer,autumn,winter]
> A.
spring
> B.
summer
> C.
autumn
> D.
winter

このように、リスト同士のマッチングは先頭の要素から順番に行います。

パターンマッチングを使って、リストを分解することができます。次の例を見てください。

> [X | Y] = [spring, summer, autumn, winter].
[spring,summer,autumn,winter]
> X.
spring
> Y.
[summer,autumn,winter]

X と Y の間にある "|" に意味があります。| でリストを区切ると、それより後ろの変数は「残りのリストすべて」とマッチングします。この場合、X が先頭の要素とマッチングし、spring を取り除いた残りのリストと Y がマッチングします。もう少し例を見てみましょう。

> [X1, Y1 | Z1] = [spring, summer, autumn, winter].
[spring,summer,autumn,winter]
> X1.
spring
> Y1.
summer
> Z1.
[autumn,winter]

この例では、X1 が spring、Y1 が summer にマッチングし、残りのリストと Z1 がマッチングします。このように、| の前にはいくつでも変数を置くことができますが、| の後ろには変数をひとつしか置けません。「残りのリストすべて」とマッチングするのですから、複数の変数を書いても意味がありません。また、| を同じリストに複数書くこともできません。次の例はすべてエラーになります。

[X | Y, Z]     % | の後ろに変数が複数ある
[X | Y | Z]    % | が複数ある
[ | X]         % | の前にデータがない
[X | ]         % | の後ろにデータがない

Lisp / Scheme ユーザーであれば、[X | Y] がドットリスト (X . Y) と同じであることに気づかれたことでしょう。Lisp / Scheme では、関数 car でリストの先頭の要素を、関数 cdr で先頭要素を取り除いた残りのリストを求めることができます。Erlang の場合はパターンマッチングを行うことで、リストを分解することができます。

●文字と文字列

Erlang の場合、文字を表すデータ型は用意されていません。文字は整数で表します。文字の前に $ を付けると、その文字のアスキーコードに変換されます。

> $a.
97
> $b.
98
> $c.
99

文字列 (string) は "foo" や "bar" のように二重引用符 ( " ) で囲みます。これを「文字列リスト」といいます。Erlang の場合、文字列は文字を表すアスキーコード (ASCII CODE) のリストと同じです。

> [C1, C2, C3] = "abc".
"abc"
> C1.
97
> C2.
98
> C3.
99
> [$a, $b, $c].
"abc"

UTF-8 が扱える端末であれば日本語を扱うこともできます。

$ erl
Erlang/OTP 24 ... 略 ...

Eshell V12.2.1  (abort with ^G)
1> "あいうえお".
[12354,12356,12358,12360,12362]
2> $あ.
12354
3> halt().

$ erl +pc unicode
Erlang/OTP 24 ... 略 ...

Eshell V12.2.1  (abort with ^G)
1> "あいうえお".
"あいうえお"
2>  $あ.
12354

Eshell の起動時にオプション +pc unicode を指定すると、UTF-8 の文字リストを日本語で表示することができます。Windows の場合、以前のバージョンではコマンドプロンプトの文字コードを UTF-8 に切り替えても、上記のような動作にはなりませんでした。werl を使うと上記と同じ動作になります。現バージョンでの動作は未確認です。ご注意くださいませ。


初出 2011 年 10 月 2 日
改訂 2018 年 12 月 9 日, 2024 年 11 月 1 日