Hatena::Grouperlang

lnzntの Erlang 日記 このページをアンテナに追加 RSSフィード

2011年08月12日

いろはメモ - Erlang 編 - 「い」 : Erlang の基礎

| 22:13 |   いろはメモ - Erlang 編 - 「い」 : Erlang の基礎 - lnzntの Erlang 日記 を含むブックマーク はてなブックマーク -   いろはメモ - Erlang 編 - 「い」 : Erlang の基礎 - lnzntの Erlang 日記   いろはメモ - Erlang 編 - 「い」 : Erlang の基礎 - lnzntの Erlang 日記 のブックマークコメント

いろはメモ - 初心者の書いたメモです。間違いは随時直していきます。

----

インストール

DebianLinux では erlang パッケージをインストールする。

$ sudo apt-get install erlang

Erlang シェル (インタプリタ)

起動
  $ erl
終了

以下のいずれか。

Eshell V5.7.4  (abort with ^G)
1> q().  %% q(). を入力
ok
Eshell V5.7.4  (abort with ^G)
1>      %% ここで CTRL-C (Windows の場合は CTRL-[Pause/Break] らしい)
BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
       (v)ersion (k)ill (D)b-tables (d)istribution
a       %% a を入力
Eshell V5.7.4  (abort with ^G)
1>        %% ここで CTRL-G (CTRL-C ではない!) 
User switch command
 --> q    %% q を入力

変数とタプルとリスト

変数
Eshell V5.7.4  (abort with ^G)
                 %%「=」はパターンマッチ照合演算子 (代入演算子ではない!)
1> X = 1 + 1.    %% X は変数。変数名は大文字か _ で始まる
2                %% X は 2 に束縛された束縛済み変数(になった)
2> X = 2.        %% パターンマッチ
2                %%                --> 成功
3> X = 3.        %% パターンマッチ ... 失敗する
** exception error: no match of right hand side value 3
4> Y = hello.    %% hello はアトム。アトムは小文字で始まる。
hello             
5> Z = 'HELLO'.  %% 'HELLO' のように「'」で括られたものもアトム。
'Hello'          %% アトムは文字の並びである。が、Ruby でいうシンボルに
                 %% 相当する(と思う...)。
6> S = "hello"   %% "hello" のように「"」で括られたものは(文字の)リスト。
"hello"          %% 文字のリストは、いわゆる文字列に相当する。

(上のプロンプト 1> のところ:)未束縛変数にパターンマッチを行なうと、変数を値に束縛して束縛済み変数にする。他の言語の代入みたいな働きになる。

変数束縛を解消するには f() を使う。( 2009-06-20 - バリケンのErlang日記 - はてなグループ: erlangを参考にしました。感謝です )

1> X = 2.
2
2> X.
2
3> f().    %% "すべて"の変数束縛が解消される
ok
4> X.     
* 1: variable 'X' is unbound

とりあえず、まとめ!

   +------------+
   |     X      |
   |  (未束縛)  |
   +------------+
         |
         | = 2  (2 とパターンマッチ)
         V
   +------------+
   |     X      |   = 2 (2 とパターンマッチ)   / 成功 (^_^)
   |  (束縛=2)  |   = 3 (3 とパターンマッチ)   / 失敗 (;_;)
   +------------+

  ---- (まとめ) ----
    未束縛の変数 X くんは、値 2 さんに束縛されて、束縛済み変数になります。
    X くんの束縛は、生涯解消されません。。。と、思ってたら f() で解消できます。
    X くんは、2 さんとマッチさせると、成功します。
    けれども、2 さん以外の誰とマッチさせても、決して成功しません。  
アンダースコア変数

名前が _ で始まる変数変数が一度しか使われなくとも警告が出ない。

