- タイトル
Erlang ユーザーガイドから始めよう
- バージョン
6.2 / 7.1
- 原文
- 原文の著作権者
Ericsson AB.
-
-
Save Gab-km/ed96c640970f367b911a to your computer and use it in GitHub Desktop.
<前のページ <https://gist.github.com/Gab-km/72a7601e32b5570dc61a 次のページ>>
ほとんどの OS にはコマンドインタープリタかシェルがあり、Unix と Linux にはたくさん、Windows にはコマンドプロンプトがあります。Erlang には、ちょっとした Erlang のコードを直接書くことができ、何が起こるか(shell(3) を参照してください)を確かめるためにそれらを評価(実行)することができる、専用のシェルがあります。
お使いの OS にあるシェルかコマンドインタープリタを立ち上げて、erl
とタイプすることで(Linux か UNIX で)Erlang のシェルを開始させます。するとこのような感じのものが見られるでしょう。
% erl
Erlang R15B (erts-5.9.1) [source] [smp:8:8] [rq:8] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.9.1 (abort with ^G)
1>
シェル上で "2 + 5." と打ち込み、エンター(キャリッジ・リターン)を入力してみましょう。終止符 "." とキャリッジリターンで終わることで、コードを入力し終わったよとシェルに伝えていることに気をつけましょう。
1> 2 + 5.
7
2>
ご覧のとおり、Erlang シェルは入力された行に番号を振り(1> 2> のように)、それから 2 + 5 が 7 であると正しく回答します。シェル上で入力を間違ってしまった場合、ほとんどのシェルのようにバックスペースキーで削除することができます。シェルにはさらに多くの編集用コマンドがあります(ERTS ユーザーガイドにある "tty - A command line interface" を参照してください)。
(以下の例にあるシェルから設定された行番号の多くが順番通りではないことに注意してください。これはこのチュートリアルがそれぞれのセッションで書かれ、コードがテストされたためです)
こちらは、より複雑な計算です:
2> (42 + 77) * 66 / 3.
2618.0
カッコの使用、乗算演算子 "*"、そして除算演算子 "/"、これらは普通の四則演算です(Expressions を参照してください)。
Erlang システムと Erlang シェルをシャットダウンするには Control-C を入力します。
次のように出力されます:
BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
(v)ersion (k)ill (D)b-tables (d)istribution
a
%
Erlang システムから離れるために "a" を入力します。
Erlang システムを終了するもう一つの方法は、 halt()
と入力することです:
3> halt().
%
シェルからのみコードを実行可能なのであれば、プログラミング言語はあまり役に立ちません。そこで、ここに小さな Erlang プログラムがあります。適したテキストエディタを使って tut.erl
という名前のファイルにそれを入力します。tut.erl
というファイル名は重要で、erl
を開始したのと同じディレクトリにあります。もしあなたのエディタが入力やコードをいい感じにフォーマットするのを簡単にしてくれる Erlang モード(ツールユーザーガイドにある The Erlang mode for Emacs を参照してください)があるならラッキーですが、なくても完璧に上手くやれます。こちらが入力するコードです:
-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 モジュール と呼ばれるものを含みます。モジュールにあるコードの最初の行はモジュールの名前を教えてくれます(Erlang リファレンスマニュアルの "Modules" の章を参照してください)。
-module(tut).
これはモジュールが tut だということを示しています。行末の "." に注意してください。モジュールを保存するに使われるファイルは、拡張子の ".erl" を除いてモジュールと同じ名前でなければなりません。私たちのケースでは、ファイル名は tut.erl
です。別のモジュールにある関数を使うときは、 module_name:function_name(arguments)
という文法を使います。ですから
4> tut:double(10).
は tut
モジュールにある double
関数を引数 "10" で呼び出しているということです。
2行目の:
-export([double/1]).
は tut
モジュールが1つの引数(私たちの例では X
)を取る double
という関数を持っているということ、そしてこの関数は 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
最初の部分:
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
の行を mult
という2引数を取る別の関数がある、という情報で拡張しないといけません。
コンパイルして:
7> c(tut1).
{ok,tut1}
やってみましょう:
8> tut1:mult(3,4).
12
上記の例で、数は整数でコード上の関数にある引数 N
、 X
、 Y
は変数と呼ばれます。変数は大文字から始めなければなりません(Erlang リファレンスマニュアルの "Variables" の章を参照してください)。変数の例は Number
、 ShoeSize
、 Age
などです。
アトムは Erlang における別のデータ型です。アトムは小文字から始まります((Erlang リファレンスマニュアルの "Atom" の章を参照してください))、例えば: 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
説明なしに小数(浮動小数点数)を導入しましたが、きっとあなたなら切り抜けてくれますね。
変換関数にセンチメートルやインチ以外のものを入力したら何が起こるか見てみましょう:
12> tut2:convert(3, miles).
** exception error: no function clause matching tut2:convert(3,miles) (tut2.erl, line 4)
convert
関数の2つの部分は節と呼ばれます。ここで "miles" はいずれの節の部分でもないことが分かります。Erlang のシステムはどの節にも マッチ できなかったので、 function_clause
エラーメッセージを取得しました。シェルはエラーメッセージをいい感じに整形しますが、エラーのタプル(組)はシェルの履歴リストに保存され、シェルの v/1
コマンドで出力することができます:
13> v(12).
{'EXIT',{function_clause,[{tut2,convert,
[3,miles],
[{file,"tut2.erl"},{line,4}]},
{erl_eval,do_apply,5,[{file,"erl_eval.erl"},{line,482}]},
{shell,exprs,7,[{file,"shell.erl"},{line,666}]},
{shell,eval_exprs,7,[{file,"shell.erl"},{line,621}]},
{shell,eval_loop,3,[{file,"shell.erl"},{line,606}]}]}}
さて、 tut2
プログラムはとても良いプログラミングスタイルではありません。考えてみてください:
tut2:convert(3, inch).
これは3がインチを表しているんでしょうか?それとも、3はセンチメートルで、インチに変換したいんでしょうか?ところで、 Erlang には物事をより理解できるようにするためにまとめる方法があります。それを タプル と言います。タプルは "{" と "}" に囲まれています。
ですので、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インチをセンチメートルに変換し、それを戻してちゃんと元の値に戻りました。つまり、関数の引数に別の関数の結果を使うことが出来るのです。少し立ち止まって(上の)16行目がどのように動作しているのか考えてみましょう。関数に与えた引数 {inch,5}
はまず convert_length
の最初の頭の節、つまり {inch,5}
にマッチしない {centimeter,X}
が見える convert_length({centimeter,X})
にマッチします(頭とは "->" の前の箇所です)。これが失敗したので、次の節の頭、つまり convert_length({inch,Y})
を試し、これがマッチするので Y
は値5を得ます。
上で2つの部分を持つタプルをお見せしましたが、タプルは欲しいだけいくつでも部分を持つことができ、任意の有効な Erlang ターム でも含むことができます。例えば、世界の様々な都市の気温を表現するために
{moscow, {c, -10}}
{cape_town, {f, 70}}
{paris, {f, 28}}
と書くことができます。
タプルは固定数のものを持ちます。タプル内のそれぞれのものを要素と呼びます。だから {moscow,{c,-10}}
というタプルでは、要素1は moscow
で要素2は {c,-10}
です。百分度(つまりセ氏)を意味する c
とカ氏を意味する f
を選んでみました。
タプルが物事をまとめるのに対して、物事のリストを表現できるようにもしたいです。Erlang におけるリストは "[" と "]" に囲まれています。例えば、世界中のいろんな都市の気温のリストは次のようになります:
[{moscow, {c, -10}}, {cape_town, {f, 70}}, {stockholm, {c, -4}},
{paris, {f, 28}}, {london, {f, 36}}]
このリストはとても長かったので一行に収まりませんでした。気にすることはありません、Erlang は「分別がある箇所」のすべてで改行を許しますが、、例えばアトムの真ん中や整数などではできません。
リストの一部を見るためのとても便利な方法、それは "|" をつかうことです。これはシェルを使って例示するのが一番です。
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).
最初の要素が First
で、残りの要素が Rest
であるリストの長さは 1 + Rest
の長さになります。
(進んでいる読者の方へ: これは末尾再帰ではなく、この関数を書くためのもっと良い方法があります)
一般に、別の言語で言うところの「レコード」や「構造体」を使うような場面でタプルを使い、サイズを変えられるような対象(つまり、別の言語で言うところの連結リストを使うような場面)を表したい時にリストを使うと言えます。
Erlang は文字列データ型を持たない代わりに、ASCII 文字のリストで文字列を表すことができます。ですから、リスト [97, 98, 99]
は "abc" と等しいのです。Erlang シェルは「賢く」、どんな種類のリストを意図しているか推測し、シェルの考える最も適切な形式で出力します。例えば:
30> [97,98,99].
"abc"
マップはキーから値への関連付けの集合です。これらの関連は "#{" と "}" に閉じ込められています。 "key" から値42への関連を作るために、このように書きます:
> #{ "key" => 42 }.
#{"key" => 42}
いくつかの興味深い特徴を使った例で、一番深いところに一気に飛び込みましょう。
以下の例は、色とアルファチャンネルを参照するマップを使って、どのようにアルファブレンディングを計算するかを示しています:
-module(color).
-export([new/4, blend/2]).
-define(is_channel(V), (is_float(V) andalso V >= 0.0 andalso V =< 1.0)).
new(R,G,B,A) when ?is_channel(R), ?is_channel(G),
?is_channel(B), ?is_channel(A) ->
#{red => R, green => G, blue => B, alpha => A}.
blend(Src,Dst) ->
blend(Src,Dst,alpha(Src,Dst)).
blend(Src,Dst,Alpha) when Alpha > 0.0 ->
Dst#{
red := red(Src,Dst) / Alpha,
green := green(Src,Dst) / Alpha,
blue := blue(Src,Dst) / Alpha,
alpha := Alpha
};
blend(_,Dst,_) ->
Dst#{
red := 0.0,
green := 0.0,
blue := 0.0,
alpha := 0.0
}.
alpha(#{alpha := SA}, #{alpha := DA}) ->
SA + DA*(1.0 - SA).
red(#{red := SV, alpha := SA}, #{red := DV, alpha := DA}) ->
SV*SA + DV*DA*(1.0 - SA).
green(#{green := SV, alpha := SA}, #{green := DV, alpha := DA}) ->
SV*SA + DV*DA*(1.0 - SA).
blue(#{blue := SV, alpha := SA}, #{blue := DV, alpha := DA}) ->
SV*SA + DV*DA*(1.0 - SA).
コンパイル(color.erl
というファイルに保存します)してテストしましょう:
> c(color).
{ok,color}
> C1 = color:new(0.3,0.4,0.5,1.0).
#{alpha => 1.0,blue => 0.5,green => 0.4,red => 0.3}
> C2 = color:new(1.0,0.8,0.1,0.3).
#{alpha => 0.3,blue => 0.1,green => 0.8,red => 1.0}
> color:blend(C1,C2).
#{alpha => 1.0,blue => 0.5,green => 0.4,red => 0.3}
> color:blend(C2,C1).
#{alpha => 1.0,blue => 0.38,green => 0.52,red => 0.51}
この例はいくつか説明が必要です:
-define(is_channel(V), (is_float(V) andalso V >= 0.0 andalso V =< 1.0)).
まず、ガードテストに役立つ is_channel
というマクロを定義します。これはここだけで便利なものであり、構文で溢れかえるのを減らすためのものです。Erlang リファレンスマニュアルの Macros についてさらに読むことができます。
new(R,G,B,A) when ?is_channel(R), ?is_channel(G),
?is_channel(B), ?is_channel(A) ->
#{red => R, green => G, blue => B, alpha => A}.
new/4
関数は新しいマップのタームを作り、 red
、 green
、 blue
、および alpha
に初期値を紐付けます。この場合、各引数に対して ?is_channel/1
マクロによって保証された0.0以上1.0の浮動小数点値のみを許可します。新しいマップを作る際、 =>
演算子のみが許可されます。
new/4
で作成した色のタームに対して blend/2
を呼び出すことにより、2つのマップのタームで決定される結果の色を計算することができます。
blend/2
が行う最初のことは、結果のアルファチャンネルを計算することです。
alpha(#{alpha := SA}, #{alpha := DA}) ->
SA + DA*(1.0 - SA).
両方の引数に対して :=
演算子を用い、 alpha
キーに関連付けられた値を取得します。マップにある他のキーは無視され、 alpha
キーだけが必須となりチェックされます。
これはまた red/2
、 blue/2
、 green/2
のケースでも同様です。
red(#{red := SV, alpha := SA}, #{red := DV, alpha := DA}) ->
SV*SA + DV*DA*(1.0 - SA).
ここでの違いは、各マップ引数にある2つのキーに対してチェックを行うということです。他のキーは無視されます。
最後に blend/3
で結果の色を返します。
blend(Src,Dst,Alpha) when Alpha > 0.0 ->
Dst#{
red := red(Src,Dst) / Alpha,
green := green(Src,Dst) / Alpha,
blue := blue(Src,Dst) / Alpha,
alpha := Alpha
};
新しいチャンネル値で Dst
マップを更新します。既存のキーを新しい値で更新するための構文は :=
演算子によってなされます。
Erlang には何かやるのに便利なたくさんの標準モジュールがあります。例えば、 io
モジュールには整形された入出力を扱うのに便利なたくさんの関数が入っています。標準モジュールについての情報を調べるために、 erl -man
コマンドを OS のシェルやコマンドプロンプトで(つまり、 erl
を起動した場所と同じで)使うことができます。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 として含まれており、ドキュメントを HTML として読んだり www.erlang.se (商用 Erlang) や www.erlang.org (オープンソース) から PDF としてダウンロードすることができ、例えば R9B リリースですと次のようになります:
http://www.erlang.org/doc/r9b/doc/index.html
これらの例で整形された出力ができるのはいい感じなので、次の例は io:format
関数を使うシンプルな方法をお見せします。もちろん、エクスポートされた他のすべての関数のように、 io:format
関数をシェル上でテストすることができます:
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つのリストを取ります。最初のものは、ほぼ必ず引用符(")で囲まれたリストです。このリストは、~w が2番目のリストから順番通りに取得したタームによって置き換えられるのを除いて、書いたとおりに印字されます。~n はそれぞれ改行に置き換わります。すべてが計画通りに進んだ場合、 io:format/2
関数自身は ok
アトムを返します。Erlang の他の関数のように、エラーが発生した場合はクラッシュします。これは Erlang においては失敗ではなく、よく考慮された上でのポリシーなのです。Erlang は後ほど目にするエラーを扱う洗練されたメカニズムを持ちます。練習として、 io:format
をクラッシュさせてみましょう、難しくはないはずです。ですが、 io:format
がクラッシュしても、Erlang シェル自身はクラッシュしないということは気がついてください。
さぁ、これまで私たちが学んできたことを足固めするための大きな例です。世界中のたくさんの都市から得た、測定した気温のリストがあるとしましょう。(先のリストにおいて)あるものはセ氏(百分度)で、またあるものはカ氏です。まずは全部セ氏に変換し、それからデータを見やすく出力してみましょう。
%% このモジュールは 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}}))
を呼び出します。
convert_to_celsius({moscow,{c,-10}})
の関数呼び出しを見たのと同様に、 print_temp
関数への引数を見てみましょう。このように関数呼び出しがネストしている時、内側から順に処理(評価)します。つまり、最初に気温が既にセ氏である値 {moscow,{c,-10}}
を与えた convert_to_celsius({moscow,{c,-10}})
を評価し、それから print_temp({moscow,{c,-10}})
を評価します。以前の例にある convert_length
関数と同様に convert_to_celsius
関数は動作します。
print_temp
は上で述べてきたのと似たような方法で io:format
を単純に呼び出します。~-15w はフィールドの長さ(幅)を15として左寄せで「ターム」を出力する、ということです。(io(3))
いよいよ引数としてリストの残りを format_temps(Rest)
と呼び出します。この方法は他の言語におけるループ構造と似たようなものです。(えぇ、これは再帰ですが、心配しないで)。ですから同じ format_temps
関数が再度呼ばれ、今回は City
には値 {cape_town,{f,70}}
が入り、前と同じ手続きを繰り返します。これをリストが空、つまり [] になるまで続けますが、これは 最初の節である format_temps([])
にマッチします。これは単に ok
アトムを返す(という結果で終わる)ので、プログラムは終了します。
こんなリストの最高と最低の気温が分かると便利でしょう。これをするためにプログラムを拡張する前に、リスト内の最大と最小の要素を見つけるための関数を見てみましょう:
-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
まず同名の list_max
という2つの関数があることを述べておきます。しかし、これらはそれぞれ異なる数の引数(パラメータ)をとります。Erlang では、これらは全く異なる関数として見做されます。これらの関数を区別する必要がある箇所では、 name/arity
と書き、 name
は関数名で arity
は引数の個数で、この場合は list_max/1
と list_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
である時に Result_so_far
の代わりに Head
を使います。 when
は、後続のテストが真である場合にだけこの関数の部分を使うでしょう、ということを言うために、関数の ->
の前で使う特別な単語です。この手のテストを ガード と言います。ガードが真でない場合(ガードが失敗する、と言います)、関数の次の部分を試します。このケースでは、 Head
は Result_so_far
より大きくない場合、小さいか等しいはずなので、関数の次の部分にガードは必要ありません。
ガードにおけるいくつかの便利な演算子は、 <
より小さい、 >
より大きい、 ==
等しい、 >=
以上、 =<
以下、 /=
等しくない、です。(Erlang リファレンスマニュアルの "Guard Sequences" の章を参照してください)。
上記プログラムをリストの最小要素をはじき出すものに変更するために、する必要のある全てのことは >
の代わりに <
を書くことです。(ですが関数名を list_min
に変えた方が賢明でしょう( ◜◡◝ ))。
思い出してください、前に変数はそのスコープで一度だけ値が与えられると言いましたよね?上で見ましたが、例えば、 Result_so_far
はいくつかの値が与えられています。 list_max/2
を呼び出すたびに新しいスコープを作り、スコープ毎に Result_so_far
を全く異なる変数だと見做すことができるので、これはOKなのです。
変数を作り値を与える別の方法は、マッチ演算子 =
を使うことです。なので、もし M = 5
と書いた場合、 M
という変数が作られて値は5になるでしょう。もし、同じスコープで M = 6
と書いたなら、エラーとなります。シェルで試してみましょう:
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 タームを引き剥がして新しいものを作るのに特に便利です。
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);
ちょっとはスッキリするのではないでしょうか。
|
演算子はリストの先頭要素を取得するのに使うことができることを思い出しましょう:
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
がどのように作られているか考えてみましょう。以下に示すように、 []
として始まり、続いて反転させて 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) <http://www.erlang.org/doc/man/lists.html> を参照しましょう)。
さて都市の気温のところに戻りますが、今回はより構造化されたアプローチを取ってみましょう。まずはリストにある全要素を以下のようにセ氏に変換し、関数をテストしてみます:
-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}}; % 変更する
true ->
Max_City = {Max_Name, {c, Max_Temp}} % 変更していない
end,
if
Temp < Min_Temp ->
Min_City = {Name, {c, Temp}}; % 変更する
true ->
Min_City = {Min_Name, {c, Min_Temp}} % 変更していない
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
find_max_and_min
関数は最高と最低の気温をどうにか導きます。ここで if
という新しい概念を導入しました。 if
は次のように動作します:
if
Condition 1 ->
Action 1;
Condition 2 ->
Action 2;
Condition 3 ->
Action 3;
Condition 4 ->
Action 4
end
end
の前には ";" がありませんね!条件はガードと同じで、成功するか失敗するかをテストします。Erlang は一番上からスタートし、条件が成功するまで進んだところで、条件に続くアクションを評価(実行)し、 end
より前のその他全ての条件とアクションを無視します。マッチする条件がなかった場合、実行時の失敗となります。常に成功する条件はアトム、 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.erl, line 5)
tut9:test_if(33,33)
はどの条件も成功しなかったので、 if_clause
という実行時エラーになり、シェルがいい感じに整形してくれています。利用可能なたくさんのガードテストを詳しく知るために、Erlang リファレンスマニュアルの "Guard Sequences" の章を参照してください。 case
は Erlang における別の概念です。 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
の振る舞いはガードを使うことでも変更できます。願わくば例がこれを明確にしてくれるといいんですけど。以下の例は与えられた年に対し、月の長さ(訳註: 日数)を教えてくれます。うるう年の2月は29日あるので、もちろん年を知る必要があります。
-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
組み込み関数 BIF は何らかの理由で Erlang の仮想マシンに組み込まれた関数です。BIF はしばしば Erlang で実装できない機能や Erlang で実装すると効率が悪い機能を実装します。いくつかの BIF は関数名だけで呼び出すことができますが、一般には erlang モジュールに属しているので、例えば以下の trunc
という BIF は erlang:trunc
という呼び出しと同等です。
ご覧のとおり、まずは年がうるう年かそうでないかを確かめてみます。年が400で割り切れる場合、うるう年です。これを調べるために年を400で割り、組み込み関数 trunc
(詳しくは後ほど)を使って小数部分を切り捨てます。それから再び400をかけて、同じ値になるかどうか確認します。例えば、2004年だと:
2004 / 400 = 5.01
trunc(5.01) = 5
5 * 400 = 2000
で、2004と同じではない2000が帰ってくるので、2004は400で割りきれません。2000年だと:
2000 / 400 = 5.0
trunc(5.0) = 5
5 * 400 = 2000
なので、うるう年です。年が100または4で割り切れるかどうか、という次の2つのテストは同じ方法でできます。最初の if
は変数 Leap
の結果になる leap
か not_leap
を返します。月の長さを教えてくれる下記の case
にある feb
用のガードの中でこの変数を使います。
この例は trunc
の使い方を示しましたが、より簡単な方法は割ったあとの余りを与える Erlang の演算子である rem
を使うことでしょう。例えば:
74> 2004 rem 400.
4
なので、こう書く代わりに
trunc(Year / 400) * 400 == Year ->
leap;
このように書けます。
Year rem 400 == 0 ->
leap;
trunc
のような組み込み関数(BIF)は他にもたくさんあります。僅かな組み込み関数だけガードで使えて、自分で定義した関数をガードで使うことはできません。(Erlang リファレンスマニュアルの "Guard Sequences" を参照してください) (進んだ読者のための余談: ガードが副作用を持たないことを保証しております)。シェル上でこれら関数のうちのいくつかで遊んでみましょう:
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つの BIF は Erlang 行うのが難しい(または不可能な)変換を行います。
Erlang には、ほとんどのイマドキの関数型プログラミング言語のように、高階関数があります。シェルを使った例から始めてみましょう:
86> Xf = fun(X) -> X * 2 end.
#Fun<erl_eval.5.123085357>
87> Xf(5).
10
ここでやったことは、数値を2倍する関数を定義して変数にこの関数を割り当てました。ですので Xf(5)
は値10を返します。リストを扱う時に便利な2つの関数は foreach
と map
ですが、これらは以下のように定義されています:
foreach(Fun, [First|Rest]) ->
Fun(First),
foreach(Fun, Rest);
foreach(Fun, []) ->
ok.
map(Fun, [First|Rest]) ->
[Fun(First)|map(Fun,Rest)];
map(Fun, []) ->
[].
これら2つの関数は標準モジュール lists
にて提供されています。 foreach
はリストを取ってリストの各要素に fun (訳註: 高階関数のことです)を適用し、 map
はリストの各要素に fun を適用することで新しいリストを生成します。シェルに戻って、 map
とリストの各要素に3を加える fun を使うところから始めてみましょう:
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
は Fun
が2つの引数を取る fun である sort(Fun, List)
関数も持っています。この 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
よりも小さい場合、 Temp1 < Temp2
は true
を返します。
<前のページ <https://gist.github.com/Gab-km/72a7601e32b5570dc61a 次のページ>>