トップ «前の日記(2007-05-06) 最新 次の日記(2007-05-09)» 編集

じじぃの日記、ツッコミ可

Twitter: @jijixi_org
Xbox Live: jijixi

初心者が書いた OCaml 入門
Spotlight tips サイト内リンク集
1970|01|02|
2003|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|11|
2011|05|
2012|01|

2007-05-08 [長年日記]

% [雑談] 夜更かしなのか早起きなのか、よくわからん状態

4 時すぎに寝て、5 時半に起きた。 レム睡眠の訪れる間隔は 1 時間半ほどだと言われているので、つまり入眠後最初のレム睡眠時に何らかの刺激があって覚醒してしまったと思われる。

……つーか納得行かねー。

% [Erlang] erl コマンドの -remsh オプション

多分レム睡眠とは関係無い。 erl コマンドのマニュアルを眺めてて見つけた。

-remsh Node
    Starts Erlang with a remote shell connected to Node. 

ということで、リモートノードをいじるための Erlang シェルを起動できるということらしい。 なわけでテスト。

% cat test.erl
-module(test).
-compile(export_all).
test() -> io:format('test.\n').

こんなコードを用意しておいて、適当なノードに読み込ませる。

% erl -sname hoge@localhost
Erlang (BEAM) emulator version 5.5.4 [source] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.5.4  (abort with ^G)
(hoge@localhost)1> c(test).
{ok,test}

んで、別のノードを -remsh オプションを使って立ち上げる。

% erl -sname fuga@localhost -remsh hoge@localhost
Erlang (BEAM) emulator version 5.5.4 [source] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.5.4  (abort with ^G)
(hoge@localhost)1> 

別ノードと会話をする都合上、-sname なり -name なりで名前を付けておく必要がある模様。 でもプロンプトの表示が fuga じゃなく hoge なところに注目。

(hoge@localhost)1> test:test().
test.
ok

VM 自体は fuga なんだけど、いじってるのはあくまで hoge の方なんで、hoge の方でしか読み込んでない test モジュールが普通に使える。 まあ、あたりまえっちゃーあたりまえ。

ちなみに ^G で使える User switch command からでもリモートシェルは起動できるので、

% erl
Erlang (BEAM) emulator version 5.5.4 [source] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.5.4  (abort with ^G)
1> net_kernel:start([piyo@localhost, shortnames]).
{ok,<0.32.0>}
(piyo@localhost)2> 
----- ここで ^G を入力している -----
User switch command
 --> ?
  c [nn]   - connect to job
  i [nn]   - interrupt job
  k [nn]   - kill job
  j        - list all jobs
  s        - start local shell
  r [node] - start remote shell
  q        - quit erlang
  ? | h    - this message
 --> r hoge@localhost
 --> c
Eshell V5.5.4  (abort with ^G)
(hoge@localhost)1> test:test().
test.
ok

こういうこともできる。

さらにちなみに、最近気付いたんだけど、Atom としては "_" 以外に "@" も特別扱いなので、hoge@localhost みたいのはシングルクォートで囲まなくても Atom になる。

% [Erlang][Ruby] Erlang で引数に fun() を求められているときの苦悩

…が何日か前に解消された。 教えてくれたのはここのページ(実際ははてなの方だったけど、今はもう消えてるみたい)。 ここは他にも色々参考になるのでありがたいです。

で、具体的にどういうことかというと、OCaml だと

List.map string_of_int [1;2;3]

などと書く場面で、Erlang の関数をそのまま渡す方法がわからなかったので、以前は

lists:map(fun(X) -> integer_to_list(X) end, [1,2,3]).

とか冗長なことを書いてたわけ。 んで、今回これが、

lists:map(fun erlang:integer_to_list/1, [1,2,3]).

と書けるようになってわ〜い、と。 ……って微妙に短くなってないけど、これはたまたま使った関数がモジュール名を省略できるヤツだったからで、他の関数ならもっと嬉しいと思う。 どっちにしろクロージャにするとスタックが一段無駄だし。

で、唐突に Ruby の話になるけど、関数型言語でこういう書き方に慣れてくると、Ruby で

[1,2,3].map { |x| x.to_s }
              ^^^

この変数宣言の部分がすごくウザく感じるんだよね。 一応、関数的メソッドの場合なら、

[1,2,3].each &method(:p)

みたいなのもアリかも知れないが、これはこれで色んな意味であんまりだし、上記の to_s みたいな場合はどうしようもない。 こういうときは何か暗黙の変数が欲しくなる (どこぞの言語の it みたいな)。 実際にそれが導入されるのが良いことなのかは、微妙に美しくない気もして、何とも言えないんだけど。

でも、やっぱりブロックの仮引数は、ブロック外の変数と同一スコープだから名前には多少なりと気を使うわけだし、それが実質ほとんど意味の無い変数だったりするとストレスだよね。 まあ、スコープルールに関しては改善される予定はあるみたいだし、今後に期待しておこう。

実は引数を囲むパイプ文字 (正式名称知らず) も嫌いなんだよな。 位置的に打ちづらいし、キーボードによって場所が違ったりするし(苦笑

% [Erlang] 対話環境で色々やるにあたって、まず読んどいた方が良いもの

c モジュールとか shell モジュールとか。 特に憶えておいた方が良いのは、

f()        全ての変数束縛を解除
f(X)       変数 X の束縛を解除
flush()    シェルプロセスのメールボックスに溜ってるメッセージを消去 (ついでにそのリストを表示)
pid(X,Y,Z) <X,Y,Z> というプロセス ID を返す (list_to_pid 使うより楽)

この辺かね。 特に f 関数を憶えておくと、いちいち新しいシェルを起動したり VM を再起動したりしなくても色々実験ができて楽。

% [Erlang] エラー報告の見方を訓練してみよう

Erlang のエラーメッセージは意味がわからんという話を色んなところで目にした。 まあ、実際慣れてないとわけわからん気はするんだけど、ある程度の肝を押さえてしまえば、わりと何てことない気もする (それでも "わかりやすい" かは微妙だけど)。 つーことで、エラーの報告の見方について、とりとめもなく書いてみる。 って言うか、わしもまだまだ勉強中だから、いつものようにテディベアへの説明だってことで。

まず強く意識しておかなきゃならないのは、手続き型なら「代入」、関数型なら「束縛」などと称される…

X = 1.

こういう式は、Erlang ではパターンマッチであるということ。 まあ、OCaml の let とかもそうなんだけど、OCaml の場合どんどん元の束縛値を隠して新しい値を同じ変数に束縛していけるのに対して、Erlang では「束縛は一度きり」なので「一度何かを束縛した変数は定数と同じ」扱いになる。 上記の例で言えば、X に何も束縛されていない状態であれば、X に 1 が束縛されて両辺が等しくなるのでパターンマッチが成功するけど、もし X に 2 とかが束縛されていると…

1> X = 2.
2
2> X = 1.

=ERROR REPORT==== 8-May-2007::19:37:44 ===
Error in process <0.30.0> with exit value: {{badmatch,1},[{erl_eval,expr,3}]}

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

このようにパターンマッチに失敗してエラー (他の言語で言うところの例外と思って良い) になる。 この 2 行目の X = 1 は 2 = 1 と同じことだからだね。 どうせだから、このエラーから見方を考えていこう。

通常、エラーを起こしたプロセスはその時点でエラーを示す終了値を持って終了する。 この例で言えば、<0.30.0> というプロセス中で {badmatch,1} というエラーを起こして終了している。 では、この終了値の残りの部分 [{erl_eval,expr,3}] てのは何かと言うと、スタックトレースである。 もう少し詳しく説明すると (等幅フォント推奨)、

{ { badmatch, 1 },
              ^ ←エラーを起こした式 (厳密には badmatch の場合は "値")
    ^^^^^^^^ ←エラーの種類
  ^^^^^^^^^^^^^^^ ←エラーメッセージ (Exit Reason)
  [ { erl_eval, expr, 3 } ] }
                      ^ ←アリティ (引数の数) もしくは直近の呼び出しだと引数のリストになる場合も
                ^^^^ ←関数名
      ^^^^^^^^ ←モジュール名
    ^^^^^^^^^^^^^^^^^^^^^ ←一段分のスタック
  ^^^^^^^^^^^^^^^^^^^^^^^^^ ←スタックのリスト

こんな感じ。 「1 という式で badmatch (パターンマッチ失敗) というエラーが起きた」ということを示すタプルと、エラーが起きたときに呼ばれていた関数を示すタプルのリストを要素にするタプルが終了値になっているわけ。

Erlang の対話環境では (ソース読んだわけじゃないので、動作からの想像だけど)、入力したコードを別プロセスで erl_eval:expr/3 という関数を使って評価して、その結果を対話環境側に戻しているんだと思う。

さて、この例だとスタックが一段だけでつまらないので、もう少し派手なエラーを出させてみる。

3> io:format('hello ~s~n', "world").
** exited: {badarg,[{io,format,[<0.23.0>,'hello ~s~n',"world"]},
                    {erl_eval,do_apply,5},
                    {shell,exprs,6},
                    {shell,eval_loop,3}]} **

…って言うほど派手じゃなかったけど、ともあれ。 この場合、badarg がエラーメッセージで、それに続くリストがスタックトレースだ。 さっきの例と違って別プロセスを立てずに直接関数を呼び出してるので、Error in process なんちゃらという表示は無い。 badarg はもちろん「引数がおかしいぞ」という意味である。

io モジュールのソースを読めばわかると思うけど、io:format/2 は第一引数に standard_io を補って io:format/3 に丸投げされる。 その呼び出しが {io,format,[<0.23.0>,'hello ~p~n',"world"]} の部分。 ちなみに、なぜ erl_eval:do_apply/5 から io:format/2 ではなく io:format/3 が直接呼ばれてるかは定かではないが、多分コンパイル時に最適化されているんではないかと思う (つーか単に末尾呼び出しだからスタックトレースに現れるわけが無いということかも)。

ところで上記の式が badarg エラーなのが、なぜなのかわからない人は io モジュールのマニュアルをよく読むこと。 わしは Erlang 触り始めの頃には毎回のように、この間違いをしてたな。 最近ようやく慣れた。

えーと、こんな感じで。 あと一応書いとくと、エラーが起きたときにエラーが伝播するのは link しているプロセスだけで、link していないプロセスがエラーを起こしても何も起こらない (ただし、monitor している場合には 'DOWN' メッセージが届く)。 例えば、こんな感じだと…

15> spawn(io,format,['hoge ~s~n', "fuga"]).
<0.54.0>

spawn 関数だと link はされないので、あきらかに生成したプロセスではエラーが起きているはずだけどエラーは報告されない。 これを、spawn_link 関数に変えると、

17> spawn_link(io,format,['hoge ~s~n', "fuga"]).
<0.57.0>
** exited: {badarg,[{io,format,[<0.23.0>,'hoge ~s~n',"fuga"]}]} **

このようにエラーが報告される。 さらに厳密に言うと、シェルプロセス側でエラーの処理をしていないので、実はこの時点でシェルプロセスも死んでいる。

18> self().
<0.59.0>
19> spawn_link(io,format,['hoge ~s~n', "fuga"]).
<0.61.0>
** exited: {badarg,[{io,format,[<0.23.0>,'hoge ~s~n',"fuga"]}]} **
20> self().                                     
<0.63.0>

このように、何事もなくシェルプロセスが続いているように見えるけど、実は一度死んで新たに起動させられているだけだったりする。 きっとシェルプロセスをモニタしているプロセスが、いろいろよろしくやってくれてるんだろう。

と、ここまで書いて気付いたが、この例だと ERROR REPORT が出てないな (** exited ... というのは self() の終了メッセージだと思う)。 きっとエラーを掴まえて何かをするプロセスがどっかにいるんだろう。 そんで、単に spawn_link を使うと、それを経由しないから ERROR REPORT が表示されないとか、そういう感じだろうか。

なんか毎度毎度締まらないけど、この辺で終了。 あ、エラーメッセージ (正しくは Exit Reason) についてはマニュアルに表があるので読んでおくとためになる。

本日のツッコミ(全3件) [ツッコミを入れる]
% yhara (2007-05-08 20:11)

> この変数宣言の部分がすごくウザく感じるんだよね<br>Ruby界でもそういう話が盛り上がったことがあります(to_proc)。<br>http://wota.jp/ac/?date=20060309<br>Ruby本体に正式採用されるかどうかは…分かりません。

% jijixi (2007-05-08 21:21)

あ、なんか見た覚えがあります。<br>多分、Ruby 本体に取り込まれたら使うでしょうけど、個人的な趣味で言うと微妙に字面が美しくない気はしますね。<br>もう少し「インスタンスメソッドを呼んでるぞ」という気になる見た目だと良いんですが。

% TrackBack (2007-05-10 19:08)

http://jijixi.azito.com/cgi-bin/diary/index.rb?date=20070510#p01<br>jijixi's diary<br>[Erlang] エラーネタの続き<br>この前の話の補足みたいな。 最後の方で ERROR REPORT が出ない云々という話があったけど、あれってどうも ERROR REPORT を吐く役目を持ってるプロセス (たぶんシェルプロセス) が、それを吐く前に exit の伝播によって死んでるのが原因っぽい。だから、例えば↓のように..

お名前:
E-mail:
コメント:

トップ «前の日記(2007-05-06) 最新 次の日記(2007-05-09)» 編集

日記ってのは本来、自分で読み返すためにあるもんだよなあ……
もしくは有名人になったら死後に本になったりとかか?

RSS はこちら

jijixi at azito.com