タプル
Eshell V5.7.4  (abort with ^G)
1> T = {a, 10}.   %% タプルは {x, y, ..., z} と書く
{a,10}
2> {a, X} = T.
{a,10}
3> X.
10
4> {_,X} = T.   %% _ は無名変数。(匿名変数?)
{a,10}
5> X.
10
6> 
リスト
1> L = [1,2,3].   %% リストは [x,y,...,z] と書く
[1,2,3]
2> [H|T] = L .    %% パターンマッチ 
[1,2,3]
3> H.             %% H は 1 に束縛されている
1
4> T.             %% T は [2,3] に束縛されている
[2,3]
5> L1 = [0|L].    %% 0 を先頭に追加
[0,1,2,3]
6> X = "hello".   %% 文字のリスト(文字列相当)。「"」で括る
"hello"
7> X1 = [65|X].   %% 65(= A) を先頭に追加
"Ahello"
8> X2 = [$a|X1].  %% a を先頭に追加。$a は a の文字コード。
"aAhello"
9> [A,B,C|Z] = [1,2,3,4,5].
[1,2,3,4,5]
10> A.
1
11> B.
2
12> C.
3
13> Z.
[4,5]
1> [1,2] ++ [3].    %% リストの結合
[1,2,3]
2> [1,2,3] -- [3].  %% 差分リスト
[1,2]
3> [1,2,3,1] -- [1,3,4].
[2,1]
その他のケース
1> [_,X,{a,{b,[_|T]}}] = [1,hello,{a,{b,[1,2,3]}}]. %% タプルとリスト
[1,hello,{a,{b,[1,2,3]}}]
2> X.
hello
3> T.
[2,3]
1> [X,X,X] = [1,1,1].   %% パターンマッチ成功
[1,1,1]
2> X.
1
1> [X,X,X] = [1,2,3].    %% パターンマッチ失敗
** exception error: no match of right hand side value [1,2,3]
1> [_,X,_] = [1,2,3].    %% パターンマッチ成功
[1,2,3]
2> X.
2

([注意] 上の例を試す場合は、毎回シェルを起動しなおすか、変数名を変えて試しましょう。X が既に束縛された状態だと意図しない結果になる場合があります)

プログラムの実行

例: hello.erl (拡張子は .erl。コンパイルされたファイルの拡張子は .beam)

-module(hello).       %% このプログラムをモジュールに入れる宣言。
                      %% モジュール名はファイルの basename(この場合 hello)
-export([start/0]).   %% 関数 hello:start/0 を外部呼び出し可能にする

                      %% 関数 start/0 の定義 (0 はアリティ(引数の数))
start() -> io:format("hello, world~n").

hello モジュールの start 関数は、hello:start と表記する。

関数を実行すると "hello, world"*1を出力する。

シェルコンパイル、実行
Eshell V5.7.4  (abort with ^G)
1> c(hello).               # コンパイル。hello.beam ができる
{ok,hello}
2> hello:start().          # 実行
hello, world
ok
コマンドラインコンパイル、実行
$ erlc hello.erl                            # コンパイル。hello.beam ができる
$ erl -noshell -s hello start -s init stop  # 実行

オプション -noshell は対話型機能(バナーとか?)の抑止、-s hello start は hello:start() の実行、init:stop() は処理系の終了処理(かな?)

escript で実行

escript は Erlang プログラムスクリプトとして実行するコマンド。

例 hello.escript

#!/usr/bin/env escript

main(_) ->                            %% 最初に呼ばれるのは main()
    io:format("hello, world~n").

実行

$ escript hello.escript   # この場合、スクリプトの shebang は不要
hello, world

または、

$ chmod +x hello.escript  # この場合、スクリプトの shebang は必須
$ ./hello.escript
hello, world
ワンライナー

erl コマンドの -eval オプションを使う。

$ erl -eval 'io:format("hello, world~n").' -noshell -s init stop
hello, world

関数の定義

例: リストの要素の総和を求める関数 (定義部分のみ)

  sum(L) -> sum(L, 0).             %% sum/1 を定義

  sum([], N) -> N;                 %% 節の区切りは ; (セミコロン)
  sum([H|T], N) -> sum(T, H + N).  %% sum/2 を定義

sum/1 と sum/2 の 2つの関数を定義している。sum/2 は 2 つの節から成る。

無名関数
1> Double = fun(X) -> X * 2 end.  %% 無名関数の定義には fun 〜 end を使う
#Fun<erl_eval.6.13229925>
2> Double(2).
4
3> L = [1,2,3].
[1,2,3]
4> lists:map(Double, L).           %% Double を引数として渡す
[2,4,6]

下の例では、外側の fun がエンクロージャ*2になり内側の fun をクロージャ*3にしている。

1> Mult = fun(Times) -> (fun(X) -> X * Times end ) end. %% 関数を返す関数
#Fun<erl_eval.6.13229925>
2> Triple = Mult(3).
#Fun<erl_eval.6.13229925>
3> Triple(5).
15                  %% 5 * 3
制御構造を作る例

