14

ここにこのコードがあるとしましょう:

do_recv_loop(State) ->
    receive
    {do,Stuff} ->
        case Stuff of
        one_thing -> 
            do_one_thing(),
            do_recv_loop(State);
        another_thing ->
            do_another_thing(),
            do_recv_loop(State);
        _ ->
            im_dead_now
        end
    {die} -> im_dead_now;
    _ -> do_recv_loop(State)
    end.

さて、理論的には、これは末尾再帰です。do_recv_loopへの3つの呼び出しのいずれも、何も返す必要がないためです。しかし、erlangはこれが末尾再帰であることを認識し、適切に最適化しますか?ネストされた構造で認識できなくなるのではないかと心配です。

4

4 に答える 4

17

はい、そうなります。末尾呼び出しを最適化するにはErlangが必要ですが、関数が呼び出された後は何も起こらないため、これは明らかに末尾呼び出しです。

tailcall以前は、コンパイラが無効な使用について警告できるように、Erlangにキーワードがあればいいのにと思っていましたが、その後はそれに慣れました。

于 2011-03-24T21:54:37.217 に答える
2

再帰関数がコンパイラによって最適化されているかどうかをどのように知るかについて質問しているので、これは適切だと思います。あなたはlists:reverse / 1を使用していないので、以下は当てはまらないかもしれませんが、まったく同じ質問をしているが異なるコード例を持っている他の誰かにとっては非常に関連があるかもしれません。

Erlang効率ガイドのErlangパフォーマンスの8つの神話から

R12B以降のリリースでは、多くの場合、body-recursive呼び出しでスタックで使用される単語の数を減らす最適化があり、body-recursivelist関数とlists:reverse/1を呼び出す末尾再帰関数が使用されます。最後に、まったく同じ量のメモリを使用します。

http://www.erlang.org/doc/efficiency_guide/myths.html#id58884

持ち帰りのメッセージは、場合によっては何が最善かを確認するために測定する必要があるかもしれないということだと思います。

于 2011-03-24T23:02:58.253 に答える
2

はい、末尾再帰です。注意すべき主な落とし穴は、例外に巻き込まれているかどうかです。その場合、例外がスタック上に存在する必要がある場合があります。これにより、末尾再帰に見えるものが、一見そうではないものになります。

末尾呼び出しの最適化は、呼び出しが末尾位置にある場合に適用できます。テール位置は「関数が戻る前の最後のもの」です。で注意してください

fact(0) -> 1;
fact(N) -> N * fact(N-1).

が計算された後、継続を実行する必要があるため(つまり、を掛ける)、ファクトへの再帰呼び出しはテール位置にありませんfact(N-1)N * _N

于 2011-03-25T00:36:27.520 に答える
-1

私はErlangにかなり慣れていませんが、私が集めたものから、末尾再帰であるためには、関数は任意の論理ブランチで2つのことのいずれかを実行する必要があるというルールのようです。

  • 再帰的な呼び出しを行わない
  • 再帰呼び出しの値を返し、その後は何もしません

その再帰呼び出しは、その後何も起こらない限り、必要な数の、、、または呼び出しifcaseネストできます。receive

于 2011-03-24T22:36:05.937 に答える