レポート中の質問・感想へのコメント

let と in が多くて、見た感じややこしい。

C言語と変数の扱いがだいぶ異なり、なかなか慣れない。またif文等でCだと if(〜)となっていたのが、括弧をつけないのが通例というのが、ソースを読む 上でなかなか読みづらいです。他に引数チェックなどの処理の際、if文の型の 制約から、エラーを知らせるのを文書で出来ないのは、非常に違和感がありま す。

そうですね,文法が慣れた言語からかなり離れているので違和感を覚える人は 少なくないようです.Objective Caml (多くの関数型言語)では, 複雑な入れ子になった式を書くことがおおく,括弧の使用頻度が多いようです. ので,if などの構文が括弧を必要とすると余計に括弧が増えて見にくくなる, という人もいます.(プログラムを英語の文章っぽく読める,という点では Objective Caml の方が読みやすいかな,という気もします :-) また,不正な引数のチェックがしにくいということですが,もう少しすると, 例外を自分で発生させることができます.(0 での除算のように引数が不正な 場合をチェックして,計算を値を返さずに異常終了させることができます.)

…ループを使わず再帰的呼び出しで処理するのは単純 に考えると効率は悪いが、そのように設計された言語なのだろうか。

再帰呼出しでも効率よく実行する工夫はいくつかされています. 例えば,以下のような最適化(一般に末尾再帰の除去と呼ばれます)を行っています. C言語の関数呼出し f(x,y) は,機械語レベルでは,
  1. 引数 x, y と戻り番地をスタックに push して
  2. f に対応する番地へジャンプ
します.この積まれた引数をフレームといったりします. また return するときには,スタックに積まれたフレームを pop して,呼出し側に戻ります.現在のフレームは,関数呼出し後 の計算のために保存しておく必要があるため,関数呼出しの入れ子段数分 スタックが延びていきます. Objective Caml の実装では,末尾呼出し,つまり,ある関数内で,別の関数(再帰関数 であれば自分自身)を呼んで,その返り値が自分自身の返り値になるような場合 (facti など)には,フレームを保存する必要がないので,新しいフレームを push するかわりに,現在のフレームを新しいフレームで上書きして, 関数本体にジャンプするようなコードを実行します.つまり, 末尾の関数呼出しはスタックが延びません.また, 末尾再帰でない関数呼出しの直後に,いっぺんに戻ることができます. たとえば
let f x = if x then g 1 else 1 + g 2;;
(f true) + 1
のようなものを考えます.then 節の g の呼出しが末尾呼出しです. この場合,(f true) +1 の実行は以下のようになります.
  1. true と戻り番地を push して f のコードにジャンプ
  2. g 1 の呼出しは,末尾呼出しなので, true の入っている場所に 1 を上書きして,戻り番地は push せずに g のコードにジャンプ
  3. g の実行が終了すると,1 と戻り番地を pop して,戻り番地にジャンプ.この時, 戻り番地は,f の呼出し時に push されたもの,で f true の呼出し直後に直接戻ることができる.
また,末尾再帰の形で再帰関数を書くと,実質上ループをまわっているような実行になります.

Objective Caml 入門 テキスト P.50 4.2.3中で
   > fun x -> -. (sqrt x)
   > は,
   > # let f = o sqrt ( -. );;
   > val f : float -> float -> float = <fun>
   > # f 2.0;;
   > - : float -> float = <fun>
という箇所がありますが(授業中に先生はおかしいと言っておられましたが)、 マニュアルを見たところ、負の数を表す正確な記述として "~-." という記号が ありました。これを用いたところ、
   # let f = o (sqrt) (~-.);;
   val f : float -> float = <fun>
   # f 2.0;;
   - : float = -1.41421356237
となったので、恐らく負の数の記号が間違っているのではないかと思います。

その通りです m(__)m 指摘ありがとう.

igarashi@kuis.kyoto-u.ac.jp
last update on $Date: 2002/10/17 05:09:09 $.