M12i.

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

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

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

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

2.6 標準モジュールとマニュアルページ

Erlangはあなたのプログラミングを支援する標準モジュールをたくさん取りそろえています。例えば、IOモジュールはあなたが形式に則った入力/出力を行うのを助ける種々の関数を含んでいます。標準モジュールに関する情報を調べるには、erl -manコマンドをOSシェル〔ターミナル〕やコマンドプロンプト(ようするにあなたが最前Erlangシェルを起動した環境)で実行します。ではOSのシェルで試しに実行してみましょう:

% erl -man io
ERLANG MODULE DEFINITION                                    io(3)
MODULE
     io - Standard I/O Server Interface Functions
DESCRIPTION
     This module provides an  interface  to  standard  Erlang  IO
     servers. The output functions all return ok if they are suc-
     ...

もしあなたのシステムで動作しなかったら、Erlang/OTPの配布ファイルにHTML版のドキュメントが含まれていますし、Webサイトwww.erlang.se(商用Erlang)やwww.erlang.orgオープンソースErlang)でもHTMLもしくはPDFの形式でドキュメントを入手できます。例えばリリースR9Bであれば:

http://www.erlang.org/doc/r9b/doc/index.html

2.7ターミナルに出力する

サンプルコードのなかで整形した出力ができると便利ですね。次の例では、io:furmat関数をシェル上で使用する方法について解説しています:

31> io:format("hello world~n", []).
hello world
ok
32> io:format("this outputs one Erlang term: ~w~n", [hello]).
this outputs one Erlang term: hello
ok
33> io:format("this outputs two Erlang terms: ~w~w~n", [hello, world]).
this outputs two Erlang terms: helloworld
ok
34> io:format("this outputs two Erlang terms: ~w ~w~n", [hello, world]).
this outputs two Erlang terms: hello world
ok

format/2(すなわち引数を2つとるformat関数)は、2つのリストをとります。1つめのそれはほとんどいつも””(ダブルクオテーション)によって表記されます。この1つめのリストのうち、それぞれの“~w”には2つめのリストの要素が順番に置換されます。またそれぞれの“~n”は改行復帰〔new line〕に置換されます。io:format/2関数は処理が問題なく終了した場合、アトムokを返します。Erlangにおける他の関数と同様に、もし何らかのエラーが起きればクラッシュします。Erlangにおいてこれは障害/不具合〔fault〕ではありません。熟慮されたポリシーに則ったものなのです。後に見るようにErlangにはエラー・ハンドリングに関して洗練されたメカニズムが備わっています。学習のため、io:formatを実際にクラッシュさせてみてください。そんなに難しくありません。とまれ、io:formatがクラッシュしても、Erlangシェル自体がクラッシュすることはない点に注意してください。

2.8 より大きなプログラムの例

それでは、わたしたちがここまで学んできた事項を集約した、より大きなプログラムの例を見てみましょう。世界中の多くの都市から気温のリストを得たとします。それらのうちいくつかがセルシウス(セ氏温度)であり、またいくつかがカ氏温度です(前掲のサンプルのように)。まずそれらすべてをセルシウスに変換し、その後きれいに出力してみましょう。

%% このモジュールのファイル名はtut5.erl 
-module(tut5).
-export([format_temps/1]).
%%この関数だけがモジュール外に公開されます
format_temps([])->                        %空のリストだったら何も出力しません
    ok;
format_temps([City | Rest]) ->
    print_temp(convert_to_celsius(City)),
    format_temps(Rest).
convert_to_celsius({Name, {c, Temp}}) ->  % 変換の必要がありません
    {Name, {c, Temp}};
convert_to_celsius({Name, {f, Temp}}) ->  % カ氏温度からセ氏温度に変換します
    {Name, {c, (Temp - 32) * 5 / 9}}.
print_temp({Name, {c, Temp}}) ->
    io:format("~-15w ~w c~n", [Name, Temp]).
