Erlang クエックブック


文字列
文字列を数値に変換する
n進数の文字列を数値に変換する
部分文字列を取得する
文字列の前方一致でパターンマッチングする
文字列が文字列の中に含まれているか判定する
数値
数値を16進数で表記
数値をn進数で表記
数値を文字列に変換する
数値をn進数の文字列に変換する
リスト
リストを連結する
リストから要素を取り除く
リストの要素1つずつに対して処理を行う
ドットリストを作る
タプル
タプルの要素数を取得する
タプルの要素を取得する
タプルに要素を追加する
タプルに値を設定する
ディクショナリ
dict モジュール
バイナリ
文字列リテラルをバイナリで表記する
バイナリのパターンマッチング
関数
"fun モジュール名:関数名/引数の数" で関数を参照
日付と時刻
日付と時刻の取得する
曜日を取得する
エラー(例外)処理
エラー処理を行う
ファイル
ファイル全体を文字列で取得する。
一行ずつ読み込む
ファイル情報を取得する
リンクかどうか判定する
パスがファイルかディレクトリか判定する
ファイルをコピーする
ファイルのリネーム
ファイルの改行コードを変更する
現在のディレクトリ(current working directory)を取得・設定する
絶対パスを取得する
標準入出力
ディレクトリ内のファイルを(再帰的に)処理する
ワイルドカード指定でファイルリストを取得する
Web
Web ページを取得する
HTML エスケープを行う
ネットワーク
サービス名からポート番号を取得する
FTP でファイルをアップロードする
コンカレントプログラミング
プロセスを生成する
メッセージを送信する
メッセージを受信する
タイムアウトを指定してメッセージを受信する
プロセスを登録する
プロセスをリンクする
プロセスをグルーピングする
プロセスを監視する
ポートスキャン
分散プログラミング
分散ノードを開始する
分散ノードを停止する
分散ノードへの ping
RPC
リモートノードへのモジュールロード
Yaws
動的なページを作成する
セッションデータを使う
外界とのつながり
OS(シェル)コマンドを実行する
環境変数を取得する
環境変数を設定する
メタ
Beam ファイルのパスを取得する
Beam ファイルからソースコードを取得(再構築)する
文字列をコンパイルする
関数情報を取得する
DB
ODBC 接続
色々
関数の実行時間を計測する

文字列

文字列を数値に変換する

list_to_integer("123").    % 123
list_to_integer("-10").    % -10

n進数の文字列を数値に変換する

u は指定した基数で変換、# は文字列が表現している基数で変換します。

io_lib:fread("~16u", "100").       % {ok,[256],[]}
io_lib:fread("~2u", "100abc").     % {ok,[4],[abc]}
io_lib:fread("~36u", "100%%%").    % {ok,[1296],"%%%"}
io_lib:fread("~#", "16#100").      % {ok,[256],[]}
io_lib:fread("~#", "2#100abc").    % {ok,[4],[abc]}
io_lib:fread("~#", "36#100%%%").   % {ok,[1296],"%%%"}

部分文字列を取得する

Latin-1 の文字なら問題ないのですが、日本語は完全にバイト単位です。 次の例は UTF-8 で実行した場合です。 なにかいいライブラリがあるといいのですが。

string:substr("abcd", 2).         % "bcd"
string:substr("abcd", 2, 1).      % "b"
string:substr("あいう", 1, 3).    % [227,129,130]
string:substr("あいう", 2, 1).    % [129]

文字列の前方一致でパターンマッチングする

文字列の前方一致、プレフィックスでパターンマッチングが可能です。

1> fun("Hello" ++ _) -> ok; (_) -> ng end("Hello World!").
ok
2> fun("Hello" ++ _) -> ok; (_) -> ng end("Fello World!").
ng
3> fun([$H, $e, $l, $l, $o | _]) -> ok; (_) -> ng end("Hello World!").
ok
"Hello" ++ _ は [$H, $e, $l, $l, $o | _] と同じということです。

なので、後方一致のパターンマッチングはできません。

文字列が文字列の中に含まれているか判定する

string:str/2 は頭から探し、string:rstr/2 は後から探します。 返り値は何文字目で見つかったかで、見つからない場合は 0 が返ります。 Erlang は 1 オリジンです。

1> string:str("Hello element", "el").
2
2> string:rstr("Hello element", "el").
7
3> string:rstr("Hello element", "xel").
0

数値

数値を16進数で表記

16#ff.    % 255
16#A1.    % 161

数値をn進数で表記

2進数から36進数まで表記可能です。

2#101.    % 5
3#211.    % 22
36#zz.    % 1295

数値を文字列に変換する

