Hatena::Grouperlang

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

 | 

2009-03-17 (火)

Before/Afterリスト方式によるストリーム風処理

| 09:42

データ処理の中間結果を引数に保持しながら、再帰呼び出しを次々と行う方式はすごく多用される。データはリストや文字列整数のリストに過ぎない)だが、ストリームのイメージで扱うときにこの方法が使われる。

まずは実例、文字列の大文字化(string:to_upper/1と同じ)。

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

%% @doc
-module(toupper).
-export([to_upper/1]).

%% @doc アスキー英小文字を大文字にする
%% @spec char_to_upper(char()) -> char()
char_to_upper(C) ->
  if ($a =< C andalso C =< $z) -> 
      C - 32;
     true ->
      C
  end.

%% @doc 文字列を大文字化する
%% @spec to_upper(string()) -> string()
to_upper(S) ->
  to_upper(S, []).

to_upper([Before1|BeforeRest], After) ->
  to_upper(BeforeRest, [char_to_upper(Before1)|After]);
to_upper([], After) ->
  lists:reverse(After).

まだ処理してないデータを第1引数、処理後のデータを最後の引数、必要なら中間結果を保持する引数を何個か入れる。要素に対する処理をFunとすると、

  • [B|Bs], A → Bs, [Fun(B)|As]

というステップを重ねるのが基本。

で、何が言いたいかというと、最後にreverseが必要。僕は毎度忘れる^^;

Erlangに限らずリスト処理では、先頭への追加(cons)は軽いが、末尾への追加は重い。とはいえフィルター処理では、入力ストリーム先頭から読んだモノは、出力ストリーム末尾に吐き出すのであって、「入力ストリーム先頭→加工→出力ストリーム先頭」では、出力側が逆順になる。そこでreverseが登場するわけだ。

Erlangのlists:reverseがメチャガンバッテ最適化されているのは、この用途に多用するからでしょ、たぶん。

[追記]追加サンプル

これは Before/Afterと雰囲気が違う。が、スタックにまず展開してから、それを畳みながら計算するのとも違う。

index(A, List)  ->
  index(A, 1, List).

index(_A, _N, []) ->
  -1;
index(A, N, [A|_]) ->
  N;
index(A, N, [_|Rest]) ->
  index(A, N + 1, Rest).

[/追記]

コマンドライン引数 (2)

| 14:33

実際的な対処としては、プレーン引数という概念を一切使わなければそれで済む。これ以上詮索するのは虚しい話なんだが、気になるのでまた少し調べた。推測が多いのでアテにならないけど。

どうも、マニュアル(または仕様)の著者と、CパートのプログラマErlangパートのプログラマで意識がだいぶズレてるようだ。

http://www.erlang.org/doc/man/erl.html :

Plain arguments are not interpreted in any way. They are also stored by the init process and can be retrieved by calling init:get_plain_arguments/0. Plain arguments can occur before the first flag, or after a -- flag. Additionally, the flag -extra causes everything that follows to become plain arguments.

これによれば、プレーン引数

  1. 最初のフラグの前
  2. --フラグの直後
  3. -extraフラグに後続する全ての引数

のハズ。僕は、"after a -- flag"を誤読していて、-extraと同じく、--が出現したらそこから先はフラグの認識をしなくなるかと思っていた。Cパートの引数処理を見ると、どうも僕と同じ解釈(誤解)をしているっぽい。

    while (i < argc) {
        if (argv[i][0] != '-') {
            erts_usage(); // 終了してしまう
        }
        if (strcmp(argv[i], "--") == 0) { /* end of emulator options */
            i++;
            break;
        }
        switch (argv[i][1]) {
        // 
        // 省略
        //
        default:
            erts_fprintf(stderr, "%s unknown flag %s\n", argv[0], argv[i]);
            erts_usage();
        }
        i++;
    }

"--"はオプションフラグ)解析ループをbreakで終了させる。

'+'で始まるオプションエミュレータフラグ)の解析部分と、CパートからErlangパートへのコマンドライン受け渡しがイマイチ分からないのだが、init.erlを見てみた。Erlangで書かれているから解読しやすい。

[追記]erts/etc/common/erlexec.c がホントの起動コマンドのようだ。これのmain()から execv(emu, Eargsp) からエミュレータのmain()を呼んでいる。このとき、引数の配列を編成し直してからエミュレータに渡している。よって、erlexec内での引数処理も見ないとダメだね。[/追記]

Erlangパートinit.erlの作者は、明らかに"--"を違った意味で解釈している。"--"をend_argsというシンボルで表しているが、argsはなんと「フラグ引数」を意味している。つまり、-foo bar baz -zot ... というコマンドラインがあると、bar baz はフラグ-fooの引数パラメータとか呼んで区別すべき!!)と呼んでいる。そのフラグパラメータの終了を"--"でマークするという解釈(これは間違いない)。

したがって、-foo bar -- baz -zot なら、-fooのパラメータはbarだけとなり、bazはプレーン引数となる。-foo -- bar baz -zot なら、-fooのパラメータはナシ、bar bazはプレーン引数となる。また、--がどのフラグよりも前に出現してしまうとプレーン引数扱いとなる。フラグのスコープ内に出現したときだけ、"--"がend_of_flag_paramsとなるわけ。

「最初のフラグの前はプレーン引数」という記述もErlnagパートだけなら正しい。が、Cパートはそうなってないようだ。

事実として:

  1. "--"は、直後の引数を強制的にプレーン引数とみなすエスケープのようには働かない。
  2. 原則的に、"--"はフラグパラメータ列を終了させるマーカー(データではない)。
  3. だが、出現位置により "--"自体がプレーン引数データとみなされる。
  4. また、出現位置により "--"がCパートのオプション解析を終了させるマーカー。
  5. -extraは、後続の全ての引数を強制的にプレーン引数にする効果を持つ。

なんか悲惨。

ゲスト



 |