35> c(tut5).
{ok,tut5}
36> tut5:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
moscow          -10 c
cape_town       21.11111111111111 c
stockholm       -4 c
paris           -2.2222222222222223 c
london          2.2222222222222223 c
ok

このプログラムがどのように動くかを調べる前に、コード内に追加されたいくつかのコメントに注意してください。コメントは%により始まり行の終わりまで続きます。さて、-export([format_temps/1]).の行を見ると、format_temps/1しか含まれていません。その他の関数はローカル関数、すなわちtut5モジュールの外側からは見ることができない関数です。

シェル上でこのプログラムを実行するときには、引数のリストが長すぎるため、2行にわたって記述しなくてはなりません。

最初にformat_tempsを呼び出したとき、変数Cityは値として{moscow,{c,-10}}を得、変数Restはリストの残りの部分を得ます。そしてprint_temp(convert_to_celsius({moscow,{c,-10}}))が呼び出されます。

ここで、print_temp関数の引数として与えられたconvert_to_celsius({moscow,{c,-10}})という関数呼び出しについて見てみましょう。入れ子の関数呼び出しは、内側から外側へと実行(評価)されていきます。すなわち最初にconvert_to_celsius({moscow,{c,-10}})が評価されて、結果、{moscow,{c,-10}}──もともとセルシウスでした──が返されます。そしてprint_temp({moscow,{c,-10}})が評価されます。

print_temp関数は以前説明したのと同じ形式でio:format関数を呼んでいます。〔第1引数の文字列のなかの〕~-15wは、〔第2引数でリストの要素として指定する〕Erlang表現〔the “term”〕を15文字の長さの左詰めで出力するように、と指定するものです。(io(3))

さて、format_temps(Rest)はリストの残りの部分を引数として呼び出されます。これは他の言語におけるループ構造に似ていますね?(そうです、これは再帰です。でも心配しないでください。)同じformat_tempsがまた呼び出されました。今度は変数City{cape_town,{f,70}}を得ます。そして最前と同じ処理が繰り返されます。この繰り返しはリストが空に──つまりに──なるまで続きます。このとき関数の最初の節format_temps()がマッチするからです。この節は単純に返値としてアトムokを返します。そしてプログラムは終了します。

2.9 マッチング、ガード条件、そして変数のスコープ

先ほど取り上げたようなリストから、最大値と最小値を見つけ出せたら便利です。これを行うべくプログラムを拡張する前に、リストの要素の中から最大値を見つけ出す関数について見てみましょう:

-module(tut6).
-export([list_max/1]).
list_max([Head|Rest]) ->
   list_max(Rest, Head).
list_max([], Res) ->
    Res;
list_max([Head|Rest], Result_so_far) when Head > Result_so_far ->
    list_max(Rest, Head);
list_max([Head|Rest], Result_so_far)  ->
    list_max(Rest, Result_so_far).
37> c(tut6).
{ok,tut6}
38> tut6:list_max([1,2,3,4,5,7,4,3,2,1]).
7

まずはじめに同じ名前の2つの関数があることに注意してください。それぞれは異なる数の引数(パラメータ)をとります。Erlangではこうした関数はまったく異なる別々の関数として見なされます。関数同士を区別したい場合name/arity──nameは関数の名前であり、arityは引数の数です──の書式で区別します。今回の例で言えば、list_max/1list_max/2です。

これはリストの要素を順に追いながら、何らかの値を“運搬”していく処理の例で、運搬役はResult_so_far です。list_max/1は単純にリストの先頭の要素が最大値であると仮定しています。そしてlist_max/2をリストの残りの部分と、リストの先頭の値とを引数にして呼び出しています。上の例では、list_max([2,3,4,5,7,4,3,2,1],1)となるでしょう。もしlist_max/1に空のリストや、そもそもリストでないものを渡したら、エラーが発生します。Erlangの哲学ではこの手のエラーのハンドリングは問題の関数内では行わず、ほかの場所で行います。この点については後ほどより詳しく述べます。

