怠justな評価がどのように機能するかを学んだところ、私は疑問に思っていました:なぜ怠producedな評価が現在生産されているすべてのソフトウェアに適用されないのですか?まだ熱心な評価を使用しているのはなぜですか?
怠justな評価がどのように機能するかを学んだところ、私は疑問に思っていました:なぜ怠producedな評価が現在生産されているすべてのソフトウェアに適用されないのですか?まだ熱心な評価を使用しているのはなぜですか?
回答:
遅延評価では、簿記のオーバーヘッドが必要です。評価済みかどうかなどを知る必要があります。熱心な評価は常に評価されるので、知る必要はありません。これは特に同時コンテキストで当てはまります。
次に、必要に応じて、後で呼び出すために関数オブジェクトにパッケージ化することにより、熱心な評価を遅延評価に変換するのは簡単です。
第三に、遅延評価は制御の喪失を意味します。ディスクからファイルの読み取りを遅延評価した場合はどうなりますか?または時間を取得しますか?それは受け入れられません。
熱心な評価はより効率的で制御しやすく、簡単に遅延評価に変換されます。遅延評価が必要なのはなぜですか?
readFile
がまさに必要なものです。また、遅延評価から熱心な評価への変換も簡単です。
head [1 ..]
Haskellではそれが与えるので、熱心に評価された純粋な言語であなたに何を与え1
ますか?
怠zyなコードと状態がうまく混ざり合い、バグを見つけるのが難しいためです。依存オブジェクトの状態が変化すると、評価時に遅延オブジェクトの値が間違っている可能性があります。状況が適切だとわかったときにプログラマーがオブジェクトを怠laになるように明示的にコーディングする方がはるかに優れています。
サイドノートでは、Haskellはすべてに対して遅延評価を使用します。これは関数型言語であり、状態を使用しないため可能です(明確にマークされているいくつかの例外的な状況を除く)
set!
は、怠SchemeなSchemeインタープリターでの使用についてだったと思います。> :(
遅延評価は常に良いとは限りません。
遅延評価のパフォーマンス上の利点は大きい可能性がありますが、熱心な環境でほとんどの不必要な評価を避けるのは難しくありません-遅延は確かに簡単で完全になりますが、コードで不必要な評価が大きな問題になることはめったにありません。
遅延評価の良いところは、より明確なコードを書くことができるときです。無限の自然数リストをフィルタリングし、そのリストの10番目の要素を取得して10番目の素数を取得することは、最も簡潔で明確な処理方法の1つです(疑似コード)。
let numbers = [1,2...]
fun is_prime x = none (map (y-> x mod y == 0) [2..x-1])
let primes = filter is_prime numbers
let tenth_prime = first (take primes 10)
怠zyなくして簡潔に表現するのは非常に難しいと思います。
しかし、怠zyはすべてに対する答えではありません。手始めに、状態の存在下でレイジーネスを透過的に適用することはできません。また、ステートフルネスを自動的に検出することはできません(たとえば、Haskellで作業している場合を除き、状態は非常に明確です)。そのため、ほとんどの言語では、遅延を手動で行う必要があります。これにより、物事がわかりにくくなり、遅延評価の大きな利点の1つがなくなります。
さらに、遅延評価には、評価されていない式を保持するという大きなオーバーヘッドが発生するため、パフォーマンス上の欠点があります。ストレージを使い果たし、単純な値よりも動作が遅くなります。怠zyなバージョンは犬のように遅いため、コードを熱心に確認する必要があることを知ることは珍しくありません。
それが起こる傾向があるので、絶対的な最高の戦略はありません。Lazyは、無限のデータ構造または他の使用可能な戦略を利用してより良いコードを記述できれば素晴らしいですが、熱心に最適化する方が簡単です。
以下は、熱心な評価と怠evaluationな評価の長所と短所の簡単な比較です。
熱心な評価:
ものを不必要に評価する潜在的なオーバーヘッド。
妨げられない、迅速な評価。
遅延評価:
不要な評価はありません。
値を使用するたびの簿記のオーバーヘッド。
したがって、評価する必要のない式が多数ある場合は、レイジーの方が優れています。それでも、評価する必要のない式がない場合、遅延は純粋なオーバーヘッドです。
それでは、実際のソフトウェアを見てみましょう。あなたが書いた関数のうち、すべての引数の評価を必要としない関数はいくつありますか?特に、1つのことだけを行う最新の短い関数では、このカテゴリに分類される関数の割合は非常に低くなっています。したがって、遅延評価では、ほとんどの場合、簿記のオーバーヘッドが発生するだけで、実際に何かを保存する機会はありません。
その結果、遅延評価は単に平均的には支払われません。熱心な評価は最新のコードにより適しています。
@DeadMGが指摘したように、遅延評価にはブックキーピングのオーバーヘッドが必要です。これは、熱心な評価に比べて費用がかかる場合があります。次のステートメントを検討してください。
i = (243 * 414 + 6562 / 435.0 ) ^ 0.5 ** 3
これには、計算に少し時間がかかります。遅延評価を使用する場合は、使用するたびに評価されているかどうかを確認する必要があります。これが頻繁に使用されるタイトループ内にある場合、オーバーヘッドは大幅に増加しますが、メリットはありません。
熱心な評価と適切なコンパイラを使用すると、式はコンパイル時に計算されます。ほとんどのオプティマイザーは、必要に応じて、発生するループから割り当てを移動します。
遅延評価は、アクセス頻度が低く、取得するオーバーヘッドが大きいデータの読み込みに最適です。したがって、コア機能よりもエッジケースに適しています。
一般的に、できるだけ頻繁にアクセスされるものを評価することをお勧めします。遅延評価はこの方法では機能しません。常に何かにアクセスする場合、遅延評価はオーバーヘッドを追加するだけです。遅延評価を使用するコスト/利点は、アクセスされるアイテムがアクセスされる可能性が低くなるにつれて減少します。
常に遅延評価を使用することは、早期の最適化も意味します。これは悪い慣行であり、コードが非常に複雑で高価になります。残念ながら、時期尚早の最適化により、コードの実行が単純なコードよりも遅くなることがよくあります。最適化の効果を測定できるようになるまで、コードを最適化することはお勧めできません。
時期尚早な最適化を回避することは、優れたコーディング慣行と競合しません。適切なプラクティスが適用されなかった場合、最初の最適化は、計算をループ外に移動するなどの適切なコーディングプラクティスを適用することで構成されます。
式の値を決定するために式を完全に評価する必要がある場合、遅延評価は不利になる可能性があります。ブール値の長いリストがあり、それらのすべてが真であるかどうかを調べたいとします。
[True, True, True, ... False]
これを行うには、リスト内のすべての要素を調べる必要があります。そのため、評価を遅らせる可能性はありません。フォールドを使用して、リスト内のすべてのブール値がtrueであるかどうかを判断できます。遅延評価を使用するフォールドライトを使用する場合、リスト内のすべての要素を確認する必要があるため、遅延評価の利点は得られません。
foldr (&&) True [True, True, True, ... False]
> 0.27 secs
この場合、右折は厳密な左折よりもはるかに遅くなり、遅延評価を使用しません。
foldl' (&&) True [True, True, True, ... False]
> 0.09 secs
その理由は、厳密なフォールドが末尾再帰を使用するためです。これは、戻り値を蓄積し、大量の操作チェーンを構築してメモリに格納しないことを意味します。どちらの関数もリスト全体をとにかく見る必要があり、フォールドライトは末尾再帰を使用できないため、これはレイジーフォールドライトよりもはるかに高速です。だから、ポイントは、手元のタスクに最適なものを使用する必要があるということです。