lib_misc:for() を作る。(lib_misc.erl)

-module(lib_misc).
-export([for/3]).

for(Max,Max,F) -> [F(Max)];
for(I, Max, F) -> [F(I) | for(I+1, Max, F)].
1> c(lib_misc).
{ok,lib_misc}
2> lib_misc:for(1,10,fun(I) -> I * I end).
[1,4,9,16,25,36,49,64,81,100]
リスト内包
1> L = [1,2,3].   
[1,2,3]
2> [2*X || X <- L].   %% リスト内包。形式は [constractor || qualifier, ...]
[2,4,6]
-module(lib_misc).
-export([qsort/1]).

qsort([]) -> [];
qsort([Pivot|T]) ->
    qsort([X || X <- T, X < Pivot])   %% qualifier は X <- T, X < Pivot
    ++ [Pivot] ++                     %% ++ はリスト連結演算子
    qsort([X || X <- T, X >= Pivot]). %% qualifier は X <- T, X >= Pivot

リスト内包の一般的な形

  [ X || Qualifier1, Qualifier2, ... ]
  • 限定子(qualifier) はジェネレータかフィルタ
  • ジェネレータの形式は Pattern <- ListExpr。ListExpr はリストを返す式
  • フィルタは、true または false を返す関数、あるいはブール式
ガード
-module(lib_misc).            %% ソースファイル lib_misc.erl
-export([f/1]).

f(X) when X > 0, X < 2 ; X =:= 3 -> X * 10;   %% =:= は等価判定演算子
f(X)                             -> X.
1> c(lib_misc).
{ok,lib_misc}
2> [lib_misc:f(X) || X <- [1,2,3]].
[10,2,30]

ガードの形式。(上例の場合)

  when X > 0,     X < 2  ; X =:= 3    %% 意味は (X > 0 && X < 2) || X == 3 
       ^^^^^      ^^^^^    ^^^^^^^
       ガード式  ガード式  ガード式   %% ガード式: true、定数、ブール式など
       ^^^^^^^^^^^^^^^^^^  ^^^^^^^^
             ガード         ガード    %% ガード  : 1つ以上のガード式の並び
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^                (区切りは「,」。ANDの意)
                ガード列              %% ガード列: 1つ以上のガードの並び
                                                   (区切りは「;」。OR の意)

その他のこと

レコード

特殊なタプル。ソースコードで宣言する。(シェルではできない)

例: award.hrl (拡張子は .hrl (という習慣?))

-record(award, {title='N/A', year}).      %% award はレコードの名前
                                          %% title, year はキー
                                          %% title はデフォルト値有り

シェルレコードファイルを読み込んでレコードを作ってみる。

1> rr("award.hrl").     %% rr でレコードファイル読み込み
[person]
2> A1 = #award{}.                       %% デフォルト値で作る
#award{title = 'N/A',year = undefined}  %% デフォルト値でできる
3> A2 = #award{title='Kita no Yado kara',year=1976}. %% 値を指定して作る
#award{title = 'Kita no Yado kara',year = 1976}
4> A3 = A2#award{title='I Wish For You'}.     %% A2 の title の値を変えて A3 にコピー 
#award{title = 'I Wish for You',year = 1976}  %% (年のツッコミ無用)
5> #award{title='Kita no Yado kara', year=Year} = A2.  %% パターンマッチ
#award{title = 'Kita no Yado kara',year = 1976}
6> Year.
1976

余談。rfレコードの定義を忘れさせると普通のタプルとして見える。

7> rf(award).   %% rf で award 型レコードを忘れさせる
ok
8> A2.
{award,'Kita no Yado kara',1976}  %% 普通のタプルとして見える

award 型のレコードにマッチさせる関数の定義 (ガードを使う)

 f(X) when is_record(X, award) -> ...  %% is_record はレコードか判定する述語
例外

例外を起こす BIF (build-in function; 組み込み関数)は以下の 3 つ。

10> exit('No!').  %% 現在のプロセスにリンクされているプロセスに終了メッセージを投げる
** exception exit: 'No!'
11> throw('No!'). %% 修復可能そうな例外の場合に投げる
** exception throw: 'No!'
12> erlang:error('No!'). %% クラッシュエラーの場合に投げる。内部的エラーと同じレベル
** exception error: 'No!'

exit(normal) は正常終了を表す。

