19

専門家によって書かれた Scala のドキュメントを読むと、後者の方が簡潔で明確であっても、while ループよりも末尾再帰の方が優れているという印象を受けることがあります。これは一例です

object Helpers {
    implicit class IntWithTimes(val pip:Int) {
            // Recursive
        def times(f: => Unit):Unit = {
            @tailrec
            def loop(counter:Int):Unit = {
                if (counter >0) { f; loop(counter-1) }
            }
            loop(pip)
        }

            // Explicit loop
        def :@(f: => Unit) = {
            var lc = pip
            while (lc > 0) { f; lc -= 1 }
        }   
    }
}   

(はっきりさせておきますが、専門家はループについてはまったく言及していませんでしたが、この例では、まるで本能によるかのように、この方法でループを記述することを選択しました。これが私に疑問を投げかけたものです: 私は同様の本能を発達させるべきでしょうか.. )

while ループの唯一の改善点は、反復変数がループの本体に対してローカルであることと、変数の変更が固定された場所にあることですが、Scala はその構文を提供しないことを選択しています。

明確さは主観的なものですが、問題は (末尾の) 再帰スタイルによってパフォーマンスが向上するかどうかです。

4

3 に答える 3

19

JVM の制限により、すべての潜在的な末尾再帰関数が Scala コンパイラによって最適化されるわけではないので、パフォーマンスに関する質問に対する短い (そして時には間違った) 答えはノーです。

より一般的な質問(利点がある)に対する長い答えは、もう少し不自然です。whileを使用すると、実際には次のようになることに注意してください。

  1. カウンターを保持する新しい変数を作成します。
  2. その変数を変更します。

オフバイワンのエラーと可変性の危険により、長期的には、whileパターンでバグを導入することが保証されます。実際、times関数は次のように簡単に実装できます。

def times(f: => Unit) = (1 to pip) foreach f

これは、よりシンプルで小さいだけでなく、一時的な変数と可変性の作成を回避します。実際、呼び出している関数の型が結果に関係するものである場合、while構造はさらに読みにくくなります。のみを使用して次の実装を試みてくださいwhiles:

def replicate(l: List[Int])(times: Int) = l.flatMap(x => List.fill(times)(x))

次に、同じことを行う末尾再帰関数の定義に進みます。


アップデート:

私はあなたが言っているのを聞きます:「ねえ!それは不正行為です!それは電話でも呼び出しforeachでもありません」. まあ、本当に?forの Scala の定義を見てみましょう。whiletail-recforeachLists

  def foreach[B](f: A => B) {
    var these = this
    while (!these.isEmpty) {
      f(these.head)
      these = these.tail
    }
  }

Scala での再帰について詳しく知りたい場合は、このブログ投稿をご覧ください。関数型プログラミングに慣れたら、夢中になって Rúnar のブログ投稿を読んでください。さらに詳しい情報はこちらこちら

于 2013-09-07T15:31:40.870 に答える
6

一般に、直接末尾再帰関数 (つまり、常に自分自身を直接呼び出し、オーバーライドできない関数) は、常にwhileコンパイラによってループに最適化されます。注釈を使用し@tailrecて、コンパイラが特定の関数に対してこれを実行できることを確認できます。

原則として、末尾再帰関数は (通常はコンパイラによって自動的に)whileループとして書き換えられ、その逆も可能です。

(末尾) 再帰スタイルで関数を記述する目的は、パフォーマンスや簡潔さを最大化することではなく、コードの意図をできるだけ明確にすると同時に、バグが発生する可能性を最小限に抑えることです (可変変数を排除することにより、一般的には関数の「入力」と「出力」が何であるかを追跡するのが難しくなります)。適切に作成された再帰関数は、終了条件のいずれも満たされない場合に行われる再帰呼び出し (末尾再帰でない場合にのみ複数形) を伴う終了条件 (カスケードまたはパターン マッチのいずれかを使用if)の一連のチェックで構成されます。else

再帰を使用する利点は、いくつかの異なる終了条件が考えられる場合に最も劇的になります。一般に、一連の条件またはパターンは、(潜在的に複雑で相互に関連する) ブール式をまとめifた単一の条件よりもはるかに理解しやすいです。特に、終了条件によって戻り値を変える必要がある場合会った。while&&

于 2013-09-10T02:19:30.013 に答える