うーん、いくつかの質問が 1 つに。
再帰の必要性
- 再帰は必須ではありませんが、非常に洗練されたソリューションを提供する場合があります。
- ソリューションが末尾再帰であり、コンパイラが末尾呼び出しの最適化をサポートしている場合、ソリューションは効率的ですらあります。
- すでによく言われているように、Scala には、同じタスクをより表現力豊かに効率的に実行するために使用できる多くのコンビネーター関数があります。
古典的な例の 1 つは、 n 番目の フィボナッチ数を返す関数を作成することです。以下は単純な再帰的実装です。
def fib (n: Long): Long = n match {
case 0 | 1 => n
case _ => fib( n - 2) + fib( n - 1 )
}
さて、これは非効率的です (間違いなく末尾再帰ではありません) が、その構造がフィボナッチ数列にどのように関係しているかは非常に明白です。ただし、適切に末尾再帰にすることはできます。
def fib (n: Long): Long = {
def fibloop(current: Long, next: => Long, iteration: Long): Long = {
if (n == iteration)
current
else
fibloop(next, current + next, iteration + 1)
}
fibloop(0, 1, 0)
}
もっと簡潔に書くこともできたかもしれませんが、これは効率的な再帰的な実装です。とはいえ、最初のものほどきれいではなく、その構造は元の問題とあまり明確に関連していません。
最後に、このサイトの他の場所から恥知らずに盗まれたのは、Luigi Plinge のストリームベースの実装です。
val fibs: Stream[Int] = 0 #:: fibs.scanLeft(1)(_ + _)
非常に簡潔で、効率的で、エレガントで、(ストリームと遅延評価を理解していれば)非常に表現力豊かです。実際、これは再帰的でもあります。#::
再帰関数ですが、遅延評価されたコンテキストで動作します。この種の解決策を考え出すには、確かに再帰的に考えることができなければなりません。
For/While ループと比較した反復
ここでは、 の従来の C スタイルを意味していると思います。
C/C++/Java スタイルの while ループは値を返さず、何かを達成するために副作用を必要とするため (これは C スタイルの forおよび Java スタイルのforeachにも当てはまります)、再帰的なソリューションはしばしばwhileループよりも望ましい場合があります。率直に言って、私はしばしば Scala がwhile を実装していなかったら(または、Scheme の名前付き let のようなもののシンタックス シュガーとして実装していたら) ほしかったと思います。副作用のあるループが発生する状況があります。は、何かを達成するためのより表現力豊かな方法ですが、Javaに固執する開発者は、それを実現するために少し難しくする必要がありました(たとえば、 a for comprehensionを悪用することにより)。
シンプルに、伝統的なwhileとfor は、ぎこちない命令型コーディングを非常に簡単にします。それが気にならないなら、なぜ Scala を使っているのですか?
Stackoverflow の効率性とリスク
テールの最適化により、スタックオーバーフローのリスクが排除されます。再帰的ソリューションを適切に末尾再帰的に書き直すと、非常に醜いものになる可能性があります (特に JVM で実行されている言語では)。
再帰的なソリューションは、命令型のソリューションよりも効率的である場合がありますが、驚くほど効率的です。理由の 1 つは、リストを操作することが多く、ヘッドとテールのアクセスのみが関係することです。リストの先頭と末尾の操作は、より構造化されたコレクションのランダム アクセス操作よりも実際には高速です。
動的計画法
優れた再帰アルゴリズムは通常、複雑な問題をより単純な問題の小さなセットに減らし、解決する問題を 1 つ選択し、残りを別の関数 (通常はそれ自体への再帰呼び出し) に委譲します。さて、私には、これは動的計画法に非常に適しているように思えます。確かに、問題に対して再帰的なアプローチを試みている場合、すべてのケースを解決できないことがわかっている単純なソリューションから始めることがよくあります。失敗した場所を確認し、そのパターンをソリューションに追加して、成功に向けて繰り返します。
Little Schemerには、再帰プログラミングに対するこの反復アプローチの例が多数あります。これは特に、以前のソリューションを後のより複雑なソリューションのサブコンポーネントとして再利用するためです。これは動的計画法アプローチの縮図と言えます。(これは、これまでに作成されたソフトウェアに関する最も優れた教育書の 1 つでもあります)。特にSchemeを同時に教えてくれるので、お勧めできます。あなたが本当にSchemeを学びたくないのなら(なぜ?どうしてしないの?)、それは他のいくつかの言語に適応されています.
If 対 Match
Scala のif式は値を返します (これは非常に便利で、Scala が三項演算子を必要としない理由です)。シンプルを避ける理由はない
if (something)
// do something
else
// do something else
式。単純なif...elseの代わりに突き合わせを行う主な理由は、case ステートメントの力を利用して複雑なオブジェクトから情報を抽出するためです。 これが一例です。
一方、if...else if...else if...elseはひどいパターンです
- すべての可能性を適切にカバーしたかどうかを確認する簡単な方法はありません。
- 式が見つけにくい場合に意図せず入れ子になる
- 無関係な条件を結び付けるのは簡単すぎる (偶然または骨の折れる設計による)
else ifと書かれているところはどこでも、代替案を探してください。 試合は始めるのに適した場所です。