例外のキャッチは try ... catch 構文で行なう。

1> try throw('No!')
1> catch
1>   X -> {thrown, X}      %% 例外をここでキャッチ
1> end.
{thrown,'No!'}

try ... catch の一般形式は以下。of ...、after... は省略できる。

  try 式 of
     パターン [ガード] -> 式;  %% 例外が発生しない時に、
              :
     パターン [ガード] -> 式   %% 戻り値とパターンマッチさせて式を評価する
  catch
     例外タイプ: 例外パターン -> 式; %% 例外タイプ省略時は throw と解釈
                 :
     例外タイプ: 例外パターン -> 式  %% 例外タイプは上の3つ(exit,throw,error)
  after                         %% 例外発生した/しないに関わらず実行される
     式
  end

ありうる例外をすべて捕捉したい場合のイデオム。

  try Expr
  catch
    _:_ -> ....   %% パターンは _ ではダメ
  end

catch プリミティブで例外を捕捉できる。

3> catch throw('No!').
'No!'

スタックトレースを知りたい場合

  try Expr
  catch
    error:X -> { X, erlang:get_stacktrace() } %% erlang:get_stacktrace() を使う
  end
バイト列
4> <<16#ff,128,0>>.  %% 16#xx は 16進数形式
<<255,128,0>>
5> <<"ABC">>.        %% "ABC" は [16#41, 16#42, 16#42]
<<"ABC">>

(厳密な仕様はバイト列でなくオクテット列かも...?[勉強中])

ビット構文
6> Red = 2.
2
7> Green = 61.
61
8> Blue = 20.
20
9> Mem = <<Red:5, Green:6, Blue:5>>. %% RGB に各 5, 6, 5 ビット割当てる
<<23,180>>                           %% 2 バイトにパックされる
式と式列
評価して値を返すもの。catch、try..catch、if、case なども式。レコードモジュール宣言などは式ではない。
式列
式の並び。「,」(カンマ)で区切って並べる。
ブール式

ブール式は以下の 4 つ。ブール値は trueアトムと false アトム。

 not B1
 B1 and B2
 B1 or B2
 B1 xor B2
短絡的ブール式
 B1 orelse B2  : 短絡的 OR
 B1 andelse B2 : 短絡的 AND
その他の式
begin     %% ブロック式
  Exprl,
  ...
  ExprN
end

case Expr of        %% case 式
  Pattern1 [ when Gaurd1 ] -> Expr_seq1;
          :
  PatternN [ when GaurdN ] -> Expr_seqN
end

if                  %% if 式
  Gaurd1 -> Expr1;
     :
  Gaurd2 -> Expr2;
end

項の比較順序
 数 < アトム < リファレンス < fun < ポート < プロセスID < タプル < リスト < バイナリ

プロセス辞書

そのプロセスだけから見える辞書。(...?[勉強中])

属性
-module(Modname).             %% モジュール宣言
-export([Name/1, ...]).       %% 関数を外部呼び出し可能にする
-import(Mod, [Name/1, ...]).  %% 関数をモジュール名省略で呼び出せるようにする
-compile(Options).            %% コンパイルオプションに Options を追加する。
-vsn(Version).                %% モジュールバージョンの指定

宣言 -compile(export_all) はすべての関数エクスポートしてコンパイルする指示である。デバッグ時に有益

ユーザ定義の属性

-SomeTag(Value).
インクルード
-include(Filename).           %% ファイルを include する
-include_lib(Name).           %% ライブラリのヘッダ向き
マクロ定義
-define(XXXX, 5).
io:format() のフォーマット指定のにょろ(~)
にょろ説明
~n(システムに応じた)改行を出力する
~p引数を pretty print する
~s引数文字列を出力する
~w標準構文を使って出力する(...?[勉強中])
いっぱいあるらしい
Erlang の型記法

ドキュメンテーション記法として Erlang コミュニティで使用。(言語仕様ではない)

  @spec func(Arg1, ..., ArgN) -> Val

----

参考書籍

プログラミングErlang

プログラミングErlang

オーム社のページ(サンプルソースダウンロードなど): Ohmsha | 商品一覧

*1:「hello, world」は言語の初学者が最初に書くべきプログラム。その言語の神様から祝福を受けるための儀式。

*2レキシカル変数(この例では Times)を定義している関数

*3レキシカル変数(この例では Times)を参照している関数