コーディング |
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 とか言われてもどれだかワカラン。
リハビリ・サンプルとして、自分で書いた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.
冗長ではあるけれど、それぞれのケースをズバリそのまま記述しているから具体性が高い。
短い名前も複雑なアルゴリズムも、読む側に推測だの解釈だのという知的努力を要求する。知的努力を要求しない書き方は、冗長、ダサイ、カッコ悪いになるんだけど、トータルなコストを勘案すると、なるべくアホっぽく書いた方がかえって良いみたい、と思った。
すっかりErlangはご無沙汰。ものすごく久しぶりに使った。12月に使っていた機械が壊れたのでインストールからやり直し。おっ、GUIはwxになったんですか、GSより綺麗だ。unicodeってモジュールも入ったが、それで悲惨な日本語処理がどうにかなるかどうかは分からない。
さて、体験したこと感じたこと; 以前は、OSのシェルや他のインタプリタ使っていても行末でピリオドを打ってしまって困ったが、今はピリオドを忘れて困る。データが全部イミュータブルだってことを忘れてイライラする。まー、これは以前からイライラしていたか。
一方、ガード付き関数節がたくさん書けること、引数パターンやcase式で場合分けが書けること、リスト処理が比較的充実していること、などはシミジミとありがたい。今はプロセス使う用途がないので、プロセスがありがく感じる状況じゃない。
言語機能 |
fun式の場合
8> (fun (0) -> 1; (1) -> 2; (2) -> 3 end)(1) . 2 9>
と書ける。けっこうスッキリしている。
(0) -> 1; (1) -> 2; (2) -> 3.
という書き方が、普通の関数定義でも使えるといいな。
succ(0) -> 1;
(1) -> 2;
(2) -> 3.
とか、
succ (0) -> 1; (1) -> 2; (2) -> 3 .
いいね、いいね。でもモチロン使えない(泣)。
[追記]余計なことが書いてないのがいいし、表みたいな感じの関数節をイッパイ書いた後で関数名をリネームするとき便利だと思う。ムキになって無理クリ:
succ(N) -> (fun (0) -> 1; (1) -> 2; (2) -> 3 end)(N).
エラーしたときの関数名が '-succ/1-fun-0-' みたいになってしまうのが難点。[/追記]
[追記の追記] funで囲うより、内側にcaseを入れたほうがスッキリでしょ、って?
succ(N) -> case N of 0 -> 1; 1 -> 2; 2 -> 3 end.
確かにそうだ。
コーディング |
「コマンドラインなら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})
とか。
サンプル |
とあるディレクトリから下に、サイズ1メガ(1024*1024 = 1048576 ≒ 1000000)以上のファイルがあるかどうかを調べる必要があった。
$ find the/dir -type f -size +1000000c -print
で出来た。
が、なぜか(なぜだ?)Erlangでも書いてみようと思って、無駄に丁寧に書いた。あー無駄だ。
100> check_size:traverse("the/dir", fun check_size:check_size/4, [1000000]).
↑で、先のfindコマンドとほぼ同じ。
%% -*- coding: utf-8 -*- -module(check_size). -compile(export_all). % @doc ファイルの種類を返す. % @spec (string()) -> (not_exit | 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. % @equiv traverse(TopDir, Action, []) traverse(TopDir, Action) -> traverse(TopDir, Action, []). % @doc 起点となるディレクトリから再帰的にディレクトリツリーをたどり、 % 各ファイルに対して指定されたアクションを実行する. % % == アクション関数の仕様 == % <dl> % <dt>第1引数</dt><dd>ディレクトリ名</dd> % <dt>第2引数</dt><dd>ファイルベース名</dd> % <dt>第3引数</dt><dd>ファイルのタイプ (not_exit | dir | regular | special)</dd> % </dl> % % 他に追加の引数をいくつでも付けてよい. % 追加引数は、リストにまとめて traverse の第3引数に渡す. % @spec (string(), function(), [any()]) -> ok traverse(TopDir, Action, MoreArgs) -> {ok, Names} = file:list_dir(TopDir), lists:foreach(fun (Name) -> do_action(TopDir, Name, Action, MoreArgs) end, Names). % @doc 関数として渡されたアクションを、 % 指定のディレクトリとファイル名(basename)に対して実行する. % @spec (string(), string(), function(), [any()]) -> ok do_action(Dir, Name, Action, MoreArgs) -> Filename = filename:join(Dir, Name), Type = file_type(Filename), Fullargs = [Dir, Name, Type | MoreArgs], case Type of dir -> apply(Action, Fullargs), traverse(Filename, Action, MoreArgs); _Other -> apply(Action, Fullargs), ok end. % @doc traverseのアクション関数、ファイルサイズのチェック. % @spec (string(), string(), atom(), number()) -> ok check_size(Dir, Name, Type, Limit) -> File = filename:join(Dir, Name), case Type of regular -> Size = filelib:file_size(File), if Size >= Limit -> io:format("!! ~s ~p~n", [File, Size]); true -> ok end; dir -> io:format("[~s]~n", [File]); special -> ok end.
Tips |
printfの"%2d"みたいのが今までできなかった(苦笑)。
100> io:fwrite("~5.8.0B~n", [31]).
00037
ok
101> io:fwrite("~6.16.0B~n", [31]).
00001F
ok
102> io:fwrite("~6.10._B~n", [31]).
____31
ok
103>
ま、調べりゃすぐ分かることだけど。
なんでBなのか? わからん、謎じゃ。
[追記]パディング文字は~も含めて何でもいいようだが、*はダメ。*は、なんか特殊みたい。[/追記]
YAWS |
Argに何でも入っているが、まず、request内にpathがある。
abs_pathじゃないときがあるかどうかは不明。abs_pathじゃないリクエスト? イメージできない。
argの直下にあるpathinfoがけっこう使いやすい。
if の使いどころが未だによくわかってないです :-P
> 読みやすさと言うよりは速度の面もあるのかもしれません。
定数列挙ならジャンプテーブルのような最適化が出来そうですね。パターンマッチの多方向分岐でも最適化手法があるんかしら?
> if の使いどころが未だによくわかってないです :-P
ifはなんだかワカランですね。whenガードと同じ感じの条件で多方向分岐ってことでしょうが、そんなん、あんまり出てこないし。