list_max/2ではリストの要素をしらべ、もし〔リストの先頭の値である〕Head > 〔目下のところの最大値〕Result_so_farの関係が成り立つ場合、Headを用います。whenは関数の->の前で使用する特殊なワードで、続く条件式が成り立つときのみこの関数の部分〔節〕を使用すると宣言するものです。この種の検証をガード〔guard〕と呼びます。もしガードが正でなければ(これをガード・フェイル〔guard fails〕といいます)、関数の次の部分〔節〕の使用が試みられます。今回の例では、もしHeadResult_so_farよりも大きくなければ──つまりより小さいか等しいということですが──、〔ガード・フェイルとなり〕次の節ではガードが必要ありません〔条件なしにこの節が実行されます〕。
ガードの中で使用できる便利な演算子としては──<小なり、>大なり、==イコール、>=大なりイコール、=<小なりイコール、/=イコールでない──があります。(『Erlangリファレンス・マニュアル』の“ガード・シーケンス”の章を参照)。

上述のプログラムをリストの中の最小値を見つけ出すものに変更するには、〔ガードの中の〕>を<に書き換えるだけですみます。(もちろん関数の名前もlist_minと変更した方が賢明ですけどね :-)

わたしが最前、1つの変数にはそのスコープ内で一度きりしか値を割り当てることはできないと述べたことを思い出してください。ここまで見てきたとおり、例えば、変数Result_so_farはいくつもの値を割り当てられていました。これは問題ありません。list_max/2が呼び出されるたびに、新しいスコープが生成され、それぞれのスコープ内にある変数Result_so_farは互いに何の関係もない変数と見なされたからです。

変数を生成し値を割り当てる別の方法は、マッチ演算子=〔match operator〕を使用するものです。もしM = 5と書いたら、Mという名前の変数が生成され5という値が割り当てられます。もし同じスコープ内でM = 6と書いたら、エラーが起きます。Erlangシェルで実際にやってみましょう:

39> M = 5.
5
40> M = 6.
** exception error: no match of right hand side value 6
41> M = M + 1.
** exception error: no match of right hand side value 6
42> N = M + 1.
6

マッチ演算子Erlang表現〔Erlang terms〕の中から一部分を抜き出して新しい値を生成するのにとくに便利です。

43> {X, Y} = {paris, {f, 28}}.
{paris,{f,28}}
44> X.
paris
45> Y.
{f,28}

ここでは変数Xはアトムparisを得、変数Yはタプル{f,28}を得ました。

もちろん上の手続きのあと、別の都市に対して同じことを〔同じ変数名を用いて〕行おうとすれば、エラーが起きます:

46> {X, Y} = {london, {f, 36}}.
** exception error: no match of right hand side value {london,{f,36}}

変数はまたプログラムの可読性を高めるためにも使用できます。例えば、list_max/2関数では、次のように記述することもできます:

list_max([Head|Rest], Result_so_far) when Head > Result_so_far ->
    New_result_far = Head,
    list_max(Rest, New_result_far);

すこしだけ明快になったのではないでしょうか。

2.10リストについてもう少し補足

|演算子がリストの先頭の要素を取得するのに使用できたことを思い出してください:

47> [M1|T1] = [paris, london, rome].
[paris,london,rome]
48> M1.
paris
49> T1.
[london,rome]

この|演算子はリストの先頭に要素を加える〔加えたリストを返す〕のにも使用できます:

50> L1 = [madrid | T1].
[madrid,london,rome]
51> L1.
[madrid,london,rome]

今度のリストを使用した処理の例は、リストの要素順序を逆さにするものです:

-module(tut8).
-export([reverse/1]).
reverse(List) ->
    reverse(List, []).
reverse([Head | Rest], Reversed_List) ->
    reverse(Rest, [Head | Reversed_List]);
