Skip to content

Instantly share code, notes, and snippets.

@Gab-km
Last active December 17, 2015 03:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Gab-km/ed96c640970f367b911a to your computer and use it in GitHub Desktop.
Save Gab-km/ed96c640970f367b911a to your computer and use it in GitHub Desktop.
公式チュートリアルである "Getting Started with Erlang User's Guide" の翻訳 - Sequential Programming

<前のページ <https://gist.github.com/Gab-km/72a7601e32b5570dc61a 次のページ>>

2 順を追ってのプログラミング

2.1 Erlang のシェル

ほとんどの 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().
% 

2.2 モジュールと関数

シェルからのみコードを実行可能なのであれば、プログラミング言語はあまり役に立ちません。そこで、ここに小さな 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

上記の例で、数は整数でコード上の関数にある引数 NXY は変数と呼ばれます。変数は大文字から始めなければなりません(Erlang リファレンスマニュアルの "Variables" の章を参照してください)。変数の例は NumberShoeSizeAge などです。

2.3 アトム

アトムは Erlang における別のデータ型です。アトムは小文字から始まります((Erlang リファレンスマニュアルの "Atom" の章を参照してください))、例えば: charlescentimeterinch です。アトムは単純に名前であって、他の何者でもありません。変数のように値を持てるものでもありません。

インチからセンチメートルに変換したり、その逆のことをするのに便利な次のプログラムを入力しましょう(ファイル: 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}]}]}}

2.4 タプル

さて、 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 を選んでみました。

2.5 リスト

タプルが物事をまとめるのに対して、物事のリストを表現できるようにもしたいです。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.
[]

上記すべての例で、新しい変数名を使っており、再利用しているものはありません。 FirstTheRestE1E2RABC 。この理由は、変数はそのコンテキスト(スコープ)において一度だけ値を与えられうるものだからです。後でこの話に戻ってきますが、思ったほど特別なことではないですよ!

以下の例はどのようにリストの長さを求めるかを示します:

-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"

2.6 マップ

マップはキーから値への関連付けの集合です。これらの関連は "#{" と "}" に閉じ込められています。 "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 関数は新しいマップのタームを作り、 redgreenblue、および 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/2blue/2green/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 マップを更新します。既存のキーを新しい値で更新するための構文は := 演算子によってなされます。

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

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

2.8 ターミナルへの出力を書く

これらの例で整形された出力ができるのはいい感じなので、次の例は 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 シェル自身はクラッシュしないということは気がついてください。

2.9 大きめの例

さぁ、これまで私たちが学んできたことを足固めするための大きな例です。世界中のたくさんの都市から得た、測定した気温のリストがあるとしましょう。(先のリストにおいて)あるものはセ氏(百分度)で、またあるものはカ氏です。まずは全部セ氏に変換し、それからデータを見やすく出力してみましょう。

%% このモジュールは 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 アトムを返す(という結果で終わる)ので、プログラムは終了します。

2.10 マッチ、ガード、変数のスコープ

こんなリストの最高と最低の気温が分かると便利でしょう。これをするためにプログラムを拡張する前に、リスト内の最大と最小の要素を見つけるための関数を見てみましょう:

-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/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 である時に Result_so_far の代わりに Head を使います。 when は、後続のテストが真である場合にだけこの関数の部分を使うでしょう、ということを言うために、関数の -> の前で使う特別な単語です。この手のテストを ガード と言います。ガードが真でない場合(ガードが失敗する、と言います)、関数の次の部分を試します。このケースでは、 HeadResult_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);

ちょっとはスッキリするのではないでしょうか。

2.11 リストについての詳細

| 演算子はリストの先頭要素を取得するのに使うことができることを思い出しましょう:

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/1convert_list_to_c/1 を呼び出すことが分かります。 convert_list_to_c/1List_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

2.12 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

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}

caseif の両方に 戻り値 がある、つまり上記の例では 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

2.13 組み込み関数(BIF)

組み込み関数 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 の結果になる leapnot_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 行うのが難しい(または不可能な)変換を行います。

2.14 (楽しい)高階関数(Funs)

Erlang には、ほとんどのイマドキの関数型プログラミング言語のように、高階関数があります。シェルを使った例から始めてみましょう:

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

ここでやったことは、数値を2倍する関数を定義して変数にこの関数を割り当てました。ですので Xf(5) は値10を返します。リストを扱う時に便利な2つの関数は foreachmap ですが、これらは以下のように定義されています:

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 はもっと短く簡単に理解できるようになります。

標準モジュール listsFun が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 の中に限らず、適切なところならどこでも使えます。 Temp1Temp2 よりも小さい場合、 Temp1 < Temp2true を返します。

<前のページ <https://gist.github.com/Gab-km/72a7601e32b5570dc61a 次のページ>>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment