2007/05/22

Erlang Emakefile の実行には erl -make

Emakefile の実行は対話環境から make:all(). でもよいけど、コマンドラインの erl -make でもよいです。
Makefile に書くには erl -make がすっきりしてていいですね。escript だなんて戯言を言っていました。


%%-*-erlang-*-
{'*', [debug_info,
{outdir, "../ebin"}]}.

erlc を使ってもいいのですが。

2007/05/20

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

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",
[...]|...]

でも、へんにリンクがあるとだめです。

言語は思考を規定する

やはりそうでしょう。
では、思考の可能性を最も広げてくれる言語は何でしょうか。
もっとも、広げるだけ広げてもそれでよい思考が生まれるわけでもないでしょう。
すくなくとも思考の可能性は生まれる。

間違いを犯しやすいからといって限定する言語は嫌いです。

新しい視野を示してくれる言語は素晴らしい。
想像力しだいで何もかもが自由になる言語は素晴らしい。

2007/05/19

Erlang クエックブックにインデックスを付ける。プロセスバージョン。

やっぱりプロセスを使わないと。


-module(make_index).
-compile(export_all).

start() ->
lists:foreach(fun(X) -> register(X, spawn_link(?MODULE, holder, [[]])) end,
[head, tail, index]),
register(contents, spawn_link(?MODULE, contents, [head])),
{ok, In} = file:read_file("cookbook_src.html"),
loop(binary_to_list(In)).

holder(V) ->
receive
{get, From} ->
From ! lists:reverse(V);
X ->
holder([X|V])
end.

contents(HorT) ->
receive
tail ->
contents(tail);
'終ったよ' ->
file:write_file("cookbook.html",
[begin
P ! {get, self()},
receive X -> X end
end || P <- [head, index, tail]]);
X ->
HorT ! X,
contents(HorT)
end.

loop([]) ->
contents ! '終ったよ';
loop("****index_place****" ++ T) ->
contents ! tail,
loop(T);
loop("<h2>" ++ T) ->
[Ast|Ref] = erlang:ref_to_list(make_ref()),
index ! io_lib:format("<h3><a href='~s'>", [[Ast|Ref]]),
contents ! io_lib:format("<h2><a name='~s'>", [Ref]),
h2(T);
loop("<h3>" ++ T) ->
[Ast|Ref] = erlang:ref_to_list(make_ref()),
index ! io_lib:format("<a href='~s'>", [[Ast|Ref]]),
contents ! io_lib:format("<h3><a name='~s'>", [Ref]),
h3(T);
loop([X|T]) ->
contents ! X,
loop(T).

h2("</h2>" ++ T) ->
index ! "</a></h3>",
contents ! "</a></h2>",
loop(T);
h2([X|T]) ->
index ! X,
contents ! X,
h2(T).

h3("</h3>" ++ T) ->
index ! "</a><br>",
contents ! "</a></h3>",
loop(T);
h3([X|T]) ->
index ! X,
contents ! X,
h3(T).

Erlang クエックブックにインデックスを付ける

Erlang クエックブック にインデックスを付けました。
手でつけるのはめんどうだったので、Erlang でインデックスを付けるようにしました。
そのソースです。P とか R だったらもっと簡単にできる、というのはここではなしで。
if も case もなしで。
さらに、”クエック”って何よ、というのもなしで。


#!/usr/bin/env escript

main(_) ->
{ok, In} = file:read_file("cookbook_src.html"),
{Index, Contents} = f(binary_to_list(In), [], []),
Dest = insert_index(Index, Contents),
file:write_file("cookbook.html", Dest).

insert_index(Index, Contents) ->
P = find_index(Contents, 0),
{X, Y} = lists:split(P, Contents),
X ++ Index ++ (Y -- "*index_place*").

find_index("*index_place*" ++ _, N) ->
N;
find_index([_|C], N) ->
find_index(C, N+1).

f([], Index, Contents) ->
{lists:reverse(Index), lists:reverse(Contents)};
f("<h2>" ++ T, I, C) ->
[Ast|Ref] = erlang:ref_to_list(make_ref()),
I2 = io_lib:format("<h3><a href='~s'>", [[Ast|Ref]]),
H2 = io_lib:format("<h2><a name='~s'>", [Ref]),
h2(T, [I2|I], [H2|C]);
f("<h3>" ++ T, I, C) ->
[Ast|Ref] = erlang:ref_to_list(make_ref()),
I3 = io_lib:format("<a href='~s'>", [[Ast|Ref]]),
H3 = io_lib:format("<h3><a name='~s'>", [Ref]),
h3(T, [I3|I], [H3|C]);
f([X|T], I, C) ->
f(T, I, [X|C]).

h2("</h2>" ++ T, I, C) ->
f(T, ["</a></h3>"|I], ["</a></h2>"|C]);
h2([X|T], I, C) ->
h2(T, [X|I], [X|C]).

h3("</h3>" ++ T, I, C) ->
f(T, ["</a><br>"|I], ["</a></h3>"|C]);
h3([X|T], I, C) ->
h3(T, [X|I], [X|C]).

2007/05/16

Erlang Windows での ~/.erlang

Windows での ~/.erlang は %HOME% ではなく %HOMEDRIVE%%HOMEPATH% から読みとるらしい。

2007/05/15

Emacs の indent-tabs-mode はバッファローカル

Emacs の indent-tabs-mode はバッファローカルなんですね。
グローバルに値を変えたい場合は setq ではなく setq-default を使かいます。


(setq-default indent-tabs-mode nil) ;タブではなくスペースを使う。

2007/05/14

perl - telnetコマンドを自作する の Erlang バージョン

perl - telnetコマンドを自作する の Erlang(escript)バージョンです。


#!/usr/bin/env escript

main([Host]) ->
main([Host, telnet]);
main([Host, Port]) when is_list(Port) ->
main([Host,
case catch list_to_integer(Port) of
{'EXIT', _} ->
list_to_atom(Port);
Num ->
Num
end]);
main([Host, Port]) when is_atom(Port) ->
{ok, Num} = inet:getservbyname(Port, tcp),
main([Host, Num]);
main([Host, Port]) ->
{ok, S} = gen_tcp:connect(Host, Port, []),
spawn_link(fun() -> send_loop(S) end),
recv_loop(S).

send_loop(S) ->
Line = io:get_line(''),
gen_tcp:send(S, Line),
send_loop(S).

recv_loop(S) ->
receive
{tcp, _Port, Data} ->
io:format(Data);
{tcp_closed, _Port} ->
exit(normal)
end,
recv_loop(S).

2007/05/13

Erlang standard_io は group_leader()。あるいは、file:read/2 で標準入力からリードする方法。

Erlang の io モジュールで明示的に標準入出力を指定する場合は standard_io を指定するとなっています。
その standard_io って何でしょう。
io モジュールのソースを見ると IoDevice を省略、または stadard_io を指定すると、group_leader() を IoDevice に指定するようになっています。
group_leader() は erlang モジュールで定義されています。
Erlang の全プロセスはいずれかのプロセスグループのメンバーとなっています。
erlang:group_leader/0 はそのプロセスグループのリーダーを返します。
グループ内の IO 処理はそのグループリーダーを通して行われます。
すなわち、Erlang の各プロセスにとって標準入出力とはグループリーダーそのものなのです。
というわけで、標準入出力を使用する時に group_leader() 呼んでいるのです。

以上が、前置きです。
本題は file:read/2 で標準入出力からリードする方法です。
file:read/2 に standard_io を渡しても {error,einval} が返ってきます。
上記をふまえ、ここでは group_leader() の返り値を渡せばいいのです。


1> file:read(standard_io, 1).
{error,einval}
2> file:read(group_leader(), 1).
a
{ok,"a"}


ちなみに、Common Lisp の標準入出力は *standard-input* と *standard-output* ですね。

2007/05/12

Erlang で if って

使わないよね。case は使うけど、if は本当に使いません。
以下、とりとめのない文章ですが。。。
普段の仕事で(Erlang ではない)if → 条件分岐 → テストケース増 という図式があるので、if がないプログラムはとても好ましいのです。
case も条件分岐なので if と変わりないかもしれませんが、case は条件分岐というよりむしろパターンマッチング感が強いので、心理的抵抗が低いのです。
case も関数呼び出しのパターンマッチングに置き換えられるので使わないで済ませられます。
Common Lisp でも if より and や or の方が気持ちがいいです。
単に if 嫌いなのかもしれません。
というわけで、if 嫌いなあなたに Erlang を。

Windows への TeX のインストール

Windows に TeX をインストールする際のメモです.

まず、あべのり[阿部紀行]さん作成のTeXインストーラ3を使います。
http://www.ms.u-tokyo.ac.jp/~abenori/mycreate/index.html から TeXインストーラ3 0.58 kakuto3_0_58.zip をダウンロードします。
ダウンロードした kakuto3_0_58.zip を解凍して kakuto3\kakuto3.exe を実行します。
インストール先は C:\home\ancient\local\opt\tex を指定。
他は何もかえずに「次へ」ボタンを数回押すと, ファイルのダウンロードとインストールが開始します。
AFPL Ghostscript Setup のダイアログでは Install to directory を c:\home\ancient\local\opt\gs\gs に, Use Windows True Type fonts for Chinese, Japanese and Korean を チェックします。
GSView は全てデフォルトのままでインストール。
うながされるまま再起動します。

次に WinShell をインストールします。
http://www.winshell.de/ から WinShell31.exe をダウンロードして実行します。
インストールが完了したら、WinShell が起動するので、Choose Language で Japanese を選択しましょう。

WinShell の設定を行います。UTF-8 を使用する設定にします。
メニューの「オプション」「全般」をクリックします。
「一般」タブで、言語に Japanese、ファイル形式に Unix を指定します。
「主なTeXプログラムの設定」タブで、
LaTeX: platex、コマンドライン: --kanji=utf8 -src-specials -interaction=nonstopmode "%s.tex"
BibTeX: jbibtex
DVI -> PS: dvipsk
PDFLaTeX: dvipdfmx、コマンドライン: "%s.dvi"
「ユーザ指定プログラム」のタブで、
Tool1: mendex、exeファイル名: mendex、コマンドライン: "%s.idx"
「フォント」タブで、「文章」のフォントを「MS ゴシック」、スクリプトを「日本語」、エンコーディングを「UTF-8」
というように設定します。

dvipdfmx で otf が使えるように設定します。
c:/home/ancient/local/opt/tex/share/texmf/dvipdfm/config/dvipdfmx.cfg の末尾に
f cid-x-local.map
を追記します。
c:/home/ancient/local/opt/tex/share/texmf-local/fonts/map/dvipdfm/local/cid-x-local.map を http://oku.edu.mie-u.ac.jp/~okumura/texwiki/?OTF#z044b3e2 の For dvipdfmx の フォントを埋め込まない場合 にあるとおり内容で作成します。
最後に mktexlsr.exe を実行します。

dviout もいい加減な設定をしておきます。
WinJFont で hminr-h を MS 明朝、hgothr-h を MS ゴシック で Define して Save します。

2007/05/09

FizzBuzz

Erlang でやっておこう。


-module(a).
-compile(export_all).

m() ->
F = fun
(_, 0, 0) ->
"FizzBuzz";
(_, 0, _) ->
"Fizz";
(_, _, 0) ->
"Buzz";
(X, _, _) ->
integer_to_list(X)
end,
erlang:display([F(X, X rem 3, X rem 5) || X <- lists:seq(1, 100)]).


Common Lisp でも

(loop for i from 1 to 100
collect (cond ((zerop (rem i 15)) "FizzBuzz")
((zerop (rem i 5)) "Buzz")
((zerop (rem i 3)) "Fizz")
(t i)))

Common Lisp ならマクロで

