Hatena::Grouperlang

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

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ガードと同じ感じの条件で多方向分岐ってことでしょうが、そんなん、あんまり出てこないし。

2010-01-12 (火)

久しぶりに使うと

| 15:27

すっかりErlangはご無沙汰。ものすごく久しぶりに使った。12月に使っていた機械が壊れたのでインストールからやり直し。おっ、GUIはwxになったんですか、GSより綺麗だ。unicodeってモジュールも入ったが、それで悲惨な日本語処理がどうにかなるかどうかは分からない。

さて、体験したこと感じたこと; 以前は、OSシェルや他のインタプリタ使っていても行末でピリオドを打ってしまって困ったが、今はピリオドを忘れて困る。データが全部イミュータブルだってことを忘れてイライラする。まー、これは以前からイライラしていたか。

一方、ガード付き関数節がたくさん書けること、引数パターンやcase式で場合分けが書けること、リスト処理が比較的充実していること、などはシミジミとありがたい。今はプロセス使う用途がないので、プロセスがありがく感じる状況じゃない。

2009-04-04 (土)

JSON in Erlnag

| 15:22

本編に以前書いたけど、繰り返す。

  1. YAWS付属のjson.erl (http://yaws.hyber.org/
  2. LShft社提供のrfc4627.erl (http://hg.opensource.lshift.net/erlang-rfc4627/
JSON YAWS LShift
true, false, null true, false, null true, false, null
number number() number()
string string() binary()
array {array, element_list()} array()
object {struct, proplist()} {obj, [{key(), value()}]}

// yw_ YAWS版
// ls_ LShift版

number() = integer() | float()
element_list() = [yw_value()]
proplist() = [{yw_key(), yw_value()}]
yw_key() = string() | atom()
yw_value() = (true | false | null) | 
             number() |
             string() |
             {array, element_list()} |
             {struct, proplist()}

array() = [ls_value()]
ls_key() = binary() | atom() 
ls_value() = (true | false | null) |
             number() |
             binary() | 
             array() | 
             {obj, [{ls_key(), ls_value()}]}

Erlangに標準で入ったら追加しよう。

2009-04-02 (木)

size/1, tuple_size/1

| 17:31

size/1以外にtuple_size/1てのもある。もちろん、tuple専用。なぜか、binary_size/1はない。sizeが型オーバーロードしているなら、listのlengthも分かればいいのに。

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 (水)

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-30 (月)

分散データ構造

| 08:58

大きなデータは、プロセスに分けて持たせればいいのか。PIDがポインタの代わり。DNSが参考になるかも。

2009-03-28 (土)

イミュータブルのいいところ

| 14:23

ひとりごとで嘆いていたけど、イミュータブルデータにはメリットもある。

大規模なミュータブルデータを書き換えているときに、途中で何か変なことに気が付いたとする。そこで止めてしまうとデータの整合性が取れない。で、もとに戻す必要があるなら、結局はコピーを取るとか複雑なロールバック手順を実装することになる。

イミュータブルなら、もとのデータはいじってないので、「変だなコリャ」と思ったら、処理関数は例外を投げて仕事をおっぽり出しても別に問題ない。呼ぶ側で例外をキャッチしたとき、渡したデータは何も変わってない(引数を相手側スタックにコピーして呼ぶのと似ている)。データ構造に関する操作が常にトランザクションになっているわけだ。

パフォーマンス低下と(場合により)処理の複雑化はかなり痛いペナルティだが、メリットのほうに目をやって、冷静になることにした(イライラしないように)。

2009-01-07 (水)

JanayaJanaya2011/09/20 09:53Okay I'm convniecd. Let's put it to action.

yrkhmlupbuyrkhmlupbu2011/09/20 22:20rbbaYZ <a href="http://wdwfiwoopbol.com/">wdwfiwoopbol</a>

iumvocphkiumvocphk2011/09/25 00:46RpyfFI <a href="http://dlrpdnmflluc.com/">dlrpdnmflluc</a>

sevynosevyno2011/10/03 22:10NjWvcI , [url=http://nuffnjoqzdwq.com/]nuffnjoqzdwq[/url], [link=http://fgsecqohphsz.com/]fgsecqohphsz[/link], http://jserphvnmahp.com/

XrizantemaXrizantema2012/12/26 16:34Stay with this guys, you're helping a lot of peploe.

fqgrkrfqgrkr2012/12/28 16:561oCWO9 , [url=http://mujbcsouewty.com/]mujbcsouewty[/url], [link=http://gsgffufquodc.com/]gsgffufquodc[/link], http://knewqhwfgske.com/

2008-12-30 (火)

アンケート

| 16:18

Erlangに関するアンケート結果。アンケートに関するメタデータがない(見つからないだけ?)のがナン(難)だが、面白い。