reverse([], Reversed_List) ->
    Reversed_List.
52> c(tut8).
{ok,tut8}
53> tut8:reverse([1,2,3]).
[3,2,1]

Reversed_Listがどのように組み立てられるか考えてみてください。はじめは[]でした。反転させたいリストの先頭から要素を1つ取り出しては、Reversed_Listに追加していきます。次のような具合に:

reverse([1|2,3], []) =>
    reverse([2,3], [1|[]])
reverse([2|3], [1]) =>
    reverse([3], [2|[1])
reverse([3|[]], [2,1]) =>
    reverse([], [3|[2,1]])
reverse([], [3,2,1]) =>
    [3,2,1]

listsモジュールは、リストを操作するための多くの関数を備えています。例えばこのリストを反転させる関数のように。したがって、あなた自身でリストを操作する関数を書く前に、それがすでに存在していないか確認してみるのがよいでしょう。(lists(3)も参照)

さて、都市と気温の例に立ち戻ってみましょう。けれども今回はより構造化されたアプローチで臨みます。まずはリスト内のすべての要素をセルシウスに変換してみましょう。次のような関数になります。試してみましょう:〔訳者:convert_list_to_c関数の節で、構造化された引数パターンマッチが行われていることに注意してください。〕

-module(tut7).
-export([format_temps/1]).
format_temps(List_of_cities) ->
    convert_list_to_c(List_of_cities).
convert_list_to_c([{Name, {f, F}} | Rest]) ->
    Converted_City = {Name, {c, (F -32)* 5 / 9}},
    [Converted_City | convert_list_to_c(Rest)];
convert_list_to_c([City | Rest]) ->
    [City | convert_list_to_c(Rest)];
convert_list_to_c([]) ->
    [].
54> c(tut7).
{ok, tut7}.
55> tut7:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
[{moscow,{c,-10}},
 {cape_town,{c,21.11111111111111}},
 {stockholm,{c,-4}},
 {paris,{c,-2.2222222222222223}},
 {london,{c,2.2222222222222223}}]

一つひとつ見ていきましょう:

format_temps(List_of_cities) ->
    convert_list_to_c(List_of_cities).

format_temps/1関数がconvert_list_to_c/1関数を呼び出しています。convert_list_to_c/1関数はList_of_citiesの先頭の要素を取り出し、もし必要であればセルシウスに変換します。|演算子は(おそらく)変換済みの気温データを、変換済みリストの先頭に加えます:

[Converted_City | convert_list_to_c(Rest)];

もしくは、

[City | convert_list_to_c(Rest)];

リストの終端(すなわち空リスト)に到達するまでこんな具合でやっていきます:

convert_list_to_c([]) ->
    [].

変換済みのリストは手に入れました。次はこれを出力する関数を加えます:

-module(tut7).
-export([format_temps/1]).
format_temps(List_of_cities) ->
    Converted_List = convert_list_to_c(List_of_cities),
    print_temp(Converted_List).
convert_list_to_c([{Name, {f, F}} | Rest]) ->
    Converted_City = {Name, {c, (F -32)* 5 / 9}},
    [Converted_City | convert_list_to_c(Rest)];
convert_list_to_c([City | Rest]) ->
    [City | convert_list_to_c(Rest)];
convert_list_to_c([]) ->
    [].
print_temp([{Name, {c, Temp}} | Rest]) ->
    io:format("~-15w ~w c~n", [Name, Temp]),
    print_temp(Rest);
print_temp([]) ->
    ok.
56> c(tut7).
{ok,tut7}
57> tut7:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
moscow          -10 c
cape_town       21.11111111111111 c
stockholm       -4 c
paris           -2.2222222222222223 c
london          2.2222222222222223 c
ok

次に最大値と最小値のそれぞれの都市を見つけ出す関数を付け加えないといけないとします。下に示すプログラムは、都市のリストを4回もたどり直すもので、目的を果たすための効率的なプログラムとはいえないものです。しかしまずは明快性と正確性を実現することにつとめ、〔しかるのちに〕本当に必要な場合にはプログラムを最適化しましょう:

-module(tut7).
-export([format_temps/1]).
format_temps(List_of_cities) ->
    Converted_List = convert_list_to_c(List_of_cities),
    print_temp(Converted_List),
    {Max_city, Min_city} = find_max_and_min(Converted_List),
    print_max_and_min(Max_city, Min_city).
convert_list_to_c([{Name, {f, Temp}} | Rest]) ->
    Converted_City = {Name, {c, (Temp -32)* 5 / 9}},
    [Converted_City | convert_list_to_c(Rest)];
convert_list_to_c([City | Rest]) ->
    [City | convert_list_to_c(Rest)];
convert_list_to_c([]) ->
    [].
print_temp([{Name, {c, Temp}} | Rest]) ->
    io:format("~-15w ~w c~n", [Name, Temp]),
    print_temp(Rest);
print_temp([]) ->
    ok.
find_max_and_min([City | Rest]) ->
    find_max_and_min(Rest, City, City).
find_max_and_min([{Name, {c, Temp}} | Rest], 
         {Max_Name, {c, Max_Temp}}, 
         {Min_Name, {c, Min_Temp}}) ->
    if 
        Temp > Max_Temp ->
            Max_City = {Name, {c, Temp}};           % Change
        true -> 
            Max_City = {Max_Name, {c, Max_Temp}} % Unchanged
    end,
    if
         Temp < Min_Temp ->
            Min_City = {Name, {c, Temp}};           % Change
        true -> 
            Min_City = {Min_Name, {c, Min_Temp}} % Unchanged
    end,
    find_max_and_min(Rest, Max_City, Min_City);
find_max_and_min([], Max_City, Min_City) ->
    {Max_City, Min_City}.
print_max_and_min({Max_name, {c, Max_temp}}, {Min_name, {c, Min_temp}}) ->
    io:format("Max temperature was ~w c in ~w~n", [Max_temp, Max_name]),
    io:format("Min temperature was ~w c in ~w~n", [Min_temp, Min_name]).
58> c(tut7).
{ok, tut7}
59> tut7:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
moscow          -10 c
cape_town       21.11111111111111 c
stockholm       -4 c
paris           -2.2222222222222223 c
london          2.2222222222222223 c
Max temperature was 21.11111111111111 c in cape_town
Min temperature was -10 c in moscow
ok
2.11 ifとcase

関数find_max_and_minは気温の最大値と最小値を取り出すものでしたが、その中でif構造を新たに導入しています。ifは次のようにはたらきます:

if
    Condition 1 ->
        Action 1;
    Condition 2 ->
        Action 2;
    Condition 3 ->
        Action 3;
    Condition 4 ->
        Action 4
end

最後の節には“;”がないことに注意してください。Conditionの部分はガードと同様です。成功もしくは失敗を検証します。Erlangは〔if構造の〕先頭から検証をはじめ、成功する条件を見つけると当該のCondition部分につづくAction部分を評価(遂行)します。その後、if構造の終わりまでの間にある他のすべてのConditionAction部分は無視されます。もしどの条件も適合しなければ、ランタイム・フェイラ〔run-time failure〕が発生します。どんなときでも成功となる条件として、アトムtrueがあります。trueはしばしばif構造の最後に置かれ、他のすべての条件が失敗した場合に実行すべき処理を担います。

次に、ifのはたらきを示す小さなプログラムをお見せします。

-module(tut9).
-export([test_if/2]).
test_if(A, B) ->
    if 
        A == 5 ->
            io:format("A == 5~n", []),
            a_equals_5;
        B == 6 ->
            io:format("B == 6~n", []),
            b_equals_6;
        A == 2, B == 3 ->                      %すなわち A イコール 2 かつB イコール 3
            io:format("A == 2, B == 3~n", []),
            a_equals_2_b_equals_3;
        A == 1 ; B == 7 ->                     %すなわち A イコール 1 または B イコール 7
            io:format("A == 1 ; B == 7~n", []),
            a_equals_1_or_b_equals_7
    end.

このプログラムを試してみましょう:

60> c(tut9).
{ok,tut9}
61> tut9:test_if(5,33).
A == 5
a_equals_5
62> tut9:test_if(33,6).
B == 6
b_equals_6
63> tut9:test_if(2, 3).
A == 2, B == 3
a_equals_2_b_equals_3
64> tut9:test_if(1, 33).
A == 1 ; B == 7
a_equals_1_or_b_equals_7
65> tut9:test_if(33, 7).
A == 1 ; B == 7
a_equals_1_or_b_equals_7
66> tut9:test_if(33, 33).
** exception error: no true branch found when evaluating an if expression
     in function  tut9:test_if/2

tut9:test_if(33,33)という呼び出しに注意してください。どの条件節も成功とならなかったため、ランタイム・エラーif_clauseが発生しました。ここでもErlangシェルによってエラー情報はきれいに整形されて表示されています。多くのガード条件検証についての詳細が『Erlangリファレンス・マニュアル』の“ガード・シーケンス”の章に載っています。caseErlangにおけるもう1つの構造です。私たちが以前作成したconvert_length関数を思い起こしてみましょう:

convert_length({centimeter, X}) ->
    {inch, X / 2.54};
convert_length({inch, Y}) ->
    {centimeter, Y * 2.54}.

同じプログラムを次のように記述することもできます:

-module(tut10).
-export([convert_length/1]).
convert_length(Length) ->
    case Length of
        {centimeter, X} ->
            {inch, X / 2.54};
        {inch, Y} ->
            {centimeter, Y * 2.54}
    end.
67> c(tut10).
{ok,tut10}
68> tut10:convert_length({inch, 6}).
{centimeter,15.24}
69> tut10:convert_length({centimeter, 2.5}).
{inch,0.984251968503937}

caseにしろifにしろ返値が存在することに気をつけてください。すなわち上記の例であれば、case構造は{inch,X/2.54}もしくは{centimeter,Y*2.54}という値を返します。caseの振る舞いはガードの使用によって変更することもできます。例というものは明快であることが望まれます。次に示す例は与えられた年の、ある月の長さを教えてくれるものです。〔訳者:この例で示されているtrunc関数は次の節で説明があるとおり、小数点以下の切り捨てを行う関数です。なお例の中に出てくるleapは閏年のことです。〕

-module(tut11).
-export([month_length/2]).
month_length(Year, Month) ->
    %% 400で割り切れる〔整除できる〕年はすべて閏年
    %% 100で割り切れる年は閏年ではない(上記400ルールは除く)
    %% 4で割り切れる年は閏年(上記100ルールは除く)
    Leap = if
        trunc(Year / 400) * 400 == Year ->
            leap;
        trunc(Year / 100) * 100 == Year ->
            not_leap;
        trunc(Year / 4) * 4 == Year ->
            leap;
        true ->
            not_leap
    end,  
    case Month of
        sep -> 30;
        apr -> 30;
        jun -> 30;
        nov -> 30;
        feb when Leap == leap -> 29;
        feb -> 28;
        jan -> 31;
        mar -> 31;
        may -> 31;
        jul -> 31;
        aug -> 31;
        oct -> 31;
        dec -> 31
    end.
70> c(tut11).
{ok,tut11}
71> tut11:month_length(2004, feb).
29
72> tut11:month_length(2003, feb).
28
73> tut11:month_length(1947, aug).
31
2.12 組み込みの関数(BIFs)

組み込みの関数BIFs〔built in functions〕は、いくつかの理由からErlang仮想マシンに〔あらかじめ〕組み込まれている関数たちです。BIFsはしばしば、Erlang言語では実装不可能な機能やErlang言語では効率的な実装ができない機能を提供します。いくつかのBIFsは関数名だけを使って呼び出すことができますが、それらはデフォルトでerlangモジュールに属しています。例えば次に見るBIF関数truncは、erlang:truncと同義です。

〔前節の最後の例では〕ご覧のように、ある年が閏年であるかそうでないかを調べています。もし年数が400で割り切れれば〔整除できれば〕、それは閏年です。このためわれわれはまず年数を400で割り、組み込み関数trunc(後述)で小数を切り捨てています。しかる後、再度400で乗じて元と同じ値を得られるかどうか確認します。例えば2004年です:〔訳者:もちろんこの一連の処理の中で、400で割り切れない整数は、もとの数値に戻すことができません〕

2004 / 400 = 5.01
trunc(5.01) = 5
5 * 400 = 2000

結果は2000でした。2004とは同じ値になりませんでした。2004は400では割り切れない〔整除できない〕わけです。2000年ならばどうでしょう:

2000 / 400 = 5.0
trunc(5.0) = 5
5 * 400 = 2000

今度は閏年でした。その年が100もしくは4で割り切れるかという引き続く2つのテストも、同じ方法で実施します。最初のif構造は結果としてアトムleapもしくはnot_leapを返しLeapに代入します。この変数は続くcase構造──月の長さを返す──の中のfebガードで使用されます。

trunc関数を用いた上記の例は、Erlang演算子remを使用することでより簡単に実現できます。remは剰余を返します。例えば:

74> 2004 rem 400.
4

したがってこれまでは次のように書いてきましたが:

trunc(Year / 400) * 400 == Year ->
    leap;

今後は次のように書けます:

Year rem 400 == 0 ->
    leap;

truncのような組み込み関数(BIF)はほかにもたくさんあります。いくつかの関数だけがガード条件の中で使用できます。そしてあなた自身が定義した関数はガード内では使用できません。(『Erlangリファレンス・マニュアル』の“ガード・シーケンス”の章を参照。)(上級読者のための余談:〔上記の制約は〕ガードが副作用を持たないことを保証するためです。)では、Erlangシェルでこれらの関数のいくつかを実際に使ってみましょう。

75> trunc(5.6).
5
76> round(5.6).
6
77> length([a,b,c,d]).
4
78> float(5).
5.0
79> is_atom(hello).
true
80> is_atom("hello").
false
81> is_tuple({paris, {c, 30}}).
true
82> is_tuple([paris, {c, 30}]).
false

上に示した関数はすべてガードで使用できます。次にガード内で使用できないものもあげておきましょう:

83> atom_to_list(hello).
"hello"
84> list_to_atom("goodbye").
goodbye
85> integer_to_list(22).
"22"

この3つのBIFsはErlang言語としては実現困難(もしくは不可能)な変換の機能を提供しています。

2.13高階関数(Funs)

Erlangは多くのモダンな関数型言語と同様に、高階関数の機能を持っています。シェルを使って例を見てみましょう:

86> Xf = fun(X) -> X * 2 end.
#Fun<erl_eval.5.123085357>
87> Xf(5).
10

何をやっているのかというと、まず数値を倍する関数を定義して、それを変数に割り当てているのです。したがってXf(5)10を返します。リストとともに使用する便利な関数にforeachmapがありますが、これらは次のように定義されています:

foreach(Fun, [First|Rest]) ->
    Fun(First),
    foreach(Fun, Rest);
foreach(Fun, []) ->
    ok.
map(Fun, [First|Rest]) -> 
    [Fun(First)|map(Fun,Rest)];
map(Fun, []) -> 
    [].

これらの関数は標準モジュールlistsの中で提供されています。foreachはリストを引数にとり、その要素のいちいちに対して任意の関数を適用していきます。またmapはリストの要素のいちいちに対して任意の関数を適用した結果からなる新しいリストを生成します。Erlangシェルに戻りましょう。map関数と、リストの要素のそれぞれに3を足すためのfun〔fun1(…)-> … end.という書式で動的に定義された関数〕を使用してみましょう:

88> Add_3 = fun(X) -> X + 3 end.
#Fun<erl_eval.5.123085357>
89> lists:map(Add_3, [1,2,3]).
[4,5,6]

今度は(またしても)都市のリストに載っている気温の情報を出力してみましょう:

90> Print_City = fun({City, {X, Temp}}) -> io:format("~-15w ~w ~w~n",
[City, X, Temp]) end.
#Fun<erl_eval.5.123085357>
91> lists:foreach(Print_City, [{moscow, {c, -10}}, {cape_town, {f, 70}},
{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
moscow          c -10
cape_town       f 70
stockholm       c -4
paris           f 28
london          f 36
ok

さてそれでは、都市と気温のリストをめぐってすべての要素をセルシウスに変換するのに使えるfunを定義してみましょう。

-module(tut13).
-export([convert_list_to_c/1]).
convert_to_c({Name, {f, Temp}}) ->
    {Name, {c, trunc((Temp - 32) * 5 / 9)}};
convert_to_c({Name, {c, Temp}}) ->
    {Name, {c, Temp}}.
convert_list_to_c(List) ->
    lists:map(fun convert_to_c/1, List).
92> tut13:convert_list_to_c([{moscow, {c, -10}}, {cape_town, {f, 70}},
{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
[{moscow,{c,-10}},
 {cape_town,{c,21}},
 {stockholm,{c,-4}},
 {paris,{c,-2}},
 {london,{c,2}}]

convert_to_c関数自体は以前登場したものと変わりません。しかし今回はfunとして使用しています:

lists:map(fun convert_to_c/1, List)

他の場所で〔つまりその実行コンテキストではなくモジュール内などで〕定義された関数をfunとして使用するとき、それはFunction/Arityという書式で参照できます。(思い出してください。Arityとは、引数の数です。)それで、map関数を呼び出す際にはlists:map(fun convert_to_c/1, List)と記述したのです。ご覧の通り、convert_list_to_cはより短く、簡潔なものになりました。

標準モジュールlistsは、sort(Fun, List)という関数も含んでいます。ここで、Funは2つの引数をとる何らかのfun です。funはもし第1引数が第2引数より〔意味的に〕小さければtrueを返し、そうでなければfalseを返さなくてはいけません。convert_list_to_c関数にソート機能も加えてみましょう:

-module(tut13).
-export([convert_list_to_c/1]).
convert_to_c({Name, {f, Temp}}) ->
    {Name, {c, trunc((Temp - 32) * 5 / 9)}};
convert_to_c({Name, {c, Temp}}) ->
    {Name, {c, Temp}}.
convert_list_to_c(List) ->
    New_list = lists:map(fun convert_to_c/1, List),
    lists:sort(fun({_, {c, Temp1}}, {_, {c, Temp2}}) ->
                       Temp1 < Temp2 end, New_list).
93> c(tut13).
{ok,tut13}
94> tut13:convert_list_to_c([{moscow, {c, -10}}, {cape_town, {f, 70}},
{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
[{moscow,{c,-10}},
 {stockholm,{c,-4}},
 {paris,{c,-2}},
 {london,{c,2}},
 {cape_town,{c,21}}]

sort関数の引数としてfunが使用されています:

fun({_, {c, Temp1}}, {_, {c, Temp2}}) -> Temp1 < Temp2 end,

ここで匿名変数“_”〔アンダーバー〕という概念が導入されました。これは何らかの値をキャッチするもののその値を使用することのない場合に使用する略記法です。匿名変数を使用できるのは、funの中でだけです。Temp1 < Temp2の式は、Temp1Temp2より小さい場合にtrueを返します。

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

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