(defmacro f (sym &rest args)
`(cond
((and ,@(mapcar #'(lambda (a)
`(zerop (rem i ,(car a))))
args)
,(format nil "~{~a~}" (mapcar #'cadr args))))
,@(mapcar #'(lambda (a)
`((zerop (rem i ,(car a))) ,(cadr a)))
args)
(t ,sym)))

(loop for i from 1 to 100
collect (f i (3 "Fizz") (5 "Buzz")))

(loop for i from 1 to 100
collect (f i (3 "Fizz") (5 "Buzz") (6 "Huzz")))


いずれも出力部は手をぬきました。

Erlang リモートプロセスは register できない

Erlang ファイルサーバの置き換えですが、次のようにリレーしなくても登録するだけでいけるのでは、と思って試してみました。
register で badarg が発生して、動きませんでした。
register ではローカルプロセスまたはローカルポートしか登録できないのでリレーが必要のなですね。


-module(pseudo_file_server2).
-export([start/0]).

start() ->
%% ローカルノードのファイルサーバを登録解除
unregister(file_server_2),
%% リモートノードのファイルサーバの pid を取得
Pid = rpc:call('ubu@172.22.10.22', erlang, whereis, [file_server_2]),
%% そのプロセスをローカルノードのファイルサーバとして登録
register(file_server_2, Pid).

2007/05/08

Erlang ファイルサーバプロセスの置き換え


やはり Erlang はすごいです。
古い方の Erlang 本『Concurrent Programing in ERLANG』の 11.9 Relay Techniques に載っていたものですが、心底驚きました。
Erlang ではファイルIOは file_server_2 という名前で登録されたプロセス経由で行います。
その file_server_2 をリモートノードのプロセスで置き換えることにより全てのファイルIOがリモートノードが走っているマシンのファイルシステム対しての操作になります。


-module(pseudo_file_server).
-export([start/0, relay/1]).

start() ->
%% ローカルノードのファイルサーバを登録解除
unregister(file_server_2),
%% リモートノードへのリレーループプロセスを開始
Pid = spawn(?MODULE, relay, ['ubu@172.22.10.22']),
%% そのプロセスをローカルノードのファイルサーバとして登録
register(file_server_2, Pid).

relay(Node) ->
%% リモートノードのファイルサーバの pid を取得
Pid = rpc:call(Node, erlang, whereis, [file_server_2]),
%% リレーループ開始
loop(Pid).

loop(Pid) ->
%% ファイルサーバとして受信したものをリモートノードへリレー
receive
Any ->
Pid ! Any
end,
loop(Pid).

次は実行例です。

2> pseudo_file_server:start().
ture
3> file:write_file("/tmp/a.txt", "hello").
ok

/tmp/a.txt はローカルのファイルシステムではなく、リモートマシンファイルシステム上さ作成されます。
なんと素晴しい。
本の中ではファイルシステムを持たない組み込みシステム等でも、このテクニックによりファイルシステムを使える、とういような説明でした。
『Concurrent Programing in ERLANG』は第一版が1993年、第二版が1996年に出版されています。これまでの間、私たちはいったい何をしていたのでしょうか。
単にマルチコアの普及により Erlang のアーキテクチャが生きるようになっただけなのでしょうが。
素敵ですね。

GWのまとめ

GWのまとめ

髪を切った。
近所の公園がオープンした。
姪の百恵ちゃん誕生。
ポークスペアリブ。
ずっと早起き。

たいしてまとめるべきものもないな。

AUCTeX の設定

~/.emacs


;;;;AUCTeX
(load "auctex.el" nil t t)
(load "preview-latex.el" nil t t)
(setq TeX-auto-save t)
(setq TeX-parse-self t)
(setq-default TeX-master nil)
(setq TeX-default-mode 'japanese-latex-mode)
(setq japanese-TeX-command-default "pTeX")
(setq japanese-LaTeX-command-default "pLaTeX")
(setq japanese-LaTeX-default-style "jsarticle")
(setq TeX-print-command "%(o?)dvips -P%p %r %s")
(add-hook 'TeX-mode-hook
(function
(lambda ()
(TeX-source-specials-mode 1)
;; (add-to-list 'TeX-output-view-style
;; '("^dvi$" "." "dvipdfmx %dS %d && acroread %s.pdf"))
(local-set-key "\C-c\C-i" 'TeX-complete-symbol)
;; (local-set-key "\C-@" 'TeX-next-error))))
)))

色付けおかしいですね。シングルクォートが文字列あつかいされている。

Yaws 組み込みモードの設定

Yaws 組み込みモードで他のマシンからアクセスできるようにするには、listen と servername の指定も必要です。


SC = [{port, 8880}, % ポート番号
{servername, "www.example.com"}, % サーバ名
{listen, {0, 0, 0, 0}}], % リッスンアドレス
GC = [{logdir, "/tmp"}], % グローバル設定
yaws:start_embedded("/var/www", SC, GC). % 組み込みモードでスタート

2007/05/07

Erlang io の引数に lists:flatten は必要ない

io_lib:format/2 の返り値が、単なる文字列ではなく、文字と文字列のリストになるのは不便なのに何故だろう、と思っていました。
ふとしたことで文字と文字列のリストそのままで出力できることに気づきました。
lists:flatten はいらないのですね。
file:write_file/2 でもネストしたリストそのままで大丈夫でした。


49> io_lib:format("Hello ~s!~n", ["World"]).
[72,101,108,108,111,32,"World",33,"\n"]
50> io:format([72,101,108,108,111,32,"World",33,"\n"]).
Hello World!
ok

そういえば、次のようなのもOKなんですよね。

51> "Hello" " " "World" "!\n".
"Hello World!\n"

2007/05/06

モジュールの attributes を取得する方法

モジュールの attributes とは - で始まっているもの。
任意の属性を定義できる。


-lisp(yes).

とあると、次のように beam_lib:chunks/2 を使って取得できる。

5> beam_lib:chunks(lisp_bif, [attributes]).
{ok,{lisp_bif,[{attributes,[{lisp,[yes]},
{vsn,[302273399647271765179114790951201449381]}]}]}}

vsn はモジュールのバージョンで、指定しない場合はモジュールのチェックサムになる。

SPAMフィルタのように学習するRSSリーダ

SPAM フィルタのように学習する RSS リーダってないかしら?

エントリ毎に好き嫌いが指定して、学習していく。
学習結果に基づくスコア順に表示。
突然変異でスコアの低いものが上位に表示され、たまには他の分野の情報も目にできる。
のような RSS リーダが欲しい。

もはやブログが多すぎて、そろそろフィルタリングする時期です。

2007/05/05

Erlang で Comet

みかログさんでErlangでCometが書かれていますが、同様に Erlang で Comet です。こちらの方が随分長いソースになっていますが。

クライアント(ブラウザ、セッション)毎に受信プロセスを常駐させます。
送信されたメッセージは全ての受信プロセスに送信され、受信プロセスはクライアントが受信待ちの場合、クライアントへメッセージを返します。

受信プロセスにはタイムアウト管理プロセスがリンクしてあり、一定時間クライアントへの送信を行わない場合、受信プロセスは exit します。

クライアント1つにつき、受信プロセスとタイムアウト管理プロセス(あとおそらく、Yaws のリクエスト処理プロセス)が常駐します。プロセス大盤振る舞いです。



ファイルの構成は次のようになっています。


chat.erl

プログラム本体。Yaws の起動も行ないます。

www/index.yaws

チャットのページ

www/send.yaws

メッセージ送信用ページ

www/receive.yaws

メッセージ受信用ページ

www/js/prototype.js

Ajax 通信等で利用する prototype.js


そのうち、OTP 流に gen_server なんかを使うようにしましょう。そのうちに。

chat.erl

-module(chat).
-compile(export_all).

%%-define(LOG(X), error_logger:info_msg("~p = ~p~n", [??X, X])).
-define(LOG(X), ok).

-define(COOKIE_KEY, "chat").

-include_lib("yaws/include/yaws_api.hrl").

-record(receive_status, {pid, ref, waiting=false, messages=[], timeout_pid}).

%% Yaws と チャットサーバを開始します。
start() ->
LogDir = filename:absname("log"),
DocRoot = filename:absname("www"),
start(LogDir, DocRoot).

%% Yaws と チャットサーバを開始します。
start(LogDir, DocRoot) ->
pg2:create(receive_proc_group), % 受信プロセスグループを作成
start_yaws(LogDir, DocRoot). % Yaws 開始

%% Yaws を開始します。
start_yaws(LogDir, DocRoot) ->
SC = [{port, 9999}], % サーバ設定
GC = [{logdir, LogDir}], % グローバル設定
yaws:start_embedded(DocRoot, SC, GC). % 組み込みモードでスタート

%% 受信プロセス初期化
receive_init(S) ->
Pid = spawn_link(?MODULE, receive_timeout, []),
receive_loop(S#receive_status{timeout_pid=Pid}).

%% 受信プロセス
receive_loop(S) ->
receive
{ping, Ref, Pid} -> % 受信プロセスの生存確認
S2 = S,
Pid ! {pong, Ref};
{wait, Ref, Pid} -> % クライアントが受信待ち
?LOG({wait, Ref, Pid}),
S2 = S#receive_status{pid=Pid, ref=Ref, waiting=true };
{message, Message} -> % メッセージが来た
?LOG({message, Message}),
S2 = S#receive_status{messages=[Message|S#receive_status.messages]}
end,
?LOG(S2),
%% 受信待ち状態かつメッセージがあるときのみ送信します。
case S2 of
#receive_status{waiting=false} -> % 受信待ちではない
?MODULE:receive_loop(S2);
#receive_status{messages=[]} -> % メッセージがない
?MODULE:receive_loop(S2);
_ -> % 受信待ちでメッセージあり
S#receive_status.timeout_pid ! ok, % タイムアウトをリセット
S2#receive_status.pid ! {message, % メッセージ送信
S2#receive_status.ref,
S2#receive_status.messages},
?MODULE:receive_loop(
S2#receive_status{waiting=false, messages=[]})
end.

%% 受信プロセスのタイムアウト管理プロセス
receive_timeout() ->
receive
_ -> % タイムアウトをリセット
receive_timeout()
after
1000 * 60 * 10 -> % 10分で受信プロセスは終了
erlang:exit(timeout)
end.

%% send.yaws から呼ばれます。
send_message(A) ->
?LOG(send_message),
{ok, User} = yaws_api:queryvar(A, "user"),
{ok, Message} = yaws_api:queryvar(A, "message"),
M = timestamp() ++ "(" ++ User ++ ")" ++ Message,
Pids = pg2:get_members(receive_proc_group), % 受信待ちプロセスを取得
%%error_logger:info_msg("~p", [length(Pids)]),
lists:foreach(fun(P) -> P ! {message, M} end, Pids), % 1つずつ送信
%%error_logger:info_msg("finish!", []).
{html, "ok"}.

%% receive.yaws から呼ばれます。
receive_message(A) ->
?LOG(receive_message),
{Pid, Header} = get_receive_proc(A),
Ref = make_ref(),
Pid ! {wait, Ref, self()}, % 待ってるよ、と送信。
receive
{message, Ref, Messages} -> % メッセージ受信。
?LOG({message, Ref, Messages}),
receive_message_response(edit_response(Messages), Header)
after 10 * 60 * 1000 -> % 10分でタイムアウトします。
receive_message_response(
io_lib:format(
"({'st': 'ok', 'mes': '~s メッセージがありません。
'})",
[timestamp()]),
Header)
end.

%% レスポンスの編集を行います。
edit_response(Messages) ->
M = lists:foldl(fun(X, Acc) -> yaws_api:htmlize(X) ++ "
" ++ Acc end,
[], Messages),
f("(~s)", [json:encode({struct, [{st, "ok"}, {mes, M}]})]).

%% クッキーをセットしない場合のレスポンス
receive_message_response(Html, undefined) ->
{html,Html};
%% クッキーをセットする場合のレスポンス
receive_message_response(Html, Header) ->
[{html, Html}, Header].

%% 受信プロセスIDを取得します。
get_receive_proc(A) ->
H = A#arg.headers,
C = H#headers.cookie,
case yaws_api:find_cookie_val(?COOKIE_KEY, C) of
[] ->
create_new_receive_proc();
Cookie ->
case yaws_api:cookieval_to_opaque(Cookie) of
{ok, Pid} ->
case check_receive_proc(Pid) of
ok ->
{Pid, undefined};
_ ->
create_new_receive_proc()
end;
_ ->
create_new_receive_proc()
end
end.

%% 受信プロセスが生きていることを確認します。
check_receive_proc(Pid) ->
Ref = make_ref(),
Pid ! {ping, Ref, self()},
receive
{pong, Ref} ->
ok
after
1000 ->
error
end.

%% 受信プロセスを作成します。
create_new_receive_proc() ->
Pid = spawn(?MODULE, receive_init, [#receive_status{pid=self()}]),
pg2:join(receive_proc_group, Pid), % プロセスグループに参加
Cookie = yaws_api:new_cookie_session(Pid),
{Pid, yaws_api:setcookie(?COOKIE_KEY, Cookie, "/")}.


%% タイムスタンプも欲しいよね。
timestamp() ->
{{_Y, _M, _D}, {H, Mi, S}} = erlang:localtime(),
f("~b:~b:~b", [H, Mi, S]).

%% 文字列化の関数です。
f(Format, Args) ->
lists:flatten(io_lib:format(Format, Args)).


www/index.yaws

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>チャット</title>
<style>
#screen {
border: 1px solid black;
height: 20em;
overflow: scroll;
}
</style>
<script src="js/prototype.js"></script>
<script>
function debug(x) {
$('debug').innerHTML = x;
}
function send() {
$('message').select();
var param = 'message=' + $F('message') + '&user=' + $F('user');
var ajax = new Ajax.Request(
"send.yaws",
{
method: 'get',
parameters: param
});
}
function receive_call_back(res) {
debug(res.responseText);
eval("var obj = " + res.responseText);
if (obj.st == 'ok') {
$('screen').innerHTML = obj.mes + $('screen').innerHTML;
receive();
} else {
alert("error: " + res.responseText);
}
}
function receive() {
var param = 'user=' + $F('user');
var ajax = new Ajax.Request(
"receive.yaws",
{
method: 'get',
parameters: param,
onComplete: receive_call_back
});
}
Event.observe(window, 'load', receive, false);
</script>
</head>
<body>
<h1>チャット</h1>
<form action="javascript:send()">
名前 <input type="text" id="user"><br>
メッセージ <input type="text" id="message" size="80">
<input type="submit" value="送信">
</form>
<div id="screen"></div>
<hr>
デバッグ
<div id="debug"></div>
</body>
</html>


www/send.yaws

<erl>
out(Arg) ->
chat:send_message(Arg).
</erl>


www/receive.yaws

<erl>
out(Arg) ->
chat:receive_message(Arg).
</erl>



受信待ちプロセスをたくさん作ったらどうなるか、と思ってやって(/usr/sbin/ab -t 1000 -n 1000 -c 1000 http://localhost:9999/receive.yaws)みたら emfile エラーが発生しました。

sudo vi limits.conf で次の2行を追加して、再起動しました(OS は Debian です)。

* soft nofile 50000
* hard nofile 50000

もう一度やってみると、全てのリクエストがコネクトして受信待ちになるまでにとても時間がかかりました。その間、CPU も DISK もそれほど使っているような感じではありませんでした。どこでひっかかっているのかしら。

それはともかく、プロセスを怒涛の羊のように作るプログラミングはなかなか楽しいです。

2007/05/04

darcsum(Emacs の darcs モード)

darcsum で最初に record するときの email のデフォルト値。
~/.emacs


;;;;;Mail
(setq user-mail-address "user@example.com")
(setq user-full-name "名前")

Erlang のマクロ

Erlang のマクロは C のマクロに似ています。
-define で定義し使用するときは ? を付けます。
モジュール名やファイル名を表す定義済マクロがあります。
定義時にマクロに引数に ?? を付けるとその引数をそのまま文字列として展開します。EUnit で便利に使われています。
-ifdef, -ifndef, -else, -endif で条件付きコンパイルができます。


-module(macro_sample).
-compile(export_all).

-define(MacroValue, "Not Debug!").
-define(MacroPreDef, io:format("MODULE: ~p, MODULE_STRING: ~p, FILE: ~p, LINE: ~p, MACHINE: ~p~n", [?MODULE, ?MODULE_STRING, ?FILE, ?LINE, ?MACHINE])).

-ifdef(debug).
-undef(MacroValue).
-define(MacroValue, "Debug Mode!").
-define(LOG(X), io:format("{~p,~p}: ~s = ~p~n", [?MODULE,?LINE,??X,X])).
-else.
-define(LOG(X), ok).
-endif.

-ifndef(debug).
-define(MM, not_debug).
-else.
-define(MM, debug).
-endif.

main() ->
io:format("~p, ~p~n", [?MacroValue, ?MM]),
?MacroPreDef,
?LOG(1 + 2),
?LOG(begin {H, M, S} = time(), lists:flatten(io_lib:format("~p:~p:~p", [H, M, S])) end).

普通にコンパイルした場合

(emacs@localhost)83> c("/home/ancient/letter/erlang/junk/macro_sample", [{outdir, "/home/ancient/letter/erlang/junk/"}]).
{ok,macro_sample}
(emacs@localhost)84> macro_sample:main().
"Not Debug!", not_debug
MODULE: macro_sample, MODULE_STRING: "macro_sample", FILE: "/home/ancient/letter/erlang/junk/macro_sample.erl", LINE: 24, MACHINE: 'BEAM'
ok

{d, debug} として debug を定義してコンパイルした場合

(emacs@localhost)85> c("/home/ancient/letter/erlang/junk/macro_sample", [{outdir, "/home/ancient/letter/erlang/junk/"}, {d, debug}]).
{ok,macro_sample}
(emacs@localhost)86> macro_sample:main().
"Debug Mode!", debug
MODULE: macro_sample, MODULE_STRING: "macro_sample", FILE: "/home/ancient/letter/erlang/junk/macro_sample.erl", LINE: 24, MACHINE: 'BEAM'
{macro_sample,25}: 1 + 2 = 3
{macro_sample,26}: begin { H , M , S } = time ( ) , lists : flatten ( io_lib : format ( "~p:~p:~p" , [ H , M , S ] ) ) end = "5:22:54"
ok


コンパイル時に P オプションを付けるとマクロ展開後のファイルが作成されます。

(emacs@localhost)87> c("/home/ancient/letter/erlang/junk/macro_sample", [{outdir, "/home/ancient/letter/erlang/junk/"}, {d, debug}, 'P']).
** Warning: No object file created - nothing loaded **
ok

マクロ展開後のファイル

-file("/home/ancient/letter/erlang/junk/macro_sample.erl", 1).

-module(macro_sample).

-compile(export_all).

main() ->
io:format("~p, ~p~n", ["Debug Mode!",debug]),
io:format("MODULE: ~p, MODULE_STRING: ~p, FILE: ~p, LINE: ~p, MACHI"
"NE: ~p~n",
[macro_sample,
"macro_sample",
"/home/ancient/letter/erlang/junk/macro_sample.erl",
24,
'BEAM']),
io:format("{~p,~p}: ~s = ~p~n", [macro_sample,25,"1 + 2",1 + 2]),
io:format("{~p,~p}: ~s = ~p~n",
[macro_sample,
26,
"begin { H , M , S } = time ( ) , lists : flatten ( io_l"
"ib : format ( \"~p:~p:~p\" , [ H , M , S ] ) ) end",
begin
{H,M,S} = time(),
lists:flatten(io_lib:format("~p:~p:~p", [H,M,S]))
end]).

2007/05/03

Erlang での unwind-protect

Common Lisp での次の式は


(unwind-protect (print "protected") (error error))

Erlang では次のようになります。

try erlang:error(error) after io:format("protected") end.

with-open-file(むしろ call-with-input-file でしょうか) 相当の実装は次のようになります。

-module(with).
-compile(export_all).

open_file(File, Fun, Mode) ->
{ok, IoDevice} = file:open(File, Mode),
try Fun(IoDevice)
after
file:close(IoDevice)
end.

test() ->
File = "/tmp/a.txt",
open_file(File,
fun(IoDevice) ->
io:write(IoDevice, "Hello")
end,
write),
open_file(File,
fun(IoDevice) ->
io:get_line(IoDevice, "")
end,
read).

2007/05/02

はてなから移行

はてなから移行しました。

[Erlang][Yaws] Yaws の実行まで



はじめに



Yaws は Erlang で書かれた Web サーバー。
http://yaws.hyber.org/

インストール



Yaws を http://yaws.hyber.org/download/ からダウンロードし展開する。

http://yaws.hyber.org/wiki/showPage.yaws?node=YawsAndWin32 から Windows install scripts(win32.tar.gz)をダウンロードし、Yaws を展開したディレクトリの中で展開する。


インストールの過程で Cygwin の方の find コマンドが動いてしまわないように環境変数 PATH を一時的に変更しておいて、インストールを実行する。

SET PATH=c:\WINDOWS;c:\WINDOWS\system32
install.cmd ALL


セットアップ



設定ファイル c:\Documents and Settings\ancient\Application Data\yaws-1.66\yaws.conf の # And then an ssl server 以降の行をコメントアウトし、とりあえず SSL は無効にする。
examples ディレクトリを My Documents/yaws にコピーしておく。

実行



実行時も同様に find の問題があるため、次のように PATH を設定してから yaws -i と実行する。なお -i でインタラクティブに実行。-D でデーモンとして実行。

C:\Documents and Settings\ancient>SET PATH=C:\Program Files\yaws-1.66;c:\WINDOWS;c:\WINDOWS\system32
C:\Documents and Settings\ancient>yaws -i
Eshell V5.5.2 (abort with ^G)
1>
=INFO REPORT==== 28-Dec-2006::17:19:46 ===
Yaws: Using config file C:/Documents and Settings/ancient/Application Data/yaws-1.66/yaws.conf
1> yaws:Add path "c:/Documents and Settings/ancient/My Documents/yaws/examples/ebin"
1> yaws:Add path "c:/Program Files/yaws-1.66/examples/ebin"
1> yaws:Running with id=undefined
Running with debug checks turned on (slower server)
Logging to directory "c:/Documents and Settings/ancient/My Documents/yaws/log"
1>
=INFO REPORT==== 28-Dec-2006::17:19:47 ===
Yaws: Listening to 0.0.0.0:8000 for servers - http://OUTIS:8000 under c:/Documents and Settings/ancient/My Documents/yaws/www
- http://localhost:8000 under c:/tmp
1>


http://ホスト名:8000/ で My Documents/yaws/www 以下にアクセスできる。

[Erlang][ErlyWeb] http://erlyweb.org/ です。



はじめに



Erlang 版 Ruby on Rails みたいなものだろうか。
チュートリアルがあるので、遊んでみる。

セットアップ



まずは、MySQL と Yaws をインストールする。

そして ErlyWeb の最新 をダウンロード、展開し、
-erltl-0.9.1
-erlydb-0.7.3
-erlyweb-0.3
-mysql-driver-0.9.7
を Erlang の lib ディレクトリ(C:\Program Files\erl5.5.2\lib) にコピーする。

アプリケーションの作成



アプリケーション用のディレクトリ(C:\apps)を作成しておく。

Erlang shell を起動(M-x run-erlang)し、アプリケーションを作成する。1番目の引数はアプリケーション名、2番目の引数はアプリケーション用のディレクトリ名。

14> erlyweb:create_app("music", "C:\\apps").
info:erlyweb_util:30: creating "C:\\apps/music"
info:erlyweb_util:30: creating "C:\\apps/music/src"
info:erlyweb_util:30: creating "C:\\apps/music/src/components"
info:erlyweb_util:30: creating "C:\\apps/music/ebin"
info:erlyweb_util:30: creating "C:\\apps/music/www"
info:erlyweb_util:65: creating "C:\\apps/music/src/music_app_view.et"
info:erlyweb_util:65: creating "C:\\apps/music/src/music_app_controller.erl"
info:erlyweb_util:65: creating "C:\\apps/music/www/index.html"
info:erlyweb_util:65: creating "C:\\apps/music/www/style.css"
ok


yaws.conf に次を追記する(本当はどう記述するのがいいんだろう?)。
>|
<server localhost>
port = 8888
listen = 0.0.0.0
docroot = /apps/music/www
appmods = <"/music", erlyweb>
<opaque>
appname = music
</opaque>
</server>
|<

アプリケーションにの動



Yaws をインタラクティブモードで起動(yaws -i)する。
http://localhost:8888/ にブラウザでアクセスするとトップページが表示される。

テーブルの作成



MySQL で music という新しいスキームを作成する。
チュートリアルにあるとおり musician テーブルを作成し、レコードをインサートする。

コンポーネントの作成



Yaws の Eshell で次の関数を実行し、musician のコンポーネントを作成する。

1> erlyweb:create_component("musician", "/apps/music").
info:erlyweb_util:65: creating "/apps/music/src/components/musician.erl"
info:erlyweb_util:65: creating "/apps/music/src/components/musician_controller.erl"
info:erlyweb_util:65: creating "/apps/music/src/components/musician_view.erl"
ok

モデルとビューとコントローラの3ファイルが作成される。

コンポーネントのコンパイル



Yaws の Eshell で次の関数を実行し、musician のコンポーネントをコンパイルする。

2> erlydb:start(mysql, [{hostname, "localhost"}, {username, "root"}, {password, "password"}, {database, "music"}]).
mysql_conn:609: greeting version "5.0.27-community-nt" (protocol 10) salt "N$fV[H7y" caps 41516 serverchar <<95,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0>>salt2 "}(M,4][0{=\\:"
mysql_auth:187: mysql_auth send packet 1: <<5,162,0,0,64,66,15,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,114,111,111,116,0,20,100,108,48,224,97,31,19,109,153,47,33,191,183,196,29,210,251,67,99,29>>
mysql_conn:418: fetch <<"use music">> (id <0.61.0>)
ok
3> erlyweb:compile("/apps/music", [{erlydb_driver, mysql}]).
debug:erlyweb:114: Compiling app controller: music_app_controller.erl
debug:erlyweb:124: Trying to invoke music_app_controller:before_compile/1
debug:erlyweb:286: Compiling ErlTL file "music_app_view"
debug:erlyweb:289: Compiling Erlang file "music_app_controller"
debug:erlyweb:289: Compiling Erlang file "musician_view"
debug:erlyweb:289: Compiling Erlang file "musician_controller"
debug:erlyweb:289: Compiling Erlang file "musician"
debug:erlyweb:143: Generating ErlyDB code for models: "musician.erl "
mysql_conn:418: fetch <<"show tables">> (id <0.61.0>)
mysql_conn:418: fetch <<"describe musician">> (id <0.61.0>)
debug:erlyweb:166: Trying to invoke music_app_controller:after_compile/1
{ok,{{2006,12,29},{15,17,27}}}
4>


musician コンポーネントにアクセスしてみる



ブラウザで http://localhost:8888/music/musician にアクセスすると、musican テーブルに登録しておいたデータが表示される。編集、追加、削除が可能となっている。

おわりに



簡単だ。
しかし、どうやってカスタマイズするんだろう。それは、また今度だね。あと日本語のとりあつかい。ちょっと試した限りではエラーとなった。
"Final words" には、Erlang は Ruby よりシンプルであるがため、ErlyWeb も Rails より自然とシンプルになる、というようなことが書かれている。確かに Erlang はかなりシンプルな言語だと感じる。だから気に入ったのかな。


[Erlang][ErlyDB] ErlyDB



ErlyDB をちょっといじってみる.
テーブルに対応するモジュールを作成し, データベースにコネクトして, code_gen とするとテーブルに対応するモジュールに色々と関数が作成される.

テーブルに対応するモジュール

-module(musician).


テストモジュール

-module(try_erlydb).
-export([start/0]).

start() ->
erlydb:start(mysql, [{hostname, "localhost"}, {username, "root"}, {password, "password"}, {database, "music"}]),
erlydb:code_gen(mysql, [musician]),
io:format("~p~n", [musician:find({name, '=', "Ringo Star"})]),
io:format("~p~n", [musician:find({name, like, "R%"})]).


実行結果

1> c("c:/home/ancient/letter/erlang/a/musician", [{outdir, "c:/home/ancient/letter/erlang/a/"}]).
{ok,musician}
2> c("c:/home/ancient/letter/erlang/a/try_erlydb", [{outdir, "c:/home/ancient/letter/erlang/a/"}]).
{ok,try_erlydb}
3> try_erlydb:start().
mysql_conn:609: greeting version "5.0.27-community-nt" (protocol 10) salt "}\\8?tKZc" caps 41516 serverchar <<95,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0>>salt2 "a@XyxoEI~`G!"
mysql_auth:187: mysql_auth send packet 1: <<5,162,0,0,64,66,15,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,114,111,111,116,0,20,244,135,64,58,95,79,42,15,19,223,3,156,183,234,11,52,19,242,201,53>>
mysql_conn:418: fetch <<"use music">> (id <0.48.0>)
mysql_conn:418: fetch <<"show tables">> (id <0.48.0>)
mysql_conn:418: fetch <<"describe musician">> (id <0.48.0>)
mysql_conn:418: fetch <<"SELECT * FROM musician WHERE (name = 'Ringo Star')">> (id <0.48.0>)
[{musician,false,
4,
<<"Ringo Star">>,
{date,{1940,7,7}},
<<"drums">>,
<<"Richard Starkey, known by his stage name\r\n Ringo Starr, is an English popular musician,\r\n singer, and actor, best known as the\r\n drummer for The Beatles...">>}]
mysql_conn:418: fetch <<"SELECT * FROM musician WHERE (name LIKE 'R%')">> (id <0.48.0>)
[{musician,false,
4,
<<"Ringo Star">>,
{date,{1940,7,7}},
<<"drums">>,
<<"Richard Starkey, known by his stage name\r\n Ringo Starr, is an English popular musician,\r\n singer, and actor, best known as the\r\n drummer for The Beatles...">>}]
ok



[DO]キャベツとコンビーフの重ね蒸し焼き



今日はキャベツとコンビーフの重ね蒸し焼きを作りました.
+ キャベツ半分とコンビーフ1缶を調達.
+ ダッチオーブンにキャベツとコンビーフを重ねていく.
+ コンソメを小匙1くらいふりかけ, 黒胡椒を少々.
+ 蓋をして30分でできあがり.
シンプルかつチープで悪くないメニューでした.

[Erlang] Erlang の本


あまりないみたい。
とりあえずこれを注文した。
asin:013508301X:detail

[Erlang] Oniguruma(Erlang ドライバ)


正規表現ライブラリの Oniguruma のドライバを Will さんが公開した.
Win32 のバイナリが http://glozer.net/code/oregexp-1.0-win32.zip からダウンロードできる.

ところで, Erlang のロードパスってどうなっているんだろうと思って調べてみた. Erlang では load path ではなく code path と言うらしい.
code モジュールで管理できる. 正確には code モジュールはコードサーバのインターフェース. コードパスの設定, モジュールのロード等ができる.
Eshell で i(). とやると code_server がいる. この code_server がモジュールのロード管理等のプロセスなんだね. Erlang だな...
code:add_path(Dir) でもいいが, 起動時のコマンドラインオプション -pa Dir でもコードパスを指定できる.

ということで, .emacs で oregexp-1.0-win32.zip の解凍先/ebin をコードパスに追加するように

(setq inferior-erlang-machine-options
'("-pa" "c:/home/ancient/letter/erlang/lib/oregexp-1.0/ebin"))

と書いて M-x run-erlang すればよい.

Oniguruma は各種文字エンコードに対応しているため, 次のように日本語も大丈夫.

15> fun() ->
{ok, R} = oregexp:parse("い(.)", [sjis]),
{matches, [M|_]} = oregexp:scan("あいうえお", R),
oregexp:free(R),
{value, G} = oregexp:group(1, M),
io:format("~s~n", [G])
end().

ok


[Erlang][Yaws] Meadow から Yaws を実行



yaws.bat を作成しておく.

SET PATH=C:\Program Files\yaws-1.66;c:\WINDOWS;c:\WINDOWS\system32
cd C:\Program Files\yaws-1.66
yaws -i


.emacs に追加.

(defun run-yaws ()
(interactive)
(let ((inferior-erlang-machine "C:/Docume~1/ancient/デスクトップ/yaws.bat"))
(run-erlang)))


M-x run-yaws

[Erlang] ErlyWeb - Blog Tutorial その1



ErlyWeb - Blog Tutorial にあるチュートリアルをやってみる.

アプリケーションの作成


ディレクトリ c:/apps を作成.

erlyweb:create_app("blog", "/apps").


テーブルの作成


MySQL でテーブルを作成する.

create table entries (
id integer auto_increment primary key,
title varchar(100),
body text,
author varchar(100)
);


コンポーネントの作成



erlyweb:create_component("entries", "/apps/blog").


start モジュールの作成


MySQL への接続とアプリケーションのコンパイルを行うためのモジュールを作成する.

-module(blog_start).
-compile(export_all).

boot() ->
boot(true).

boot(false) ->
compile();
boot(true) ->
mysql_start(),
compile().

mysql_start() ->
erlydb:start(mysql, [{hostname, "localhost"},
{username, "root"},
{password, "password"},
{database, "blog"}]).

compile() ->
erlyweb:compile("/apps/blog", [{erlydb_driver, mysql}]).

コンパイルして, blog_start:boot(). を実行.

Yaws の設定


yaws.conf に追記し, Yaws を再起動(init:restart()), blog_start:boot().
http://localhost:8889/blog/entries にアクセスできるようになるので, いくつか ertries を create new しておく.

<server localhost>
port = 8889
listen = 0.0.0.0
docroot = /apps/blog/www
appmods = <"/blog", erlyweb>
<opaque>
appname = blog
</opaque>
</server>


フロントページの作成


全エントリーを表示するフロントページを作成する.

controller

c:/apps/blog/src/components にある entries_controller.erl を編集.

-module(entries_controller).
%%-erlyweb_magic(on).
-export([index/1]).

index(A) ->
Entries = entries:find(), % 全レコード取得
{data, Entries}. % 全レコードをビューへ


c:/apps/blog/src/components にある entries_controller.erl, entries_view.erl を編集.

view

c:/apps/blog/src/components にある entries_view.erl, entries_view.erl を編集.

-module(entries_view).
%%-erlyweb_magic(on).
-export([index/1]).

index(Data) ->
entries_show:show_entries(Data).


et

c:/apps/blog/src/components/entries_show.et を作成.

<%@ show_entries(Entries) %>
<div class="entries"><% [entry(E) || E <- Entries] %></div>

<%@ entry(Entry) %>
<div class="entry">
<div class="title"><% helpers:value(entries:title(Entry)) %></div>
<div class="body"><% helpers:value(entries:body(Entry)) %></div>
<div class="author">by: <% helpers:value(entries:author(Entry)) %></div>
</div>


helper

entries:title() が undefined を返してくる場合等に対応するために c:/apps/blog/src/helpers.erl を作成.

-module(helpers).
-export([value/1]).

value(Val) ->
case Val of
undefined ->
"";
_ ->
Val
end.


スタイルシート

あと c:/apps/blog/www/style.css にちょっと追記.

div.entry {
margin: 0.25em 0.25em 0.25em 0.25em;
padding: 0.25em 0.25em 0.25em 0.25em;
}

div.title {
font-weight: bold;
}

div.author {
font-style: italic;
}


今日はここまで. つづく.

[Erlang] ErlyWeb - Blog Tutorial その2


ErlyWeb - Blog Tutorial をひきつづき.
フロントページを表示するところまでできたので, その続きから. エントリーの登録を実装する.

validate


c:/apps/blog/src/helpers.erl に validate 関数を追加する.

-module(helpers).
-export([value/1, validate/3]).

value(Val) ->
case Val of
undefined ->
"";
_ ->
Val
end.

validate(ValidatorModule, Model, Item) ->
Fields = Model:use_fields(),
Results = [ValidatorModule:Field(Model:Field(Item)) || Field <- Fields],
Errors = [Error || Error <- Results,
element(1, Error) == error],
case Errors of
[] ->
ok;
_ ->
Errors
end.


controller


entries_controller.erl にエントリ作成用の関数を追加する. GET のとき登録画面表示で, POST のとき登録実行か?

-module(entries_controller).
%%-erlyweb_magic(on).
-export([index/1, new/1, new_get/0, new_post/1]).

index(_A) ->
Entries = entries:find(), % 全レコード取得
{data, Entries}. % 全レコードをビューへ

new(A) ->
case yaws_arg:method(A) of
'GET' ->
{data, {[], new_get()}};
'POST' ->
Vals = yaws_api:parse_post(A),
{Errors, Entry} = new_post(Vals),
case Errors of
ok ->
{ewr, index};
_ ->
{data, {Errors, Entry}}
end
end.

new_get() ->
Entry = entries:new(),
Entry.

new_post(Vals) ->
Entry = entries:set_fields_from_strs(entries:new(), Vals),
Errors = helpers:validate(entries_validate, entries, Entry),
case Errors of
ok ->
entries:save(Entry),
{ok, Entry};
_ ->
{Errors, Entry}
end.


validate モジュールの作成


c:/apps/blog/src/entries_validate.erl を新規作成する.
このファイルを components ディレクトリに作成すると, これもモデルだと思って, そんなテーブルはないよ, というエラーになってしまう.

-module(entries_validate).
-export([title/1, body/1, author/1]).

title(undefined) ->
{error, title, "The title field is blank."};
title(_) ->
ok.

body(undefined) ->
{error, body, "The body field is blank."};
body(_) ->
ok.

author(undefined) ->
{error, author, "The author field is blank."};
author(_) ->
ok.


モデル


use_fields を validate するフィールドのリストを返すように実装する.

-module(entries).
-export([use_fields/0]).

use_fields() ->
[title, body, author].


ビュー


登録用のビュー関数を追加する.

-module(entries_view).
%%-erlyweb_magic(on).
-export([index/1, new/1]).

index(Data) ->
entries_show:show_entries(Data).

new(Data) ->
entries_show:display_form(Data).


テンプレート


entries_show.et

entries_show.et に 登録フォームを追加する.
.et ファイルは改行コードを \n にしておかないと <%? のところでコンパイルエラーが発生する.

<%@ show_entries(Entries) %>
<div class="entries"><% [entry(E) || E <- Entries] %></div>

<%@ entry(Entry) %>
<div class="entry">
<div class="title"><% helpers:value(entries:title(Entry)) %></div>
<div class="body"><% helpers:value(entries:body(Entry)) %></div>
<div class="author">by: <% helpers:value(entries:author(Entry)) %></div>
</div>

<%@ display_form(Items) %>
<%? {Errors, Data} = Items %>
<% helpers_html:show_errors(Errors) %>
<form action="new" method="post">
<table>
<tr>
<th>Title:</th>
<td><% erlyweb_html:input("title", text_field, undefined,
helpers:value(entries:title(Data))) %></td>
</tr>
<tr>
<th>Body:</th>
<td><% erlyweb_html:input("body", text_field, undefined,
helpers:value(entries:body(Data))) %></td>
</tr>
<tr>
<th>Author:</th>
<td><% erlyweb_html:input("author", text_field, undefined,
helpers:value(entries:author(Data))) %></td>
</tr>
<tr>
<td colspan="2"><input type="submit"></td>
</tr>
</table>
</form>


helpers_html.et

エラー表示用のテンプレートのために c:/apps/blog/src/helpers_html.et を新規作成する

<%@ show_errors(Errors) %>
<ul>
<% [error(E) || E <- Errors] %>
</ul>

<%@ error({error, _Field, Message}) %>
<li><% Message %></li>


動かしてみる


blog_start:boot(). を実行する. http://localhost:8889/blog/entries/new にアクセスすると, 登録フォームが表示される.

[Erlang] Yaws で遊ぶ



動的コンテンツ



Yaws では .yaws というサフィックスのファイルに Erlang のコードを埋め込んで動的にページを生成することができる.
Erlang コードを埋め込むためには <erl> タグを使用する.
<erl> の中に out(Arg) という関数を定義する.
out 関数の返す値は {html, String} か {ehtml, EHTML}.
html の方は String の箇所にHTMLの文字列を入れてやればよい.
ehtml の方の EHTML はタプルとリストでHTMLを生成するコードを入れてやる.
out の引数には yaws_api.hrl で定義されている arg レコードが渡される.

<html>
<body>
<div>あいう</div>
<erl>
out(Arg) ->
{html, "<div>ばのびの</div>"}.
</erl>
<erl>
out(Arg) ->
{ehtml, {table, [{border, 1}, {bgcolor, green}],
[{tr, [],
[{td, [], "こんにちは"},
{td, [], "Hello"}]}]}}.
</erl>
<erl>
out(Arg) ->
{html, io_lib:format("<pre>~p</pre>", [Arg])}.
</erl>
</body>
</html>


io_lib:format は yaws_apt:f/2 が自動的にインクルードされるので, それで代替できる.

{html, p("<pre>~p</pre>", [Arg])}.


クエリパラメータ



GET のクエリパラメータは yaws_api:parse_query で取得できる.

<html>
<body>
<erl>
out(Arg) ->
{html, f("<pre>~p</pre>", [yaws_api:parse_query(Arg)])}.
</erl>
</body>
</html>


POST データ



POST データは yaws_api:parse_post で取得できる. 値がない場合は undefined になる.

<html>
<body>
<form method="post">
<input type="text" name="c">
<input type="text" name="cc">
<input type="submit">
<erl>
out(Arg) ->
{html, f("<pre>~p</pre>", [yaws_api:parse_post(Arg)])}.
</erl>
</body>
</html>


おわりに



ファイルのポスト, cookie, セッション, AppMods 等いろいろあるけど, それらはまた機会があれば.

[Erlang] http://progexpr.blogspot.com/2006/12/erlyweb-tutorial-part-2.html



http://progexpr.blogspot.com/2006/12/erlyweb-blog-tutorial.html のパート2.
編集機能を実装する.

controller


entries_controller.erl の new_post から新しく作成する new_or_edit を呼ぶようにする. 編集用の edit, edit_get, edit_post も新しく作成する.

new_post(Vals) ->
Entry = entries:new(),
new_or_edit(Entry, Vals).

new_or_edit(Entry, Vals) ->
EntryV = entries:set_fields_from_strs(Entry, Vals),
Errors = helpers:validate(entries_validate, entries, EntryV),
case Errors of
ok ->
entries:save(EntryV),
{ok, Entry};
_ ->
{Errors, EntryV}
end.

edit(A, Id) ->
case yaws_arg:method(A) of
'GET' ->
{data, {"", edit_get(Id)}};
'POST' ->
Vals = yaws_api:parse_post(A),
{Errors, Entry} = edit_post(Vals, Id),
case Errors of
ok ->
{ewr, entries, index};
_ ->
{data, {Errors, Entry}}
end
end.

edit_get(Id) ->
Entry = entries:find_id(Id),
Entry.

edit_post(Vals, Id) ->
Entry = entries:find_id(Id),
new_or_edit(Entry, Vals).


controller のリファクタリング


new と edit は同じようなコードがあるのでリファクタリングする.
form_process を作成し, それを new と edit から使用するようにする.

form_process(A,
GetAction,
ModelAction,
PostSuccessAction,
PostFailureAction) ->
case yaws_arg:method(A) of
'GET' ->
GetAction();
'POST' ->
Vals = yaws_api:parse_post(A),
{Errors, Record} = ModelAction(Vals),
case Errors of
ok ->
PostSuccessAction();
_ ->
PostFailureAction(Errors, Record)
end
end.

new(A) ->
GetAction = fun() -> {data, {[], new_get()}} end,
ModelAction = fun(Vals) -> new_post(Vals) end,
PostSuccessAction = fun() -> {ewr, entries, index} end,
PostFailureAction = fun(Errors, Entry) -> {data, {Errors, Entry}} end,
form_process(A, GetAction, ModelAction,
PostSuccessAction, PostFailureAction).

edit(A, Id) ->
GetAction = fun() -> {data, {"", edit_get(Id)}} end,
ModelAction = fun(Vals) -> edit_post(Vals, Id) end,
PostSuccessAction = fun() -> {ewr, entries, index} end,
PostFailureAction = fun(Errors, Entry) -> {data, {Errors, Entry}} end,
form_process(A, GetAction, ModelAction,
PostSuccessAction, PostFailureAction).


view


new と edit で同じフォームを使用する.

new(Data) ->
entry_form:display_form(Data, "../new", "").

edit({Errors, Entry}) ->
entry_form:display_form({Errors, Entry}, "../edit",
integer_to_list(entries:id(Entry))).


テンプレート


entry_form.et を新規作成する. 中身は entries_show.et の display_form とほぼ同じだが, フォームのアクション等を引数で指定する.

<%@ display_form(Items, GoTo, Id) %>
<%? {Errors, Data} = Items %>
<% helpers_html:show_errors(Errors) %>
<form action="<% GoTo %>/<% Id %>" method="post">
<table>
<tr>
<th>Title:</th>
<td><% erlyweb_html:input("title", text_field, undefined,
helpers:value(entries:title(Data))) %></td>
</tr>
<tr>
<th>Body:</th>
<td><% erlyweb_html:input("body", text_field, undefined,
helpers:value(entries:body(Data))) %></td>
</tr>
<tr>
<th>Author:</th>
<td><% erlyweb_html:input("author", text_field, undefined,
helpers:value(entries:author(Data))) %></td>
</tr>
<tr>
<td colspan="2"><input type="submit"></td>
</tr>
</table>
</form>


ビューページの作成


フロントページ -> ビューページ -> エディットページという画面繊維にするため, ビューページを作成する.

entries_controller.erl


view(_A, Id) ->
Entry = entries:find_id(Id),
{data, Entry}.


entries_view.erl


view(Data) ->
entry_view:view(Data).


テンプレート

entry_view.et を新規作成.

<%@ view(Data) %>
<b><% entries:title(Data) %></b>
<br><br>
<% entries:body(Data) %>
<br>
<i>by: <% entries:author(Data) %></i>
<br><br>
<% erlyweb_html:a(["../edit", integer_to_list(entries:id(Data))],
"Edit Entry") %>


entries_show.et に view へのリンクをつける.

<%@ entry(Entry) %>
<div class="entry">
<div class="title"><% helpers:value(entries:title(Entry)) %></div>
<div class="body"><% helpers:value(entries:body(Entry)) %></div>
<div class="author">by: <% helpers:value(entries:author(Entry)) %></div>
<div class="view"><% erlyweb_html:a(["view",
integer_to_list(entries:id(Entry))],
"view") %></div>
</div>


実行


blog_start:boot(). を実行し http://localhost:8889/blog/entries にアクセスする.

[Erlang] 外部インターフェース(ポートドライバ)



Erlang での外部インターフェースはポートドライバを作成し, そのポートに対して(プロセス間通信と同様に)非同期メッセージの送受信を行う(非同期ではないのもあるが).
iconv ライブラリを Erlang から使ってみる.

外の世界


C で iconv ライブラリのポートドライバ(dll)を作成する.
構造体 ErlDrvEntry に各処理のハンドラとなる関数等を設定する.
start はポートオープン時, stop ほポートクローズ時, output はメッセージを受信して応答する時の処理である. ちなみに control が同期インターフェース.
DRIVER_INIT マクロはポートドライバがロードされた時の処理であり, 構造体 ErlDrvEntry を返す.
eliconv.c

#include <memory.h>
#include <errno.h>

#include "erl_driver.h"
#include "iconv.h"

typedef struct {
ErlDrvPort port;
} Self;

static ErlDrvData start(ErlDrvPort port, char *command);
static void stop(ErlDrvData drv_data);
static void output(ErlDrvData drv_data, char *buf, int len);
static void send_value(Self* self, ErlDrvBinary* bin, int size);
static void send_error(Self* self, ErlDrvTermData* error);

#define OUTBUFSIZ 1024
#define OK 0
#define INVALID_ENCODE 1
#define OUT_OF_MEMORY 2

static ErlDrvEntry anErlDrvEntry = {
NULL,
start,
stop,
output, /* output */
NULL, /* ready_input */
NULL, /* ready_output */
"eliconv", /* driver name */
NULL, /* finish */
NULL, /* handle */
NULL, /* control */
NULL, /* timeout */
NULL, /* outputv */
NULL, /* ready_async */
NULL, /* flush */
NULL, /* call */
NULL /* event */
};

static ErlDrvTermData atom_value;
static ErlDrvTermData atom_error;
static ErlDrvTermData atom_invalid_encode;
static ErlDrvTermData atom_out_of_memory;

DRIVER_INIT(eliconv) {
atom_value = driver_mk_atom("value");
atom_error = driver_mk_atom("error");
atom_invalid_encode = driver_mk_atom("invalid_encode");
atom_out_of_memory = driver_mk_atom("out_of_memory");
return &anErlDrvEntry;
}

static ErlDrvData start(ErlDrvPort port, char *command) {
Self* self = NULL;
self = (Self*)driver_alloc(sizeof(Self));
self->port = port;
return (ErlDrvData)self;
}

static void stop(ErlDrvData drv_data) {
driver_free(drv_data);
}

static void output(ErlDrvData drv_data, char *buf, int len) {
Self* self = (Self*)drv_data;
ErlDrvBinary* bin;
char* outbuf;
size_t outbytesleft;
const char* tocode = buf;
int tocode_len = strlen(tocode);
const char* fromcode = &buf[tocode_len + 1];
int fromcode_len = strlen(fromcode);
int to_from_len = tocode_len + 1 + fromcode_len + 1;
const char* inbuf = &buf[to_from_len];
size_t inbytesleft = len - to_from_len;
int out_size = 0;

iconv_t cd = iconv_open(tocode, fromcode);
if (cd == (iconv_t)-1) {
send_error(self, &atom_invalid_encode);
return;
}

bin = driver_alloc_binary(OUTBUFSIZ);
if (bin == NULL) {
send_error(self, &atom_out_of_memory);
return;
}
outbytesleft = OUTBUFSIZ;
outbuf = bin->orig_bytes;
while(1) {
size_t ret = iconv(cd, &inbuf, &inbytesleft,
&outbuf, &outbytesleft);
if (ret == -1) {
if (errno == E2BIG) {
out_size += OUTBUFSIZ - outbytesleft;
bin = driver_realloc_binary(bin, out_size + OUTBUFSIZ);
if (bin == NULL) {
send_error(self, &atom_out_of_memory);
return;
}
outbuf = bin->orig_bytes + out_size;
outbytesleft = OUTBUFSIZ;
continue;
}
--inbytesleft;
++inbuf;
continue;
}
out_size += OUTBUFSIZ - outbytesleft;
break;
}

send_value(self, bin, out_size);

driver_free_binary(bin);

iconv_close(cd);
}

void send_value(Self* self, ErlDrvBinary* bin, int size) {
/* {Port, value, Bin} */
ErlDrvTermData spec[] = {
ERL_DRV_PORT, driver_mk_port(self->port),
ERL_DRV_ATOM, atom_value,
ERL_DRV_BINARY, (ErlDrvTermData)bin, size, 0,
ERL_DRV_TUPLE, 3
};
driver_send_term(self->port, driver_caller(self->port),
spec, sizeof(spec)/sizeof(spec[0]));
}

void send_error(Self* self, ErlDrvTermData* error) {
/* {Port, error, Error} */
ErlDrvTermData spec[] = {
ERL_DRV_PORT, driver_mk_port(self->port),
ERL_DRV_ATOM, atom_error,
ERL_DRV_ATOM, *error,
ERL_DRV_TUPLE, 3
};
driver_send_term(self->port, driver_caller(self->port),
spec, sizeof(spec)/sizeof(spec[0]));
}


cygwin での Makefile. iconv.dll は別途パスの通ったディレクトリにおいておく必要がある.

all:
gcc -mno-cygwin -shared -Wall -o ../priv/lib/win32/eliconv.dll -I. -I"c:/Program Files/erl5.5.2/erts-5.5.2/include" -L. eliconv.c -liconv



中の世界


dllとのポートをオープンしてメッセージの送受信を行うので, オープンしたポートは使いまわしたい. そういうときは gen_server を使う.
eliconv_server.erl

%%%-------------------------------------------------------------------
%%% File : eliconv_server.erl
%%% Author :
%%% Description : eliconv
%%%
%%% Created : 7 Jan 2007 by Yoshinori Tahara <read.eval.print@gmail.com>
%%%-------------------------------------------------------------------
-module(eliconv_server).

-behaviour(gen_server).

%% API
-export([start_link/0]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).

-record(state, {port}).

-define(SERVER, ?MODULE).

%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

%%====================================================================
%% gen_server callbacks
%%====================================================================

%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([]) ->
Arch = erlang:system_info(system_architecture),
Libs = filename:join([code:priv_dir(eliconv), "lib", Arch]),
ok = erl_ddll:load_driver(Libs, "eliconv"),
Port = open_port({spawn, "eliconv"}, []),
{ok, #state{port = Port}}.

%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} |
%% {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call({ToEncode, FromEncode, Src}, _From, State) ->
Reply = do(State#state.port, ToEncode, FromEncode, Src),
{reply, Reply, State}.

%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast(stop, State) ->
{stop, normal, State};
handle_cast(_Msg, State) ->
{noreply, State}.

%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info(_Info, State) ->
{noreply, State}.

%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, State) ->
Port = State#state.port,
Port ! {self(), close},
receive
{Port, closed} ->
ok;
_ ->
io:format("close failed.~n")
after
1000 ->
io:format("close timeout.~n")
end,
erl_ddll:unload_driver("eliconv"),
ok.

%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.

%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
do(Port, ToEndoce, FromEncode, String) when list(String) ->
do(Port, ToEndoce, FromEncode, list_to_binary(String));
do(Port, ToEndoce, FromEncode, Binary) when binary(Binary) ->
To = list_to_binary(ToEndoce),
From = list_to_binary(FromEncode),
Port ! {self(),
{command, <<To/binary, 0, From/binary, 0, Binary/binary>>}},
receive
{Port, Result, Data} ->
Reply = {Result, Data}
end,
Reply.

% eliconv:start_link().
% gen_server:call(eliconv_server, {"UTF-8", "CP932", "あいうえお"}).
% gen_server:cast(eliconv_server, stop).


eliconv_server の簡単な API として eliconv.erl を作る.

%%%-------------------------------------------------------------------
%%% File : eliconv.erl
%%% Author :
%%% Description : eliconv
%%%
%%% Created : 8 Jan 2007 by Yoshinori Tahara <read.eval.print@gmail.com>
%%%-------------------------------------------------------------------
-module(eliconv).

%% API
-export([do/3, start/0, stop/0]).

-define(SERVER, eliconv_server).

%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function:
%% Description:
%%--------------------------------------------------------------------
do(ToEncode, FromEncode, BinaryOrString) ->
case erlang:whereis(?SERVER) of
undefined ->
io:format("start server...~n"),
start();
_ ->
ok
end,
gen_server:call(?SERVER, {ToEncode, FromEncode, BinaryOrString}).

start() ->
eliconv_server:start_link().

stop() ->
gen_server:cast(?SERVER, stop).

%%====================================================================
%% Internal functions
%%====================================================================


実行



1> eliconv:do("UTF-8", "CP932", "あいう").
start server...
{value,<<227,129,130,227,129,132,227,129,134>>}
2>


[DO] タコのパエリア


2回目のタコのパエリアを作った。ごはんが硬いし、味は薄いし、失敗だった。最初に作ったときはとても美味しかったのに。
最初に作ったときの分量とかもう覚えてない。きちんとメモっておくべきだった。
次回はこのレシピでやってみよう。

[Erlang] バイナリパターンマッチング



<a href="http://www.spamfreeemail.com/blogs/2007-01-10/134/" title="ErlMail-0.0.2 Release">Erlang's binary matching rocks!</a>

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

IPアドレスのタプルを32ビットデシマルに変換するコード。
Erlang はバイナリのリテラル表記があり、それでパターンマッチングができる。
お気に入りの機能の1つ。

[Erlang] http クライアント


Erlang は1オリジン

fun(Url) ->
{ok, {_Code, _Head, Body}} = http:request(Url),
io:format("~s~n", [Body]),
string:str(Body, "検索")
end("http://www.google.co.jp").


[Erlang] Erlang/OTP R11B-3 released


Bug fix release : otp_src_R11B-3
Build date : 2007-01-30
リリースされている。

[Erlang][本] Concurrent Programming in ERLANG


ISBN:013508301X:detail
が届いた。
でも、なぜか表紙が違う。

[Erlang][本] Programming Erlang Software for a Concurrent World


10年ぶりに Erlang の新しい本が出版されるらしい。出版は2007年の7月の予定。
The Pragmatic Programmers からベータ版の PDF を購入できる。
さっそく購入する。PDF だとサンプルソースのダウンロードページへのリンクが付いていたりするので便利だ。
オンラインで購入後、すぐにダウンロードできるようになったのも便利。
今回初めて The Pragmatic Programmers を利用したけど、技術系の本だと等に PDF のダウンロードで購入するというのは便利で素敵なことかもしれない。
http://pragmaticprogrammer.com/titles/jaerlang/index.html

[Erlang] Mac OS X 10.3 に Erlang をインストールする


MacPort で普通にインストールしたら、configure でひっかかる。
/opt/local/var/db/dports/sources/rsync.rsync.darwinports.org_dpupdate_dports/lang/erlang/Portfile から --enable-kernel-poll の行を削除する。

[Erlang] Erlang の = は代入ではない




1> X = 1.
1
2> Y = 1.
1
3> X = 1.
1
4> X = Y.
1
5> X = 2.

=ERROR REPORT==== 18-Mar-2007::15:40:21 ===
Error in process <0.29.0> with exit value: {{badmatch,2},[{erl_eval,expr,3}]}

exited: {{badmatch,2},[{erl_eval,expr,3}]} **




Erlang はシングルアサインメント。
でも、上の例では X に 3 回も代入を行って、4回目でようやくエラーになっている。
= は代入を行なうのではく、むしろパターンマッチングを行う。パターンマッチングの際、変数が未束縛の場合のみ代入を行う。

(1>)では X は未束縛なので 1 が代入される。
(3>)(4>)ではいずれも 1 と 1 のパターンマッチングが行われる。
(5>)で 1 と 2 のパターンマッチングが行われて {badmatch,2} というエラーなる。


[Erlang] SMP



Erlang のプロセスはネイティブスレッドではないからマルチコアだとどうなの? と思っていた。
で下のコードを動かしてみる。1つのCPUしか使ってない。
なんで? と思いつつ、マニュアル参照する。
-smp オプションを付ければいいらしい。
erl -smp で起動して、もう一度下のコードを実行する。
2つの CPU 使用率が100%になった。


-module(fib_mp).
-export([main/1, fib/1]).

main(N) ->
start(N),
start(N).

start(N) ->
spawn(?MODULE, fib, [N]).

fib(1) ->
1;
fib(2) ->
1;
fib(N) ->
fib(N - 1) + fib(N - 2).


[Erlang] Windows: Meadew: ~/.emacs



XP Home Edition じゃ、-smp auto は意味なし?

;;;; Erlang
(setq load-path (cons "C:/Program Files/erl5.5.2/lib/tools-2.5.2/emacs"
load-path))
(setq erlang-root-dir "C:/Program Files/erl5.5.2")
(setq exec-path (cons "C:/Program Files/erl5.5.2/bin" exec-path))
(setq inferior-erlang-machine-options
'("-smp" "auto"
"-mnesia" "dir" "\"/tmp/mnesia\""
"-pa" "c:/home/ancient/letter/erlang/lib/oregexp-1.0/ebin"
"-pa" "c:/home/ancient/letter/erlang/eliconv-1.0/ebin"))
(require 'erlang-start)
(defun run-yaws ()
(interactive)
(let ((inferior-erlang-machine "C:/Progra~1/yaws-1.66/run_yaws.bat"))
(run-erlang)))


[Erlang][Yaws][Mac] Mac で Yaws を動かしてみる



Mac OS X では MacPorts でインストールできます。

sudo port install yaws


ターミナルを開いて、/opt/local/etc にある yaws.conf.template を
yaws.conf とい名前にコピーします。


~% cd /opt/local/etc
/opt/local/etc% cp yaws.conf.template yaws.conf


これが Yaws の設定ファイルになります。
この設定ファイルには3つのバーチャルホストが定義されています。
ポートは80番と443番を使う設定になっています。
既に Apache 等でそれらのポートを使用している場合は、
未使用なポート番号に書換えてください。


<server Macintosh.local>
port = 4080 # 80番から書換えた
listen = 0.0.0.0
docroot = /opt/local/var/yaws/www
</server>

<server localhost>
port = 4080 # 80番から書換えた
listen = 0.0.0.0
docroot = /opt/local/tmp
dir_listings = true
dav = true
<auth>
realm = foobar
dir = /
user = foo:bar
user = baz:bar
</auth>
</server>



# And then an ssl server

<server Macintosh.local>
port = 4443 # 443番から書換えた
docroot = /opt/local/tmp
listen = 0.0.0.0
dir_listings = true
<ssl>
keyfile = /opt/local/etc/yaws-key.pem
certfile = /opt/local/etc/yaws-cert.pem
</ssl>
</server>


また、/opt/local/tmp をドキュメントルートとして使用しているので、
/opt/local/tmp がない場合はターミナルから作成してください。


/opt/local/etc% mkdir /opt/local/tmp


ターミナルから yaws -i を実行すると Yaws が開始します。


/opt/local/etc% yaws -i
Erlang (BEAM) emulator version 5.5.3 [source] [async-threads:0] [hipe]

Eshell V5.5.3 (abort with ^G)
1>
=INFO REPORT==== 21-Mar-2007::15:06:04 ===
Yaws: Using config file ./yaws.conf
yaws:Add path "/opt/local/lib/yaws/ebin"
yaws:Add path "/opt/local/lib/yaws/examples/ebin"
yaws:Running with id=default
Running with debug checks turned on (slower server)
Logging to directory "/opt/local/var/log/yaws"

=INFO REPORT==== 21-Mar-2007::15:06:04 ===
Yaws: Listening to 0.0.0.0:4443 for servers
- https://Macintosh.local:4443 under /opt/local/tmp

=INFO REPORT==== 21-Mar-2007::15:06:04 ===
Yaws: Listening to 0.0.0.0:4080 for servers
- http://Macintosh.local:4080 under /opt/local/var/yaws/www
- http://localhost:4080 under /opt/local/tmp


ブラウザで http://Macintosh.local:4080 を開くと Yaws のページが表示されます。
右側のリンクから各種ドキュメントやサンプルページにアクセスできます。

また、http://localhost:4080 はベーシック認証のサンプル、https://Macintosh.local:4443 は SSL のサンプルになっています。

[Erlang] -compile(export_all).



-export([f1/0, f2/0, f3/1, ...]).

とエクスポートする関数を普通は書かなくてはならないけど、

-compile(export_all).

でモジュール内の全関数がエクスポートされる。

ふと、関数のエクスポートは指定するけど変数のエクスポートはどうするんだろう、と思った。
xxx.hrl に書いておいて

-include_lib("xxx.hrl").

とインクルードするんだ。
シングルアサインメントだからか。

[Erlang] Erlang/OTP R11B-4 released



Erlang/OTP R11B-4 released.
escript が追加された。

#!/usr/bin/env escript

main(_) ->
io:format("Hello world\n").


[Erlang] escript


escript で少し遊ぶ。

#!/usr/bin/env escript

-include_lib("kernel/include/file.hrl").

main([Dir]) ->
{ok, FileList} = (file:list_dir(Dir)),
f(Dir, FileList).

f(Dir, [H|T]) ->
File = filename:absname_join(Dir, H),
{ok, FileInfo} = file:read_file_info(File),
p(H, FileInfo),
f(Dir, T);
f(_, _) ->
ok.

p(File, #file_info{type = regular, size = Size}) ->
io:format("~s is file(size: ~b).~n", [File, Size]);
p(Dir, #file_info{type = directory}) ->
io:format("~s is directory.~n", [Dir]).


実行は Windows 環境なので

escript a.erl /
WINDOWS is directory.
usr is directory.
tmp is directory.
TEMP is directory.
System Volume Information is directory.
sqmnoopt01.sqm is file(size: 244).
sqmnoopt00.sqm is file(size: 244).
sqmdata01.sqm is file(size: 280).
sqmdata00.sqm is file(size: 268).
RECYCLER is directory.
Program Files is directory.
pagefile.sys is file(size: 1598029824).
ntldr is file(size: 260272).
NTDETECT.COM is file(size: 47564).
My Squeak is directory.
MSDOS.SYS is file(size: 0).
meadow is directory.
IO.SYS is file(size: 0).
home is directory.
hiberfil.sys is file(size: 1063088128).
Drivers is directory.
Documents and Settings is directory.
delus.bat is file(size: 171).
DAVINCI_CODE_3 is directory.
DAVINCI_CODE_1 is directory.
cygwin is directory.
CONFIG.SYS is file(size: 0).
bootfont.bin is file(size: 132398).
boot.ini is file(size: 199).
AVG7QT.DAT is file(size: 12442353).
AVG7DB_F.DAT is file(size: 19051770).
AUTOEXEC.BAT is file(size: 0).
apps is directory.
AliceSoft is directory.
$VAULT$.AVG is directory.


-include_lib も使えるようだ。

[Erlang][Yaws] Yaws でユーザ毎のウェブディレクトリ



tilde_expand = true を使います。

http://example.com/~username/index.html は /home/username/public_html/index.html へのパスになります。

ついでにバーチャルホストの設定も。
/etc/hosts に erlang.localhost を書いておく。

yaws.conf

<server localhost>
port = 4480
listen = 0.0.0.0
docroot = /var/www/yaws-default
# ユーザ毎のウェブディレクトリを有効に
tilde_expand = true
</server>

# バーチャルホスト
<server erlang.localhost>
port = 4480
listen = 0.0.0.0
docroot = /home/ancient/public_html/erlang
</server>


[Erlang] コードパスの追加



コードパスの追加方法として、erl に -pa path, -pz path とオプションを指定する方法があります。
他にも ~/.erlang に書いておく方法もあるようです。
~/.erlang は Emacs の ~/.emacs と同様に起動時の読み込まれるファイルです。

~/.erlang

code:add_pathz("/home/ancient/letter/erlang/distel/ebin").


[Erlang] コンパイルオプションのデフォルト値


環境変数の ERL_COMPILER_OPTIONS でコンパイルオプションのデフォルト値を指定できます。

export ERL_COMPILER_OPTIONS='[debug_info,{parse_transform,internal_exports}]'


[Erlang] Distel


Distel は Erlang の分散ノードを利用する Emasc での Erlang モード。
Common Lisp の Slime みたいだったらいいな。今度ためしてみましょう。

[Erlang][Distel][Emasc] Distel



Distel を試してみました。

ソースを取得

svn checkout http://distel.googlecode.com/svn/trunk/ distel


コンパイル

./configure
make


~/.erlang でコードパスの設定

code:add_pathz("/home/ancient/letter/erlang/distel/ebin").


~/.emacs で Emacs 側の設定

;;;;Erlang
(setq inferior-erlang-machine-options
'("-smp" "auto"
"-mnesia" "dir" "\"/tmp/mnesia\""
"-sname" "emacs"
;;"-pa" "c:/home/ancient/letter/erlang/lib/oregexp-1.0/ebin"
;;"-pa" "c:/home/ancient/letter/erlang/eliconv-1.0/ebin"
))
;;;;Distel
(add-to-list 'load-path "/home/ancient/letter/erlang/distel/elisp")
(require 'distel)
(distel-setup)
(setq erl-nodename-cache
(make-symbol
(concat
"emacs@"
;; Mac OS X uses "name.local" instead of "name", this should work
;; pretty much anywhere without having to muck with NetInfo
;; ... but I only tested it on Mac OS X.
(car (split-string (shell-command-to-string "hostname"))))))
(define-key erlang-extended-mode-map "\C-c\C-i" 'erl-complete)


コードの補完が効く(~/.emacs で \C-c\C-i に割り当ててる)。
M-. で関数定義にジャンプし、M-, で戻ってこれる。
\C-c\C-ed で起動する interactive erlang shell は最高。

interactive erlang shell は *scratch* バッファみたいなものです。
式の評価に加え、関数定義もできるところが嬉しい。

%%% Welcome to the Distel Interactive Erlang Shell.
%%
%% C-j evaluates an expression and prints the result in-line.
%% C-M-x evaluates a whole function definition.

1 + 2.
--> 3

hello() ->
io:format("Hello~n").

hello().Hello
--> ok


[Erlang][Yaws] Yaws の埋め込みモード




(emacs@localhost)1> yaws:start_embedded("/home/ancient/public_html").

=INFO REPORT==== 11-Apr-2007::21:36:48 ===
Yaws: Listening to 127.0.0.1:8000 for servers
- http://localhost:8000 under /home/ancient/public_html
ok


これで Yaws が起動する。素晴しい。

[Erlang][CEAN][Yaws] CEAN で Windows でも Yaws をインストールが可能に



http://cean.process-one.net/ CEAN の1.2から Windows でも Yaws をインストールできるになった。

ダウンロードして解凍。
start.bat
cean:install("yaws").
yaws:start_embedded("/home/ancient/public_html").

いままで、Windows で Yaws を動かすのは結構やっかいだったのが、とても簡単になりました。

[Erlang] FTP でアップロード



Web サーバに FTP でファイルをアップロードするプログラムを escript で書いてみました。
escript だと chmod +x upload.es としておけば ./upload.es で実行できるので便利ですね。

FTP のモジュールは Inets の ftp です。

upload.es

#!/usr/bin/env escript

main(_) ->
{ok, Ftp} = ftp:open("www.example.com"),
ftp:user(Ftp, "user", "password"),
ftp:cd(Ftp, "/public_html"),
lists:foreach(fun(File) ->
ftp:send(Ftp, File)
end,
["index.html","main.css"]),
ftp:close(Ftp).


[Erlang][ErlyWeb] ErlyWeb 0.6



ErlyWeb 0.6 がリリースされました。MySQL のみサポートしていた ErlyDB に Mnesia driver と Postgres driver が追加されました。

Mnesia が使えるようになったのは特に嬉しいですね。RDBMS 不要で、Erlang だけで Web アプリを構築できるようになったので。

[Erlang][CEAN] CEAN で Windows の Erlang 環境を構築する



http://cean.process-one.net/download/ から
"installer", "R11B", "Developer", "Microsoft Windows" で "Download" します。

環境変数 HOME が "/home/ancient" と設定されていることを前提てして、
~/local/opt/cean にダウンロードした cean_base.zip を解凍します。

プロキシのある環境では ~/local/opt/cean/start.bat に次の行の先頭の rem を削除し、プロキシサーバの ホスト名:ポート を書きます。

set HTTP_PROXY=proxy.example.com:8080



~/local/opt/cean/satrt.bat を起動します。
認証に必要なプロキシがある場合は次の関数でユーザ名とパスワードを設定します。

cean:proxy_user("user", "password").


Emascs(Meadow)の Erlang モードが欲しいので tools をインストールします。

cean:install("tools").



~/.emacs に次の行を追加します。add-path は apel がインストール済みで (require 'path-util) で使えるようになります。

;;;; Erlang
(add-path "~/local/opt/cean/erlang/lib/tools-2.5.4/emacs")
(setq erlang-root-dir "~/local/opt/cean/erlang/erts-5.5.4")
(setq exec-path (cons "~/local/opt/cean/erlang/erts-5.5.4/windows/bin" exec-path))
(require 'erlang-start)



C:\home\ancient\local\opt\cean\erlang\erts-5.5.4\windows\bin\erl.ini の Bindir と Rootdir が相対パスで設定されていますが、それだとどうも都合がよくないので、絶対パスに書きかえます。

[erlang]
Bindir=C:/home/ancient/local/opt/cean/erlang/erts-5.5.4/windows/bin
Progname=erl
Rootdir=C:/home/ancient/local/opt/cean/erlang


Meadow で M-x run-erlang で Erlang シェルが起動します。
Erlang のソースバッファから C-c C-k でそのファイルがコンパイルされます。

[Erlang][Yaws] Yaws で動的なページを作成する



動的なページを作成するにはドキュメントルート以下に拡張子を yaws にしたファイル作成します。
<erl>タグの間に out(Arg) 関数を定義します。out(Arg) は1番目の要素がhtml、2番目の要素が文字列のタプル {html, String} を返します。表示時に<erl>タグから</erl>タグが返したタプルの2番目要素である文字列で置き換えられます。
次は現在日時を表示するサンプルです。なお、関数 f は io_lib:format/2 のエイリアスです。


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


out(Arg) は {html, String} を返すかわりに {ehtml, EHTML} を返すこともできます。
EHTML の部分は文字列、バイナリ、{TAG, Attrs, Body}、あるいは EHTML のリストです。

TAG は HTML のタグをアトムで記述します。
Attrs は HTML のタグの属性で [{border, 1}, {bgcolor, grey}] のように、属性名とその値のタプルのリストです。
Body は HTML のタグに囲まれている中身です。ここにまたタグを書くこともできます。


<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>EHTMLの例1</title>
</head>
<body>
<h1>EHTMLの例1</h1>
<erl>
out(Arg) ->
{{Y, M, D}, {H, Mi, S}} = erlang:localtime(),
{ehtml, [{pre, [], "テーブルです。"},
{table, [{border, 1}, {bgcolor, grey}],
[{tr, [],
lists:map(fun(X) -> {th, [], X} end,
["年","月","日","時","分","秒"])},
{tr, [],
lists:map(fun(X) -> {td, [], integer_to_list(X)} end,
[Y, M, D, H, Mi, S])}]}]}.
</erl>
</body>
</html>



1つ問題が、
tilde_expand = true を設定して ~/public_html の下に置いた *.yaws ファイルは適切に処理されず、そのまんまブラウザの表示されてしまいました。ちょっと悲しい。

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




(emacs@localhost)1> {_Module, Beam, _File} = code:get_object_code(base64),
(emacs@localhost)1> {ok,{_,[{abstract_code,{_,AC}}]}} = beam_lib:chunks(Beam, [abstract_code]),
(emacs@localhost)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).

encode(Bin) when is_binary(Bin), size(Bin) > 24000 ->
{A, B} = split_binary(Bin, 24000),
L = encode_l(binary_to_list(A)),
list_to_binary([L, encode(B)]);
encode(Bin) when is_binary(Bin) ->
encode(binary_to_list(Bin));
encode(List) when is_list(List) ->
list_to_binary(encode_l(List)).

encode_l(List) -> encode(List, encode_tuple()).

encode([], _T) -> [];
encode([A], T) ->
[b64e(A bsr 2, T), b64e(A band 3 bsl 4, T), $=, $=];
encode([A, B], T) ->
[b64e(A bsr 2, T),
b64e(A band 3 bsl 4 bor (B bsr 4), T),
b64e(B band 15 bsl 2, T), $=];
encode([A, B, C | Ls], T) ->
BB = A bsl 16 bor (B bsl 8) bor C,
[b64e(BB bsr 18, T), b64e((BB bsr 12) band 63, T),
b64e((BB bsr 6) band 63, T), b64e(BB band 63, T)
| encode(Ls, T)].

decode(Bin) when is_binary(Bin), size(Bin) > 24000 ->
{A, B} = split_binary(Bin, 24000),
L = decode_l(binary_to_list(A)),
list_to_binary([L, decode(B)]);
decode(Bin) when is_binary(Bin) ->
decode(binary_to_list(Bin));
decode(List) when is_list(List) ->
list_to_binary(decode_l(List)).

mime_decode(Bin)
when is_binary(Bin), size(Bin) > 24000 ->
{A, B} = split_binary(Bin, 24000),
L = mime_decode_l(binary_to_list(A)),
list_to_binary([L, mime_decode(B)]);
mime_decode(Bin) when is_binary(Bin) ->
mime_decode(binary_to_list(Bin));
mime_decode(List) when is_list(List) ->
list_to_binary(mime_decode_l(List)).

decode_l(List) ->
L = strip_spaces(List, []),
decode(L, decode_tuple(), []).

mime_decode_l(List) ->
L = strip_illegal(List, []),
decode(L, decode_tuple(), []).

decode_to_string(Bin) when is_binary(Bin) ->
decode_to_string(binary_to_list(Bin));
decode_to_string(List) when is_list(List) ->
decode_l(List).

mime_decode_to_string(Bin) when is_binary(Bin) ->
mime_decode_to_string(binary_to_list(Bin));
mime_decode_to_string(List) when is_list(List) ->
mime_decode_l(List).

decode([], _T, A) -> A;
decode([$=, $=, C2, C1 | Cs], T, A) ->
Bits2x6 = b64d(C1, T) bsl 18 bor (b64d(C2, T) bsl 12),
Octet1 = Bits2x6 bsr 16,
decode(Cs, T, [Octet1 | A]);
decode([$=, C3, C2, C1 | Cs], T, A) ->
Bits3x6 = b64d(C1, T) bsl 18 bor (b64d(C2, T) bsl 12)
bor (b64d(C3, T) bsl 6),
Octet1 = Bits3x6 bsr 16,
Octet2 = (Bits3x6 bsr 8) band 255,
decode(Cs, T, [Octet1, Octet2 | A]);
decode([C4, C3, C2, C1 | Cs], T, A) ->
Bits4x6 = b64d(C1, T) bsl 18 bor (b64d(C2, T) bsl 12)
bor (b64d(C3, T) bsl 6)
bor b64d(C4, T),
Octet1 = Bits4x6 bsr 16,
Octet2 = (Bits4x6 bsr 8) band 255,
Octet3 = Bits4x6 band 255,
decode(Cs, T, [Octet1, Octet2, Octet3 | A]).

strip_spaces([], A) -> A;
strip_spaces([$=, C | _], A) when C =/= $= -> [$=, A];
strip_spaces([$\s | Cs], A) -> strip_spaces(Cs, A);
strip_spaces([$\t | Cs], A) -> strip_spaces(Cs, A);
strip_spaces([$\r | Cs], A) -> strip_spaces(Cs, A);
strip_spaces([$\n | Cs], A) -> strip_spaces(Cs, A);
strip_spaces([C | Cs], A) -> strip_spaces(Cs, [C | A]).

strip_illegal([], A) -> A;
strip_illegal([C | Cs], A) when C >= $A, C =< $Z ->
strip_illegal(Cs, [C | A]);
strip_illegal([C | Cs], A) when C >= $a, C =< $z ->
strip_illegal(Cs, [C | A]);
strip_illegal([C | Cs], A) when C >= $0, C =< $9 ->
strip_illegal(Cs, [C | A]);
strip_illegal([$=, C | _], A) when C =/= $= -> [$= | A];
strip_illegal([C | Cs], A)
when C =:= $+; C =:= $/; C =:= $= ->
strip_illegal(Cs, [C | A]);
strip_illegal([_ | Cs], A) -> strip_illegal(Cs, A).

encode_tuple() ->
{$A, $B, $C, $D, $E, $F, $G, $H, $I, $J, $K, $L, $M, $N,
$O, $P, $Q, $R, $S, $T, $U, $V, $W, $X, $Y, $Z, $a, $b,
$c, $d, $e, $f, $g, $h, $i, $j, $k, $l, $m, $n, $o, $p,
$q, $r, $s, $t, $u, $v, $w, $x, $y, $z, $0, $1, $2, $3,
$4, $5, $6, $7, $8, $9, $+, $/}.

decode_tuple() ->
{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60,
61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29,
30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1}.

b64e(X, T) -> element(X + 1, T).

b64d(X, T) -> b64d_ok(element(X, T)).

b64d_ok(N) when N >= 0 -> N.


ok



[Erlang] 実行時のコンパイル



モジュール単位でのコンパイル、コードスワップの模様。
次のどちらの場合も compile:forms を使用している。

ErlyWeb の smerl より




test_smerl() ->
M1 = smerl:new(foo),
{ok, M2} = smerl:add_func(M1, "bar() -> 1 + 1."),
smerl:compile(M2),
foo:bar(), % returns 2``
smerl:has_func(M2, bar, 0). % returns true

fun() ->
M0 = smerl:new(foo),
{ok, M1} = smerl:add_func(M0,
"hello(X) -> io:format(\"<~s>~n\", [X])."),
{ok, M2} = smerl:add_func(M1, "barbar(X, Y) -> X * Y.")
end().

smerl:get_module(foo).

smerl:to_src(element(2, smerl:for_file("/tmp/fib.erl"))).


Erlang のメーリングリストより



Erlang のメーリングリストから引用
From: "Ulf Wiger \(TN/EAB\)"
Message-ID: <6616D98C65DD514BA2E1DDC5F922315501A993CB@esealmw115.eemea.ericsson.se>
Subject: Re: [erlang-questions] Charset conversion / stylistic question.

Consider the following module:
-module(gen_keyvals).

-export([mod/3]).


mod(Mod, Function, Vals) when is_atom(Mod), is_atom(Function) ->
[{attribute, 1, module, Mod},
{attribute, 1, export, [{Function, 1}]},
gen_function(Function, Vals)].

gen_function(F, Vals) ->
{function, 1, F, 1,
[{clause, 1, [{atom, 1, K}], [],
[erl_parse:abstract(V)]} ||
{K,V} <- Vals]}.

An example:

Eshell V5.5.3.1 (abort with ^G)
1> c(gen_keyvals).
{ok,gen_keyvals}
2> gen_keyvals:mod(m,foo,[{a,1},{b,2},{c,3}]).
[{attribute,1,module,m},
{attribute,1,export,[{foo,1}]},
{function,1,
foo,
1,
[{clause,1,[{atom,1,a}],[],[{integer,0,1}]},
{clause,1,[{atom,1,b}],[],[{integer,0,2}]},
{clause,1,[{atom,1,c}],[],[{integer,0,3}]}]}]
3> compile:forms(v(2)).
{ok,m,

<<70,79,82,49,0,0,1,184,66,69,65,77,65,116,111,109,0,0,0,51,0,0,0,8,1,10
9,
...>>}
4> code:load_binary(m,"m.beam",element(3,v(3))).
{module,m}
5> m:foo(a).
1
6> m:foo(c).
3

If you want the generated code in a .erl file, this is
also easily accomplished:

9> [erl_pp:form(F) || F <- v(2)].
[[[[[[45,"module"]],[[40,[["m"],41]]]],".\n"]],
[[[["-export"],[[40,[[91,[[["foo",47,"1"]],93]],41]]]],".\n"]],
[[[[[[["foo",[[40,["a",41]]]]]," ->"],["\n ",["1",59]]],
[10,[[[["foo",[[40,["b",41]]]]]," ->"],["\n ",["2",59]]]],
[10,[[[["foo",[[40,["c",41]]]]]," ->"],["\n ",["3"]]]]],
".\n"]]]
10> io:format("~s~n", [v(9)]).
-module(m).
-export([foo/1]).
foo(a) ->
1;
foo(b) ->
2;
foo(c) ->
3.


[Erlang] Erlang にもドットリストがあるんですね




1> [1|2].
[1|2]
2> tl([1|2]).
2
3> hd([1|2]).
1



[Erlang] Emakefile



Erlang にはコンパイルを簡単に行うための make モジュールがります。
make:all() でカレントディレクトリにある Emakefile という名前のファイルに従ってコンパイルを実行します。
最も簡単には次のような Emakefile を用意しておきます。

{'*', [debug_info]}.

これはカレントディレクトリにある全てのモジュール(*.erl)を debug_info オプション付きでコンパイルを行う、という指定です。

3> make:all().
Recompile: lisp_repl
Recompile: lisp_reader
Recompile: lisp_machine
Recompile: lisp_eval
Recompile: lisp_env
Recompile: lisp_bim
Recompile: lisp_bif
up_to_date

このように make:all(). で全モジュールがコンパイルされます。
Makefile の make や escript と組み合わせるとより便利になるでしょう。

[Erlang] fun



fun モジュール:関数名/アリティ
という書き方ができるんですね。

(emacs@localhost)75> fun erlang:tuple_to_list/1({1,2}).
[1,2]
(emacs@localhost)76> lists:map(fun erlang:size/1, [{1, 2}, {}]).
[2,0]


オペレータ(+, * なんか)も何かこんなふうにして呼べないものかしら?

[Erlang] デバッグ用の print




io:format("~p~n", [Term]).

を使用していたけど、

erlang:display(Term).

がデバッグ目的で用意されてるみたいです。

[Erlang] 関数の情報を表示する



erlang:fun_info/1 で関数の情報を取得できます。
ローカル関数の場合、env などを見られるのが楽しいです。
Erlang って結構内部的なところにアクセスできるよう作られている印象があります。

(emacs@localhost)21> erlang:fun_info(fun erlang:display/1).
[{module,erlang},{name,display},{arity,1},{env,[]},{type,external}]
(emacs@localhost)22> 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,...}}]}]]},
{type,local}]

new_index, new_uniq, index, uniq って何か使い道はあるのかしら?
new が付くものと付かないものの違いは何でしょう?