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 が付くものと付かないものの違いは何でしょう?

0 件のコメント: