Hatena::Grouperlang

檜山正幸のErlang未確認情報 RSSフィード

2010-01-21 (木)

プロセス辞書

| 12:13

プロセス辞書は使うな」はまったく正しいと思う。

破壊的代入による副作用が欲しくなると安着に使うヤツがいる。そういうヤツは、、、、それは僕です。

2010-01-20 (水)

funを使うと

| 18:26

Voluntasさん 2010/01/13 11:58 :

fun() でのパターンマッチはエラーが起こったとき優しくないので使うのは最低限にしておいたほうがよさげです。

おっしゃるとおりですなー。foldr内のfunの例ですが:

{'EXIT',{function_clause,[{lists,foldr,
                                 [#Fun<unify2.1.52405648>,{ok,},{ok,}]},
                          {erl_eval,do_apply,5},
                          {erl_eval,expr,5},
                          {shell,exprs,6},
                          {shell,eval_exprs,6},
                          {shell,eval_loop,3}]}}

1.52405648 とか言われてもどれだかワカラン。

2010-01-13 (水)

続・久しぶりに使うと -- 後で読めるようにする

| 10:05

リハビリ・サンプルとして、自分で書いたErlangコードを眺めてみたが、意味不明なところがある。それで思ったことを書きます。

僕の習慣:変数名がだいたい大文字1文字。表向き/建前の理由としては; Erlang変数局所変数しかないし単一代入だから、スコープは狭く、値のバインディングを追跡しやすい。だから、短い名前でも大丈夫。

でも、やっぱり1文字はマズイな。純粋な数値計算やリスト処理だと、N(数値のつもり)とかL(リストのつもり)とかで十分なんだけど、背後になんらかのセマンティクスがあるときは、それを示唆する名前を付けないと意味不明。例えば、LじゃなくてPersonListとかね。変数名が長くなる時はアンダスコア区切りよりキャメルケース(ただし、もちろん先頭大文字)のほうがいいような気がする。(先頭大文字はキャメルに見えないから、キャメルケースって言わないのか?)

それと、アルゴリズム書くより(それが可能なら)列挙した方がいい、と思った。例えば、mod 3 の足し算は普通次のように書くわな。

sum_mod3(N, M) ->
    (N + M) rem 3.

関数の定義域を {0, 1, 2} に確実に制限したいなら:

sum_mod3(N, M) when 0=<N, N=<2, 0=<M, M=<2 ->
    (N + M) rem 3.

だけど、次のように書いた方が分かりやすいんじゃねっ。

sum_mod3(0, 0) -> 0;
sum_mod3(0, 1) -> 1;
sum_mod3(0, 2) -> 2;
sum_mod3(1, 0) -> 1;
sum_mod3(1, 1) -> 2;
sum_mod3(1, 2) -> 0;
sum_mod3(2, 0) -> 2;
sum_mod3(2, 1) -> 0;
sum_mod3(2, 2) -> 1.

別な例を挙げると:

max(N, M) when is_integer(N), is_integer(M) ->
    if
	N >= M -> N;
	true -> M
    end.

上のより、次のほうが分かりやすいと感じる。

max(N, M) when is_integer(N), is_integer(M), N >= M -> N;
max(N, M) when is_integer(N), is_integer(M), N <  M -> M.

冗長ではあるけれど、それぞれのケースをズバリそのまま記述しているから具体性が高い。

短い名前も複雑なアルゴリズムも、読む側に推測だの解釈だのという知的努力を要求する。知的努力を要求しない書き方は、冗長、ダサイ、カッコ悪いになるんだけど、トータルなコストを勘案すると、なるべくアホっぽく書いた方がかえって良いみたい、と思った。

VoluntasVoluntas2010/01/13 11:57Erlang 自体のソースコードも列挙系が多いですよね。読みやすさと言うよりは速度の面もあるのかもしれません。
if の使いどころが未だによくわかってないです :-P

m-hiyamam-hiyama2010/01/13 12:07Voluntasさん、
> 読みやすさと言うよりは速度の面もあるのかもしれません。
定数列挙ならジャンプテーブルのような最適化が出来そうですね。パターンマッチの多方向分岐でも最適化手法があるんかしら?
> if の使いどころが未だによくわかってないです :-P
ifはなんだかワカランですね。whenガードと同じ感じの条件で多方向分岐ってことでしょうが、そんなん、あんまり出てこないし。

2009-06-10 (水)

便利関数を使ったら不便だった例

| 08:41

「コマンドラインなら1行なのに、、、」に書いたコレ:

% @doc ファイルの種類を返す.
% @spec (string()) -> (not_exist | dir | regular | special)
file_type(Filename) ->
  case filelib:is_file(Filename) of
    false ->
      not_exist;
    true ->
      case filelib:is_dir(Filename) of
        true ->
          dir;
        false ->
          % regularとspecialの区別はあんまり当てにならないみたい
          case filelib:is_regular(Filename) of
            true ->
              regular; 
            false ->
              special
          end
      end
  end.

filelibを使ったためにかえって鬱陶しくなった。read_file_info/1 を直接使ったほうが単純。

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

%% 戻り値は file.hrl を参照
file_type(Filename) ->
  case file:read_file_info(Filename) of
    {ok, Info} ->
      Info#file_info.type;
    {error, enoent} ->
      not_exist;
    {error, Reason} ->
      throw(Reason)
  end.

↑これは、裸の値(アトムだけ)かthrow例外で応答する。ok/error方式なら、

file_type_2(Filename) ->
  case file:read_file_info(Filename) of
    {ok, Info} ->
      {ok, Info#file_info.type};
    {error, Reason} ->
      {error, Reason}
  end.

common_errorを使うなら、

 throw({{?MODULE, io_error}, Reason})

とか。

しばらく書いてないと忘れてしまうこと

| 16:58

caseやifの入れ子を使うより、

  1. 引数パターンを使って関数節に分ける
  2. ガードを使って条件を記述する(これも関数節)
  3. あまり複雑にならないなら論理演算子 andalso, orelse を使う

としたほうがたいてい見やすい。

lpgtksgbfplpgtksgbfp2014/01/14 17:18hrffsfsmboh, <a href="http://www.twrxyjffyc.com/">vocqtmovko</a> , [url=http://www.adoypvwbqp.com/]rabrbhjvxx[/url], http://www.mtapblapqb.com/ vocqtmovko

2009-04-08 (水)

乱数からIDを作る

| 13:42

YAWSソースから抜き出したサンプル。

%% -*- coding: utf-8 -*-

%% @doc 乱数に基づく「ほぼID」の生成
-module(randomid).
-export([init/0, gen/0]).

%% spec () -> {integer(), integer(), integer()}
seed() ->
  now(). % {X, Y, Z}

%% @spec () -> ok
init() ->
  {X,Y,Z} = seed(),
  random:seed(X, Y, Z),
  ok.

%% @spec () -> string()
gen() ->
  N = random:uniform(16#ffffffffffffffff), %% 64 bits
  integer_to_list(N).

2009-04-02 (木)

void, nullがない。

| 16:34

voidとかnullのように、標準的などうでもいい型、無効値がない。

アームストロングvoidというアトムが好きらしい。okを返している例も多い。undefinedもよくあるな。僕はnoneが好きでよく使っている(標準ライブラリでもnoneの例がある)。

最近思ったのだが、noneやundefinedは、積極的に「値がない/未定義」を意味するから、ほんとにどうでもいい、値を使うのはやめてくれ、捨ててくれ、ってときはvoidのほうがいいかも知れない。捨てられるのだから何だっていいんだけど、コンベンションとしてね。

エラー処理と責任問題

| 16:34

apply、アブネーー関数の存在確認、例外のリスローの問題だが、状況にもよるが、apply(M, F, A) だけして何もしないのが正解かもな。

undefだけ捕まえるのは明らかにマズイ、事前に存在確認しても例外が出るときは出る。対処のしようがないからそれは捕まえない。だったら、事前に存在確認もいらねーんじゃん。ダメなときゃダメなんだから。

コールバックを正しく作って正しく配備するのはそっち(誰?)の責任だから、呼び出す側は「正しくくセットされている」という前提で仕事して、そうじゃないなら天災人災だが)と考える。

Erlangだと「俺の責任」以外は「天災/もう知らん」と思ったほうが健全そうだ。

wbdjkcqcpswbdjkcqcps2013/08/28 02:13vvoskfsmboh, <a href="http://www.teetlxcydi.com/">zeptfodakf</a> , [url=http://www.lfprwfvnog.com/]hrjlynvtod[/url], http://www.gjjqilpgam.com/ zeptfodakf

thxpfkitzuthxpfkitzu2014/03/18 18:03mxfmdfsmboh, <a href="http://www.cgeuxhshnw.com/">jxarsqwhxg</a> , [url=http://www.pfyavtpqjp.com/]pficlukqwf[/url], http://www.alpsvdvtvq.com/ jxarsqwhxg

lelocffzgtlelocffzgt2014/04/12 15:46vsmpafsmboh, <a href="http://www.mvqsrcvjyt.com/">jwqzrmhbxn</a> , [url=http://www.wnopwsfrwr.com/]xmzsismghz[/url], http://www.zpckatvivp.com/ jwqzrmhbxn

2009-04-01 (水)

LETマクロとIFマクロ

| 10:52

Eunitのヘッダから:

-define(LET(X,Y,Z), ((fun(X)->(Z)end)(Y))).

-define(IF(B,T,F), (case (B) of true->(T); false->(F) end)).

このほうが見やすい

| 17:19

関数ボディのトップレベルでifが現れたら、関数節にしたほうが見やすい。

foo(A) ->
 if is_atom(A) ->
     % (1)
    true ->
     % (2)
 end.

foo(A) when is_atom(A) ->
  % (1)
  ;
foo(A)  ->
  % (2)
  .

case式内で同じ代入していれば、1つにまとめる。

case Expr of
 Patt_1 ->
  Foo = Expr_1;
 Patt_2 ->
  Foo = Expr_2;
 % ...
 Patt_n ->
  Foo = Expr_n
end,

Foo = 
  case Expr of
   Patt_1 ->
    Expr_1;
   Patt_2 ->
    Expr_2;
   % ...
   Patt_n ->
    Expr_n
  end,

try式のときも同じ。

tree-like構造の変更

| 09:04

あのひとりごとの背景をまとめる。

ファイルシステムに似た木構造を考える。

ファイルシステム ここでのデータ構図
ディレクトリ 中間ノード
ファイル ノード
ファイルの中身
ファイルの種類 値の型

中間ノードを単にノードと呼ぶことにして、

-record(node, {
          options = [],
          children = []
          }).

つう感じ。optionsはXMLの属性みたいな雰囲気。都合により、値の型情報は値ノードには含まれず、別なところ(スキーマ)に入っている。

パス /a/b/c の内部表現は [a, b, c] というアトム・リスト。パスを渡されて値を取ってくるのは割と簡単。[追記]「このほうが見やすい」に従って書き換えるのがよろし。[/追記]

%% -*- coding: utf-8 -*-

%% @doc tree-like構造のサンプル.
%% いろいろと手抜き。
-module(toytree).

%-export([]).
-compile(export_all).

-record(node, {
          options = [], % サンプルでは未使用
          children = []
         }).

-define(is_node(X), is_record(X, node)).
-define(get_children(Node), Node#node.children).
-define(set_children(Children, Node), 
        Node#node{children = Children,
                  options = Node#node.options}).

get_value(Name, Data) ->
  if ?is_node(Data) -> % proplists:get_value/1 にお任せー
      % 注意:
      % proplists:get_value/1 は、Nameに対応する値がないと
      % undefinedを返す。undefinedという値があったときと
      % 区別がつかいない。まーイイトシヨウ。
      proplists:get_value(Name, ?get_children(Data));
     true ->
      throw(not_a_node_cannot_get)
  end.

get_value_path(_Path = [], Data) -> % まんま出してしまう、ちとヤバイが
  Data;
get_value_path([Name|Rest], Data) ->
  Next = get_value(Name, Data), % undefinedの判定もしなくていいや、死にます
  get_value_path(Rest, Next).

さて、パスを渡されて、そこの値を書き換えることが問題。まず、パスのリストを左から見て木を下にたどる。パスが [A1, A2, ..., An]のとき、Root = N0とすると、次のような感じでたどる。

  • N0 -(A1)→ N1 -(A2)→ ... Nn -1-(An)→ Nn

このとき、くだってきたパスの履歴を引数で記録しておいて、木全体の再構築を下から上に実行。文章で説明はメンドウだが;

set_value(Name, Value, Data) ->
  if ?is_node(Data) ->
      Children = proplists:delete(Name, ?get_children(Data)),
      ?set_children([{Name, Value} | Children], Data);
     true ->
      throw(not_a_node_cannot_set)
  end.

set_value_path(_Path = [], Value, _Data) ->
  Value;
set_value_path(Path, Value, Data) ->
  set_value_path(_Visited = , _Walked = , Path, Value, Data).

set_value_path(Visited, Walked, [Key], Value, Current) ->
  NewCurrent = set_value(Key, Value, Current),
  rebuild(Visited, Walked, NewCurrent);
set_value_path(Visited, Walked, [Key|Rest], Value, Current) ->
  {SavingCurrent , Next} = 
    case get_value(Key, Current) of
      undefined -> % ノードがないときは新しく作る、ここがメンドー
        NewNext = new_node(),
        NewCurrent = set_value(Key, NewNext, Current),
        {NewCurrent, NewNext};
      Data ->
        {Current, Data}
    end,
  set_value_path([SavingCurrent|Visited], [Key|Walked], Rest, Value, Next).

rebuild(_Visited = [UpperNode|V_Rest], _Walked = [Key|W_Rest], NewNode) ->
  NewUpperNode = set_value(Key, NewNode, UpperNode),
  rebuild(V_Rest, W_Rest, NewUpperNode);
rebuild([], [], Node) ->
  Node.

ここで、rebuildがミソ。変更箇所は常にリーフ、記録しておいたルートからのチェーンを逆向きにたどりながら、次々と構造を作り替えていく。メモリのお祭り騒ぎでコピーしては捨てながら完全に作り直す。その再構築はボトムアップ

このtoytreeをもう少し複雑にしたモノを考えていて、その挙動を試行錯誤するために、minshを作った。toytreeのminshに対するコールバックは次のよう。

%%%% minshのコールバック関数 %%%%%

print(Data) ->
  print(0, ".", Data).

print(Indent, Name, Data) ->
  do_indent(Indent),
  if ?is_node(Data) ->
      io:format("+- ~s~n", [Name]),
      Children = ?get_children(Data),
      lists:foreach(
        fun({Key, Value}) ->
            print(Indent + 1, Key, Value)
        end,
        Children);
     true ->
      io:format("|- ~s ~p~n", [Name, Data])
  end.

do_indent(Indent) ->
  io:format(lists:duplicate(Indent*3, 32)).

command_init(_InitArg) ->
  new_node().

help_str() ->
  ("s {Path, Val}  -- set_value_path(Path, Val, Tree)\n"
   "ENTER          -- prety-print\n"
   "SP {Path, Val} -- set_value_path and pretty-print\n"
   "SP Path        -- get_value_path(Path, Tree)\n"
   ).

command('?', _Arg, State) ->
  io:format(help_str()),
  {ok, State};
command(s, {Path, Val}, State) ->
  NewState = set_value_path(Path, Val, State),
  {ok, NewState};
command('', Arg, State) -> % 改行だけは
  case Arg of
    none ->
      print(State),
      {ok, State};
    {Path, Val} ->
      NewState = set_value_path(Path, Val, State),
      print(NewState),
      {ok, NewState};
    Path when is_list(Path) ->
      V = get_value_path(Path, State),
      io:format("Value = ~p~n", [V]),
      {ok, State};
    _ ->
      io:format("command syntax error~n"),
      {ok, State}
  end;
command(_, _, State) ->
  io:format("illegal command~n"),
  {ok, State}.

2009-03-31 (火)

関数の存在確認、例外のリスロー

| 10:09

「apply、アブネーー」の問題だが、そもそも関数の存在確認は次のようすべきだろう*1

is_defined(Mod, Fun, Arity) ->
  Exports = 
    try 
      apply(Mod, module_info, [exports])
    catch
      error:undef ->
        []
    end,
  case lists:keysearch(Fun, 1, Exports) of
    {value, {Fun, Arity}} ->
      true;
    _ ->
      false
  end.

それはそうとして、例外のリスローだが、次はダメ。

bad() ->
  1/0.

call_bad() ->
  try 
    bad()
  catch
    error:Term ->
      io:format("~p ~p~n~n", [Term, erlang::et_stacktrace()]),
      erlang:error(Term) % ここで新たなスタックトレース生成
  end.

call_call_bad() ->
  try 
    call_bad()
  catch
    error:Term ->
      io:format("~p ~p~n~n", [Term, erlang:get_stacktrace()])
  end.

erlang:raise/3は、第3引数スタックトレースを指定できるので、erlang:raise(error, Term, erlang:get_stacktrace()) とする。erlang:raise/3は、この目的以外で使うことはほとんどないだろう。無闇に使うと、スタックトレースを捏造することになり混乱を招く。

*1:R13からはlists:keyfind/3が推奨。

ValentinaValentina2012/08/24 18:51This piece was cogent, well-witrten, and pithy.

hzkiyhzhzkiyhz2012/08/25 15:48ulmEt6 <a href="http://gcoiyuwzcreu.com/">gcoiyuwzcreu</a>

kpyqduozykpyqduozy2012/08/27 00:13KrkVK3 <a href="http://dmuynnkjqulo.com/">dmuynnkjqulo</a>

dajugzudajugzu2012/08/27 22:29aIT0Zi , [url=http://jjbybzuabnzb.com/]jjbybzuabnzb[/url], [link=http://dffjjwepljnx.com/]dffjjwepljnx[/link], http://nbseayfagmif.com/

2009-03-27 (金)

init内のコマンドライン解析

| 14:03

オリジナル。誤解しやすい。

check(<<"-extra">>) -> start_extra_arg;
check(<<"-s">>) -> start_arg;
check(<<"-run">>) -> start_arg2;
check(<<"-eval">>) -> eval_arg;
check(<<"--">>) -> end_args;
check(X) when is_binary(X) ->
    case binary_to_list(X) of
	[$-|_Rest] -> flag;
	_Chars     -> arg			%Even empty atoms
    end;
check(_X) -> arg.				%This should never occur

get_args([B|Bs], As) ->
    case check(B) of
	start_extra_arg -> {reverse(As), [B|Bs]};
	start_arg -> {reverse(As), [B|Bs]};
	start_arg2 -> {reverse(As), [B|Bs]};
	eval_arg -> {reverse(As), [B|Bs]};
	end_args -> {reverse(As), Bs};
	flag -> {reverse(As), [B|Bs]};
	arg ->
	    get_args(Bs, [B|As])
    end;
get_args(, As) -> {reverse(As),}.

意味を取りやすいように修正したヤツ。

is_end_params(<<"--">>) ->
  true;
is_end_params(_Other) ->
  false.

is_flag(X) when is_binary(X) ->
  case binary_to_list(X) of
    [$-|_Rest] -> true;
    _Chars     -> false
  end.

%% get_args/2 を書き換えた:
%% フラグパラメータを取得する
%% {フラグパラメータのリスト, 残余リスト} の形で返す

get_flag_params(Bs) ->
  get_flag_params(Bs, []).

get_flag_params([B|Bs], As) ->
  case is_end_params(B) of
    true -> {reverse(As), Bs};
    _ ->
      case is_flag(B) of
        true -> {reverse(As), [B|Bs]};
        _ ->
          get_flag_params(Bs, [B|As])
      end
  end;
get_flag_params(, As) -> {reverse(As),}.

リストのお尻に追加

| 14:07

%% -*- coding: utf-8 -*-

-module(append).
% -export([]).
-compile(export_all).

-define(TIMES, 100*100*2).

append_1(N, Limit, List) ->
  if (N =:= Limit) ->
      lists:reverse(List);
     true ->
      append_1(N + 1, Limit, [N|List])
  end.

append_2(N, Limit, List) ->
  if (N =:= Limit) ->
      List;
     true ->
      NewList = lists:reverse([N|lists:reverse(List)]),
      append_2(N + 1, Limit, NewList)
  end.

append_3(N, Limit, List) ->
  if (N =:= Limit) ->
      List;
     true ->
      NewList = lists:append(List, [N]),
      append_3(N + 1, Limit, NewList)
  end.

do() ->
  statistics(runtime),
  statistics(wall_clock),
  append_1(0, ?TIMES, []),
  {_, Runtime_1} = statistics(runtime),
  {_, Wall_Clock_1} = statistics(wall_clock),
  io:format("1. ~p (~p) milliseconds~n", [Runtime_1, Wall_Clock_1]),

  statistics(runtime),
  statistics(wall_clock),
  append_2(0, ?TIMES, []),
  {_, Runtime_2} = statistics(runtime),
  {_, Wall_Clock_2} = statistics(wall_clock),
  io:format("2. ~p (~p) milliseconds~n", [Runtime_2, Wall_Clock_2]),

  statistics(runtime),
  statistics(wall_clock),
  append_3(0, ?TIMES, []),
  {_, Runtime_3} = statistics(runtime),
  {_, Wall_Clock_3} = statistics(wall_clock),
  io:format("3. ~p (~p) milliseconds~n", [Runtime_3, Wall_Clock_3]).

3> c(append).
{ok,append}
4> append:do().
1. 10 (10) milliseconds
2. 10785 (15382) milliseconds
3. 7631 (10255) milliseconds
ok
5> append:do().
1. 10 (10) milliseconds
2. 10706 (14862) milliseconds
3. 5988 (8182) milliseconds
ok
6> append:do().
1. 0 (0) milliseconds
2. 9274 (13560) milliseconds
3. 5127 (6719) milliseconds
ok
7> append:do().
1. 0 (0) milliseconds
2. 9113 (13600) milliseconds
3. 7371 (10405) milliseconds
ok
8>

consで先頭に足していって一気にreverse、これは速い。毎回reverseを2回するとさすがに遅い。reverseはできるだけまとめてやるべき(当たり前だ)。lists:append/2もそんなには速くないなー、reverseを2回よりはマシだが。

jj1bdxjj1bdx2009/03/28 11:33あまり細かいオペレーションは,別の言語で外部に出してしまう,というのが得策かもしれませんね.

m-hiyamam-hiyama2009/03/28 12:33jj1bdxさん、
> 別の言語で
そうですね。どうせ必要そうなので、リンクインドライバの書き方を習おうかと思ってます。それとは別に、Erlangによる非破壊的操作のノウハウも必要ですね。パフォーマンスが深刻じゃない状況では、Erlangで書く方がさすがに容易ですから。

2009-03-26 (木)

デザインパターン

| 09:22

Erlangデザインパターンについて、探したり試したりしてみようと思う。よく知られているデザインパターンErlangで書いてみる、つうのもあるにはある。

が、やっぱりErlangっぽい書き方をしたい。

上記のエントリーの手法はオートマトンでなくても使えるからパターンと言っていいだろう。

OTPにおける最大のデザインパターンはGeneric Behaviour(あるいはCallback Module)パターンだな。DIに近い感じがする。mapやfoldlのような高階関数の発想をモジュールまで拡張した、とも言えるかな。

last callがスタックを上書きするのを利用して割り込みをかけたりするのもErlangっぽいと思う。関数レベルで継続データを渡して再入可能にするとかも面白い。関数パイプライン(チェーン)も僕は好きだな。

メッセージの間接化用に考えた(平凡な、ありがちな)パターンも2つある。ぼちぼちポチポチと書いていこう。

Erlangデバッガ

| 15:29

使い方の説明は、PDF文書 http://www.erlang.org/doc/pdf/debugger.pdf が便利。ただし、P.18までで十分。P.19以降はmanページと同じだから、http://www.erlang.org/doc/apps/debugger/ とか http://www.erlang.org/doc/man/ を見ても同じ。

kernelの動きを追えるかと思ったがそりゃ無理だった。けっこう高水準な所でデバッグ専用インタプリタエミュレータとは別)が動いている模様。まー、デバッガ使うこともあるだろうから知っておいて損はない。本編に書くかも知れないが、今日はメモだけ。

  1. コンパイル時に debug_infoオプションがないとダメ。
  2. .beamファイルと.erlファイルの両方にアクセス可能じゃないとダメ。
  3. im()またはdebugger:start()で起動。iモジュールシェルインポートされている。
  4. GUI以外に、i, intモジュール関数インターフェースを提供する。
  5. 使うウィンドウは、Monitor(メイン)、ViewModule(ソースコード表示)、Attach Process(個々のプロセスと接続)の3種。以下、ウィンドウ名をブラケットに入れて[Monitor]などと記す。

手順:

  1. EShellからデバッガ起動。[Monitor]が出る。
  2. Module > Interpret でモジュール(.erl, .beam)を選択・追加。デバッグ対象は、モジュール群。
  3. [Monitor]の左にモジュールリスト、主領域にプロセスグリッド
  4. モジュールリストのモジュールエントリーダブルクリックで[ViewModule]
  5. ソース行でダブルクリック。その行にブレークポイントを設定できる。
  6. EShellで普通に操作。プロセスブレークポイントに達すると当該プロセスが停止する。
  7. [Monitor]のプロセスグリッドの当該の行をダブルクリックすると[Attach Process]ウィンドウが出る。
  8. [Attach Process]のボタンを使って実行を制御できる。

GUIGSで作ってあるからショボイし、ウィンドウが何枚も出て使いにくい。とはいえ、必要な機能は揃っているかな。デバッグよりは、既存プログラムの動作や構造の理解と確認に使おうと思ったが、前述のごとく、kernelとstdlibとデバッガ自身は対象外(ムーッ、他の方法を考えよう)。

個人的な好みでは、GUIよりはコマンドラインのほうがいいけどな。-- Emacsから使えれば、せめてEShellと統合されてれば、もっと日常的に使うんだが、、、ウィンドウを移動する手間がカナワン!

普通の用途ではフツーに使えるだろうから、開発中はdebug_info付けておくとよいだろう。Emakefile、Makefileとかに書いて。ただし、debug_info付きのbeamからソースを再現可能。

ブレイクポイントはソース行以外に、関数ブレイクポイントというのがある。また、条件付きブレイクポイントつうのが使いでがありそうだ。ブレイクポイントにC-モジュール(C-Module)のC-関数(C-Function)を指定する。ここで「C」はConditionのこと。

C-関数引数(1個)にはBindingsが入る。これは、なんちゃってのアレみたいだよ。オフィシャルには、int:get_bindig(Name, Bindings) -> {value, Val} | unbound しかサポートされてない。

%% -*- coding: utf-8 -*-

%% @doc 条件付きブレイクポイントのテスト
-module(c_test).

-export([print_x/1]).

%% @doc 変数Xの値を表示する
%% @spec (Bindings) -> bool()
print_x(Bindings) ->
  io:format("~p~n", [int:get_binding('X', Bindings)]),
  true.

こういう関数を条件として指定する。戻り値がtrueならブレイクして、falseならそのまま続行する。

あと、Windows上で右クリックは効かないようだ。

foldlとfoldr

| 16:45

「Before/Afterリスト方式によるストリーム風処理」に書いたように、リストの順番が逆転する現象がよく起こる。僕は順番関係が非常に苦手。当然にfoldlとfoldrの区別も分からなくなる。毎回、短いサンプルを書く。

7> lists:foldl(fun(X, A)->[X|A] end, [], [1, 2, 3]).
[3,2,1]
8> lists:foldr(fun(X, A)->[X|A] end, [], [1, 2, 3]).
[1,2,3]
9> 

ってことは(と、ここでまた混乱している)。'l'が左から見ていくのか(なにしろ'l'だからな)。だが、consで積み上げていくときは、'r'を使えばreverseしなくて済む、と。

どうせ忘れるけど。