integer_to_list(123).        % "123"
integer_to_list(-123).       % "-123"
integer_to_list(16#10).      % "16"
integer_to_list(36#0z).      % "35"

数値をn進数の文字列に変換する

io_lib:format("~.16b", [31]).          % ["1f"]
io_lib:format("~.36B~.2B", [71, 15]).  % ["1Z","1111"]

リスト

リストを連結する

1> [1, 2] ++ [3, 4].
[1,2,3,4]

リストから要素を取り除く

1> [1, 2, 3, 1, 2, 3] -- [3, 2, 1, 2].
[1,3]
2> [X || X <- [1, 2, 3, 1, 2, 3], X /= 3].
[1,2,1,2]
3> [X || X <- [1, 2, 3, 1, 2, 3], X /= 3, X /= 1].
[2,2]

リストの要素1つずつに対して処理を行う

lists:foreach/2 はリストの要素1つずつに処理を行い ok を返します。 lists:map/2 はリストの要素1つずつに処理を行った結果をリストにして返します。

2> lists:foreach(fun(N) -> io:format("~p~n", [N]) end, [1,2,3]).
1
2
3
ok
3> lists:map(fun(N) -> N * N end, [1,2,3]).
[1,4,9]

ドットリストを作る

リスト [1, 2] は [1|[2|[]]] の構文糖です。 最後が [] になっていますが、それを [] 以外にすると Lisp でいうところのドットリストになります。
5> [1|[2|[]]].
[1,2]
6> [1|[2|3]].
[1,2|3]
7> tl(v(6)).
[2|3]
8> tl(v(7)).
3

タプル

タプルの要素数を取得する

erlang:size/1 でタプルの要素数を取得できます。

size({a, b}).               % 2
size({}).                   % 0
size({{}, {1, {1, 2}}}).    % 2

タプルの要素を取得する

erlang:element/2 でタプルの要素を取得できます。 どの要素を取得するかは第1引数の1から始まるインデックスで指定します。 範囲外のインデックスを指定した場合は badarg ランタイムエラーが発生します。

element(1, {a, b, c}).      % a
element(2, {a, b, c}).      % b

タプルに要素を追加する

4> erlang:append_element({a, b}, c).
{a,b,c}

タプルに値を設定する

5> setelement(2, {a,b,c}, two).
{a,two,c}

ディクショナリ

dict モジュール

Erlang には組み込みではディクショナリやハッシュといったデータ型はありませんが、 STDLIB に dict というモジュールがあります。 Erlang は単一代入なので、値を入れるたびに変数をかえてやる必要があります。

D1 = dict:new().                                 % 作成
D2 = D1:store(key1, value1).                     % キーと値のペアを入れる
D3 = D2:store({key2, key2}, [value2, value2]).   % キーも値も任意の方を使える
D4 = D3:append({key2, key2}, value3).            % append で値を追加
D4:find(key1).                                   % {ok,value1}
D4:find(xxxx).                                   % error が返る
D4:fetch({key2, key2}).                          % [value2,value2,value3]
D4:fetch({key2, xxxx}).                          % ランタイムエラー(function_clause)が発生
D4:to_list().                                    % リストに変換([{key1,value1},{{key2,key2},[value2,value2,value3]}])

バイナリ

文字列リテラルをバイナリで表記する

任意の文字列リテラルをバイナリとして表記できます。

1> <<"Hello Bin.">>.
<<"Hello Bin.">>
2> <<"Hello あいう.">>.
<<"Hello \343\201\202\343\201\204\343\201\206.">>.
<<72,101,108,108,111,32,227,129,130,227,129,132,227,129,134,46>>

バイナリのパターンマッチング

リストやタプルと同様にバイナリでもパターンマッチングが可能です。

IPTuple = {192, 168, 1, 1},
{A,B,C,D} = IPTuple,
<<IPDecimal:32>> = <<A:8,B:8,C:8,D:8>>.

関数

"fun モジュール名:関数名/引数の数" で関数を参照

"fun モジュール名:関数名/引数の数" で関数を参照できます。

1> fun erlang:tuple_to_list/1.
#Fun<erlang.tuple_to_list.1>
2> fun erlang:tuple_to_list/1({a, b}).
[a,b]
3> lists:map(fun erlang:tuple_to_list/1, [{a, b}, {a}, {}]).
[[a,b],[a],[]]

日付と時刻

日付と時刻の取得する

日付と時刻の取得に関しては次のような BIF があります。 また、この他に calendar モジュールがあります。

1> date().                 % ローカル日付 {年, 月, 日}
{2007,5,28}
2> erlang:localtime().     % ローカル日時 {{年, 月, 日}, {時, 分, 秒}}
{{2007,5,28},{6,46,28}}
3> erlang:localtime_to_universaltime(v(-1)). % ローカル日時を UCS に変換
{{2007,5,27},{21,46,28}}
4> now().                  % {MegaSecs, Secs, MicroSecs} 1970/1/1からの経過
{1180,302477,518328}
5> time().                 % ローカル時刻 {時, 分, 秒}
{6,50,30}
6> erlang:universaltime(). % UTC 日時 {{年, 月, 日}, {時, 分, 秒}}
{{2007,5,27},{21,51,6}}
7> erlang:universaltime_to_localtime(v(-1)). % UTC 日時をローカルに変換
{{2007,5,28},{6,51,6}}

曜日を取得する

calendar:day_of_the_week の返り値は 1 が月曜、7 が日曜日です。

2> calendar:day_of_the_week({2007,6,1}).
5
3> calendar:day_of_the_week(2007,6,3).
7

エラー(例外)処理

エラー処理を行う

Erlang にも try catch があります。 catch は 例外クラス:例外パターン となり、 例外クラスには次の3つがあります。

error
ランタイムエラー。erlang:error/1,2 または erlang:fault/1,2によって発生する。
exit
exit/1 が呼ばれた場合に発生する。
throw
throw/1 が呼ばれた場合に発生する。
8> try (1 + a) catch _:Reason -> io:format("1 + a is failed, reason: ~p~n", [Reason]) end.
1 + a is failed, reason: badarith
ok
9> try (1 + a) catch error:Reason -> io:format("1 + a is failed, reason: ~p~n", [Reason]) end.
1 + a is failed, reason: badarith
ok
10> try (1 + a) catch throw:Reason -> io:format("1 + a is failed, reason: ~p~n", [Reason]) end.

=ERROR REPORT==== 30-Apr-2007::07:49:40 ===
Error in process <0.222.0> on node 'emacs@localhost' with exit value: {badarith,[{erl_eval,expr,3}]}

** exited: {badarith,[{erl_eval,expr,3}]} **

ファイル

ファイル全体を文字列で取得する。

ファイルの内容を一括でバイナリとして取得します。

get_file_contents(File) ->
    {ok, Binary} = file:read_file(File),
    Binary.

ファイルの内容を一括で文字列として取得します。

get_file_contents(File) ->
    {ok, Binary} = file:read_file(File),
    binary_to_list(Binary).

一行ずつ読み込む

print_with_line(File) ->
    {ok, IoDevice} = file:open(File, read),
    print_with_line(IoDevice, 1),
    file:close(IoDevice).

print_with_line(IoDevice, LineNumber) ->
    case io:get_line(IoDevice, "") of
    eof ->
        ok;
    Line ->
        io:format("~b ~s", [LineNumber, Line]),
        print_with_line(IoDevice, LineNumber + 1)
    end.

ファイル情報を取得する

file:read_file_info(FileName) で file_info レコードを取得できます。

% file_info レコードをインクルード
-include_lib("kernel/include/file.hrl").
% ファイル情報取得
{ok, FileInfo} = file:read_file_info(FileName).
FileInfo#file_info.size.          % ファイルサイズ
FileInfo#file_info.type.          % device | directory | regular | other
FileInfo#file_info.access.        % read | write | read_write | none
FileInfo#file_info.atime.         % 最終アクセス時間
FileInfo#file_info.mtime.         % 最終更新時間
FileInfo#file_info.ctime.         % 作成日時
FileInfo#file_info.mode.          % 8#00400 のようなファイルパーミッション
FileInfo#file_info.links.         % このファイルへのリンク数。
FileInfo#file_info.major_device.  % 0: Aドライブ, 1: Bドライブ
FileInfo#file_info.minor_device.  % Unix のキャラクタデバイスでのみ有効
FileInfo#file_info.inode.         % inode 番号
FileInfo#file_info.uid.           % 所有者のユーザID
FileInfo#file_info.gid.           % 所有者のグループID

リンクかどうか判定する

あるファイルがリンクかどうか判定するには file:read_link(Name) または file:read_link_info(Name) を使用します。


1> file:read_link("/cdrom").
{ok,"media/cdrom"}
2> file:read_link("/usr").
{error,einval}
3> file:read_link_info("/cdrom").
{ok,{file_info,11,
               symlink,
               read,
               {{2007,4,6},{5,38,13}},
               {{2005,1,9},{21,20,27}},
               {{2005,1,9},{21,20,27}},
               41471,
               1,
               2051,
               0,
               12,
               0,
               0}}
4> file:read_link_info("/usr").
{ok,{file_info,4096,
               directory,
               read,
               {{2007,4,5},{6,4,23}},
               {{2005,11,9},{21,16,44}},
               {{2005,11,9},{21,16,44}},
               16877,
               14,
               2051,
               0,
               12500993,
               0,
               0}}

パスがファイルかディレクトリか判定する

filelib:is_dir でディレクトリなら true。 filelib:is_file でディレクトリまたはファイルなら true。 filelib:is_regular でファイルなら true。

4> filelib:is_dir("/etc/yaws").
true
5> filelib:is_dir("/etc/yaws/yaws.conf").
false
6> filelib:is_file("/etc/yaws").
true
7> filelib:is_file("/etc/yaws/yaws.conf").
true
8> filelib:is_regular("/etc/yaws").
false
9> filelib:is_regular("/etc/yaws/yaws.conf").
true

ファイルをコピーする

1> file:list_dir("/tmp/bano").
{ok,["a.txt"]}
2> file:copy("/tmp/bano/a.txt", "/tmp/bano/b.txt").
{ok,1}
3> file:list_dir("/tmp/bano").
{ok,["a.txt","b.txt"]}

ファイルのリネーム

1> file:list_dir("/tmp/bino").
{ok,["a.txt"]}
2> file:rename("/tmp/bino/a.txt", "/tmp/bino/b.txt").
ok
3> file:list_dir("/tmp/bino").
{ok,["b.txt"]}

ファイルの改行コードを変更する

/tmp/a.txt の改行コード \n を、\r\n に変更して /tmp/aa.txt に出力します。

{ok, Bin} = file:read_file("/tmp/a.txt"),
Str = binary_to_list(Bin),
{ok, NewStr, RepCount} = regexp:gsub(Str, "\n", "\r\n"),
file:write_file("/tmp/aa.txt", list_to_binary(NewStr)).

%% 一行で書いてみると、こうなります。
%% 変数を使わない分、element でタプルから目的の値を取り出す必要があります。
file:write_file("/tmp/aa.txt", list_to_binary(element(2, regexp:gsub(binary_to_list(element(2, file:read_file("/tmp/a.txt"))), "\n", "\r\n")))).

現在のディレクトリ(current working directory)を取得・設定する

file:get_cwd/0, file:set_cwd/1 で取得・設定します。 Windows の場合は file:get_cwd("C:") のようにドライブごとのワーキングディレクトリを取得することもできます。

7> file:get_cwd().
{ok,"/home/ancient/public_html/erlang"}
8> file:set_cwd("/tmp").
ok
9> file:get_cwd().
{ok,"/tmp"}

絶対パスを取得する

filename:absname/1 は現在のディレクトリを基準に絶対パスを返します。 filename:absname/2 は基準となるディレクトリを第2引数に指定します。

1> file:get_cwd().
{ok,"/tmp"}
2> filename:absname("a.txt").
"/tmp/a.txt"
3> filename:absname("a.txt", "/var").
"/var/a.txt"

標準入出力

io モジュールでは、IoDevice 引数を省略、または standard_io を指定することで標準入出力を使用できます。

1> io:get_line("? ").
"? "hello.
"hello.\n"
2> io:get_line(standard_io, '? ').
? worlde.
"worlde.\n"
3> io:fwrite("hello\n").
hello
ok
4> io:fwrite(standard_io, "hello\n", []).
hello
ok

file モジュールでは erlang:group_leader/0 の返り値を IoDevice に指定することで標準入出力を使用できます。

全ての IO はグループリーダー経由で行われるため、標準入出力 = グループリーダーとなるのです。

#!/usr/bin/env escript

main(_) ->
    loop(file:read(group_leader(), 1)).

loop(eof) ->
    ok;
loop({ok, Data}) ->
    file:write(group_leader(), Data),
    loop(file:read(group_leader(), 1)).

実行例。C-d で終了します。

~% cd ~/public_html/erlang/sample
~/public_html/erlang/sample% chmod +x standard_io.erl
~/public_html/erlang/sample% ./standard_io.erl
Hello?
Hello?
あいう
あいう

ディレクトリ内のファイルを(再帰的に)処理する

filelib:fold_files/5 という関数があります。 1番目の引数がディレクトリ。 2番目が処理対象か否かを判定するための正規表現。 3番目がサブディレクトリも再帰的に処理するか否か。 4番目が各ファイルに適用する関数。 5番目が初期値。 4番目の引数である関数は引数を2つとります。最初の引数は処理対象のファイル。次は最初は5番目に指定した引数で、それ以降はこの関数の返り値です。

サフィックスが erl のファイルをリストにして取得するには次のように書きます。

>1 filelib:fold_files("/home/ancient/letter/erlang", "\\.erl$", true, fun(X, Acc) -> [X|Acc] end, []).
["/home/ancient/letter/erlang/a/fib.erl",
 "/home/ancient/letter/erlang/chat/_darcs/pristine/chat.erl",
 "/home/ancient/letter/erlang/chat/_darcs/pristine/test_chat.erl",
 "/home/ancient/letter/erlang/chat/chat.erl",
 [...]|...]

ワイルドカード指定でファイルリストを取得する

filelib:wildcard/1 を使います。?(任意の1文字)、*(任意の文字列)、{Item,..}(いずれかに一致)をワイルドカードとして使用できます。

1> filelib:wildcard("/us?/*/lib{GL,Xa*}.so").
["/usr/lib/libGL.so",
 "/usr/lib/libXau.so",
 "/usr/lib/libXaw.so",
 "/usr/lib/libXaw3d.so",
 "/usr/lib/libXaw7.so"]

Web

Web ページを取得する

まずはじめに inets:start() を呼び出しておく必要があります。

1> inets:start().
2> {ok, {Status, Header, Body}} = http:request("http://www.google.co.jp").

HTML エスケープを行う

Yaws の API があります。

1> yaws_api:htmlize("<br>&").
"&lt;br&gt;&amp;"

ネットワーク

サービス名からポート番号を取得する

inet:getservbyname/2 を使用します。サービス名はアトムで指定する必要があります。文字列で指定した場合はエラーになってしまいます。

25> inet:getservbyname(telnet, tcp).
{ok,23}
26> inet:getservbyname(echo, udp).
{ok,7}

FTP でファイルをアップロードする

make から手軽に呼べるので escript で作成します。 <div class="file-name">upload.es</div>

#!/usr/bin/env escript

main(_) ->
    inets:start(),
    {ok, Pid} = inets:start(ftpc, [{host, "ftp.example.com"}]),
    ftp:user(Pid, "user", "password"),
    ftp:cd(Pid, "/public_html/erlang"),
    lists:foreach(fun(File) ->
                          ftp:send(Pid, File)
                  end,
                  ["cookbook.html",
                   "cookbook.css",
                   "index.html"]),
    inets:stop(ftpc, Pid).

コンカレントプログラミング

プロセスを生成する

spawn でプロセスを生成します。返り値は pid() です。

spawn(Fun).
spawn(Node, Fun).
spawn(Module, Function, Args).
spawn(Node, Module, Function, Args).

メッセージを送信する

Pid(プロセスID)または登録名に ! を使用してメッセージを送信します。

Pid ! Message.
registered_name ! Message.

メッセージを受信する

receive でメッセージを受信します。

receive
  Message -> Message
end.

タイムアウトを指定してメッセージを受信する

receive で after を使います。タイムアウトはミリ秒で指定します。 タイムアウトに0を指定した場合は、メッセージがなければ即時タイムアウトします。

receive
  Message -> Message
  after 1000 -> no_message
end.

プロセスを登録する

プロセスを登録することにより、Pid を知らなくても、登録時の名前を指定し てそのプロセスにメッセージを送信することができます。

register(foo, spawn(fun() ->
                      receive {Client, Message} ->
                        Client ! Message
                      end
                    end)).
foo ! {self(), "Hello foo."}.
receive M -> M end.

プロセスをリンクする

あるプロセスでエラーが発生した場合、 リンクされている全てのプロセスが終了します。

リンクされていないプロセスは無関係に処理を継続します。

spawn_link でリンクしたプロセスを開始できます。

次の例では start_without_link はリンクをしていないため、 エラーを発生させても spawn したプロセスは処理を継続します。 start_with_link はリンクをしているため、 エラーを発生させると spawn_link したプロセスも停止します。

-module(sample.link).
-compile(export_all).

start_without_link() ->
    %% リンクしない
    spawn(?MODULE, loop, [10]),
    .timer:sleep(3000),
    .io:format("Let's stop!~n"),
    .erlang:error(stop).

start_with_link() ->
    %% リンクする
    spawn_link(?MODULE, loop, [10]),
    .timer:sleep(3000),
    .io:format("Let's stop!~n"),
    .erlang:error(stop).

loop(0) ->
    ok;
loop(N) ->
    .io:format("hello~n"),
    .timer:sleep(1000),
    loop(N - 1).

プロセスをグルーピングする

pg2 モジュールでプロセスをグルーピング化できます。 プロセスが終了した場合、そのプロセスは自動的にグループから削除されます。

-module(process_group).
-compile(export_all).

start() ->
    pg2:create(group),                          % グループ作成
    erlang:display(pg2:which_groups()),         % グループリストを取得
    Pid1 = spawn(fun p/0),
    pg2:join(group, Pid1),                      % グループ参加
    Pid2 = spawn(fun p/0),
    pg2:join(group, Pid2),                      % グループに参加
    Pid3 = spawn(fun p/0),
    pg2:join(group, Pid3),                      % グループに参加
    erlang:display(pg2:get_members(group)),     % グループメンバを取得
    pg2:leave(group, Pid1),                     % グループから抜ける
    erlang:display(pg2:get_members(group)),     % グループメンバを取得
    Pid2 ! error,                               % エラー終了
    timer:sleep(100),
    erlang:display(pg2:get_members(group)),     % グループメンバを取得
    Pid3 ! finish,                              % 普通に終了
    timer:sleep(100),
    erlang:display(pg2:get_members(group)),     % グループメンバを取得
    pg2:delete(group),                          % グループ削除
    erlang:display(pg2:which_groups()).         % グループリストを取得

p() ->
    receive
        error ->
            1 + a;
        finish ->
            ok
    end.

実行結果

6> process_group:start().
[group]
[<0.65.0>,<0.64.0>,<0.63.0>]
[<0.65.0>,<0.64.0>]

=ERROR REPORT==== 5-May-2007::16:58:31 ===
Error in process <0.64.0> on node 'emacs@localhost' with exit value: {badarith,[{process_group,p,0}]}

[<0.65.0>]
[]
[]
true

ローカルノードのプロセスだけ返す pg2:get_local_members/1 や、ローカルノードのプロセスがあればそれ、なければリモートノードのプロセスを返す pg2:get_closest_pid/1 等もあります。

プロセスを監視する

Erlang では通信相手のプロセスが停止していても ! と receive は失敗しません(登録名で receive を行う場合で、 登録名のプロセスが存在しない場合はエラーになります)。 通信相手のプロセスが停止していないかどうか検知するには erlang:monitor/2 を使います。

1番目の引数は process 固定です。 2番目の引数はプロセスID、登録名、{登録名, ノード名} のいずれかです。 返り値はリファレンスです。 2番目の引数で指定したプロセスが停止していた場合、次のメッセージを受信します。

{'DOWN', monitorの返り値と同一のリファレンス, process, monitorの2番目の引数, プロセスダウン理由}

2番目の要素に monitor の返り値と同一のリファレンスが設定されるのど、 通常それを receive のパターンマッチングに使用します。

-module(monitor_sample).
-compile(export_all).

start() ->
    Pid = spawn(fun loop/0),
    Ref = erlang:monitor(process, Pid),         % 監視を開始する
    RevFun = fun(Ref) ->
                     receive
                         {'DOWN',Ref,process,Pid,Reason} ->
                             io:format("process is down, reason: ~p.~n",
                                       [Reason]);
                         Any ->
                             io:format("~p~n", [Any])
                     after 1000 ->
                             io:format("timeout.~n")
                     end
             end,
    Pid ! {self(), "Noko"},
    RevFun(Ref),                                % ここでは Down していない
    Pid ! {self(), "Quit"},                     % Pid が終了
    RevFun(Ref),                                % ここでは Down している
    Pid ! {self(), "Hitsuji"},
    RevFun(Ref),                                % ここではタイムアウト
    Pid ! {self(), "Hitsuji"},
    Ref2 = erlang:monitor(process, Pid),        % 再度監視を開始する
    RevFun(Ref2).                               % ここでも Down している

loop() ->                                       % サーバプロセスループ
    receive
        {_Pid, "Quit"} ->
            ok;
        {Pid, Any} ->
            Pid ! "Hi, " ++ Any,
            loop()
    end.

実行例

3> monitor_sample:start().
"Hi, Noko"
process is down, reason: normal.
timeout.
process is down, reason: noproc.
ok

ポートスキャン

私の環境では50プロセスぐらいがちょうどいいみたいです。 1プロセスだと約19秒。 2プロセスだと約13秒。 50プロセスだと約8秒。 1000プロセスだと約10秒。 10000プロセスだと約60秒。

-module(port_scan).
-compile(export_all).

m(ProcCount) ->
    timer:tc(?MODULE, main, [ProcCount]).

main(ProcCount) ->
    lists:foreach(fun(_) ->
                          spawn(?MODULE, client, [self()])
                  end,
                  lists:seq(1, ProcCount)),
    init().

init() ->
    loop(lists:seq(1, 16#ffff)).

loop([H|T]) ->
    receive
        {get, C} ->
            C ! H,
            loop(T)
    end;
loop([]) ->
    io:format("finish!~n").

client(Server) ->
    Server ! {get, self()},
    receive
        Port ->
            scan(Port),
            client(Server)
    after 1000 ->
            ok
    end.

scan(Port) ->
    case gen_tcp:connect("localhost", Port, []) of
        {ok, Socket} ->
            gen_tcp:close(Socket),
            io:format("~b~n", [Port]);
        _ ->
            ok
    end.

分散プログラミング

分散ノードを開始する

分散ノードを開始するには -name か -sname オプションでノード名を指定して erl を実行します。 または、net_kernel:start を使用します。

ancient@vmubu:~% erl -name ubu@172.22.10.22
Erlang (BEAM) emulator version 5.5.4 [source] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.5.4  (abort with ^G)
(ubu@172.22.10.22)1>
1> net_kernel:start(['master@172.22.10.15', longnames]).
{ok,<0.32.0>}
(master@172.22.10.15)2>

分散ノードを停止する

net_kernel:stop().

分散ノードへの ping

net_adm:ping で分散ノードの接続確認ができます。 接続できたときは pong、接続失敗時は pang が返ってきます。

net_adm:ping('ubu@172.22.10.22').

RPC

rpc:call で分散ノードの任意の関数を実行することができます。 引数は ノード, モジュール, 関数, 引数 です。

(master@172.22.10.15)78> rpc:call('ubu@172.22.10.22', file, list_dir, ["/usr"]).
{ok,["src","share","sbin","local","lib","include","games","bin","X11R6"]}

リモートノードへのモジュールロード

リモートノードへローカルでロードしたモジュールをロードさせることができます。 ローカルでモジュールのオブジェクトコードを取得し、それを RPC でリモートノードにロードします。

(cho@Macintosh)37> {Module, Binary, Filename} = code:get_object_code(todo).
{todo,<<70,79,82,49,0,0,16,144,66,69,65,77,65,116,111,109,0,0,3,102,0,0,0,82,4,
        116,111,...>>,
      "/Users/ancient/letter/tex/erlang/sample/mnesia/todo.beam"}
(cho@Macintosh)38> rpc:call('babi@localhost', code, load_binary, [Module, Filename, Binary]).
{module,todo}

Yaws

動的なページを作成する

ファイル名の拡張子を yaws にします。 <erl>タグの間に out(Arg) 関数を定義します。 f とい関数を io_lib:format のかわり使用できます。

<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>最初のページ</title>
</head>
<body>
<h1>最初のページ</h1>
<erl>
out(Arg) ->
  {{Y, M, D}, {H, Mi, S}} = erlang:localtime(),
  {html, f("今は~b年~b月~b日~b時~b分~b秒です。", [Y, M, D, H, Mi, S])}.
</erl>
</body>
</html>

セッションデータを使う

yaws_api:find_cookie_val/2 でクッキーセッションを取得し、 yaws_api:cookieval_to_opaque/1 でセッションに保存したデータを取得します。 セッションデータを書きかえるには yaws_api:replace_cookie_session/2 を使用します。

クッキーセッションの新規作成は yaws_api:new_cookie_session/1 で引数はセッションデータです。 クッキーセッションを新規作成した場合は、yaws_api:setcookie/3 でクッキーを設定する HTTP ヘッダの作成し、それをレスポンスの html と一緒にリストにして返す必要があります。

-define(myopaque, {aaa, bbb}).

out(Arg) ->
    {Myopaque, NewHeader} = get_session_data(Arg),
    case NewHeader of
        undefined ->
            {html, "contents"};
        _ ->
            [{html, "contents"}, NewHeader]
    end.

%% 受信プロセスIDを取得します。
get_session_data(Arg) ->
    H = Arg#arg.headers,
    C = H#headers.cookie,
    case yaws_api:find_cookie_val("COOKIE_KEY", C) of
        [] ->                                   % クッキーセッションなし
            Myopaque = #myopaque{aaa=init, bbb=init},
            Cookie = yaws_api:new_cookie_session(Myopaque),
            NewHeader = yaws_api:setcookie("COOKIE_KEY", Cookie, "/"),
            {Myopaque, NewHeader};
        Cookie ->                               % クッキーセッションあり
            case yaws_api:cookieval_to_opaque(Cookie) of
                {ok, Myopaque} ->
                    {Myopaque, undefined}
            end
    end.

外界とのつながり

OS(シェル)コマンドを実行する

os:cmd/1 を使います。引数は文字列か、アトムです。

1> io:format("~s", [os:cmd(pwd)]).
/home/abc/public_html/erlang
ok
2> io:format("~s", [os:cmd("dir")]).
 ドライブ C のボリューム ラベルは IBM_PRELOAD です
 ボリューム シリアル番号は 68F0-7B18 です

 c:\home\abc\public_html\erlang のディレクトリ

2007/05/18  17:25    <DIR>          .
2007/05/18  17:25    <DIR>          ..
2007/05/18  17:34            33,564 cookbook.html
2007/05/18  17:17            33,281 cookbook.html~
2007/05/18  17:08               441 erlang.css
2007/05/18  17:08               130 index.html
2007/05/18  17:08                34 Makefile
2007/05/18  17:08    <DIR>          sample
2007/05/18  17:08               309 upload.es
2007/05/18  17:08    <DIR>          _darcs
               6 個のファイル              67,759 バイト
               4 個のディレクトリ   4,458,496,000 バイトの空き領域
ok

環境変数を取得する

引数がない場合全ての環境変数を返します。

6> os:getenv("SHELL").
"zsh"
7> os:getenv().
["windir=C:\\WINDOWS",
 "VS80COMNTOOLS=C:\\Program Files\\Microsoft Visual Studio 8\\Common7\\Tools\\",
 [...]|...]

環境変数を設定する

8> os:getenv("ENVVV").
false
9> os:putenv("ENVVV", "HELLO").
true
10> os:getenv("ENVVV").
"HELLO"

メタ

Beam ファイルのパスを取得する

code:which/1 にモジュール(atom)を渡せばそのモジュールの Beam ファイルのパスを取得できます。ただし、プレロードされているモジュールは preloaded、カバレジコンパイルされているモジュールは cover_compiled が返されます。存在しないモジュールの場合は non_existing が返されます。

1> code:which(io).
"/usr/lib/erlang/lib/stdlib-1.14.4/ebin/io.beam"
2> code:which(erlang).
preloaded
3> code:which(no_such_module).
non_existing
4> c("/tmp/a", [{outdir, "/tmp/"}]).
{ok,a}
5> code:which(a).
"/tmp/a.beam"
6> cover:compile("/tmp/a.erl").
{ok,a}
7> code:which(a).
cover_compiled

Beam ファイルからソースコードを取得(再構築)する

1> {_Module, Beam, _File} = code:get_object_code(base64),
1> {ok,{_,[{abstract_code,{_,AC}}]}} = beam_lib:chunks(Beam, [abstract_code]),
1> io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).
-file("./base64.erl", 1).

-module(base64).

-export([encode/1, decode/1, mime_decode/1,
         encode_to_string/1, decode_to_string/1,
         mime_decode_to_string/1]).

encode_to_string(Bin) when is_binary(Bin) ->
    encode_to_string(binary_to_list(Bin));
encode_to_string(List) when is_list(List) ->
    encode_l(List).
(以下略)

文字列をコンパイルする

文字列で作ったモジュール(ソース)をコンパイルする方法です。

改行コードは \n にしておかないとだめです。

-module(compile_string).
-export([compile/1, test/0]).

compile(SrcString) ->
    Forms = mk_forms(lists:flatten(SrcString)),
    %% コンパイルする
    {ok, M, B} = compile:forms(Forms),
    %% コンパイルしたものをロードする
    {module, M} = code:load_binary(M, atom_to_list(M), B),
    M.

mk_forms(String) ->
    mk_forms(String, []).

mk_forms([], Acc) ->
    lists:reverse(Acc);
mk_forms(S, Acc) ->
    %% 1文(ピリオドまで)をスキャン
    {done, {ok, Tokens, _Line}, Rest} = erl_scan:tokens([], S, 0),
    %% パースする
    {ok, Parsed} = erl_parse:parse_form(Tokens),
    mk_forms(Rest, [Parsed|Acc]).

%% テスト関数
test() ->
    %% ソース
    S = "-module(fib).
-export([fib/1]).
fib(1) -> 1;
fib(2) -> 1;
fib(N) -> fib(N-1) + fib(N-2).
",
    %% 文字列をコンパイル
    compile(S),
    %% コンパイルしたモジュールの関数を呼び出す
    fib:fib(10).

関数情報を取得する

erlang:fun_info/1 で関数の情報を取得できます。

1> erlang:fun_info(fun erlang:display/1).
[{module,erlang},{name,display},{arity,1},{env,[]},{type,external}]
2> fun() -> X = 1, Y = fun(Y) -> X + Y end, erlang:fun_info(Y) end().
[{pid,<0.59.0>},
 {module,erl_eval},
 {new_index,2},
 {new_uniq,<<146,128,248,136,99,188,48,7,216,172,210,56,139,244,145,225>>},
 {index,6},
 {uniq,72228031},
 {name,'-expr/5-fun-2-'},
 {arity,1},
 {env,[[{'X',1}],
       none,
       {eval,#Fun<shell.21.66499203>},
       [{clause,1,[{var,1,'Y'}],[],[{op,1,'+',{var,1,'X'},{var,1,'Y'}}]}]]},
 {type,local}]

取得できる情報は次のとおりです。

pid
関数を作成したプロセスID。ローカル関数のみ。
module
モジュール名。
new_index
モジュールの関数テーブルのインデックス。ローカル関数のみ。
new_uniq
関数のユニーク値(バイナリ)。ローカル関数のみ。
index
モジュールの関数テーブルのインデックス。ローカル関数のみ。
uniq
関数のユニーク値(整数)。ローカル関数のみ。
name
関数名。
arity
引数の個数
env
関数の環境。ローカル関数のみ。
type
ローカル関数の場合は local、外部関数の場合は external。

DB

ODBC 接続

多くの場合オプションに {scrollable_cursors, off} を指定しないとエラーになります。 connect する都度 odbcserver.exe が起動され disconnect しないとそのプロセスが残ります。

{ok, Ora} = odbc:connect("DSN=bino;UID=hr;PWD=password;", [{scrollable_cursors, off}]),
{selected, _, [{Aiu}]} = odbc:sql_query(Ora, "select 'あいう' from dual"),
io:format("~s~n", [Aiu]),
odbc:disconnect(Ora).

色々

関数の実行時間を計測する

シェルの time コマンドのように実行時間を計測するには timer:tc を使用します。 返り値は {実行時間(マイクロ秒), 関数の返り値} です。

5> timer:tc(fib, fib, [1]).
{3,1}
6> timer:tc(fib, fib, [40]).
{8957808,102334155}
<<36#2EOIWZ250R0HR06ZQ2Z0QG02ICDHOUQFJECQY5IZZYIJKIEUGUF4TD2RFEZQ8NYIP1A:344>>.