遅延評価の概念が役立つのはなぜですか?


30

思わ遅延評価式のが彼らのコードが実行される順序を失うコントロールにプログラマを引き起こす可能性があります。これがプログラマーに受け入れられる、または望まれる理由を理解するのに苦労しています。

式がいつどこで評価されるかが保証されていない場合、このパラダイムを使用して、意図したとおりに動作する予測可能なソフトウェアを構築するにはどうすればよいですか?


10
ほとんどの場合、それは重要ではありません。他のすべての場合は、厳密さを強制できます。
キャットプラスプラス

22
haskellのような純粋に機能的な言語のポイントは、副作用がないので、コードを実行するときに気にする必要がないということです。
ビットマスク

21
「コードの実行」について考えるのをやめ、「結果の計算」について考え始める必要があります。これは、最も興味深い問題で本当に必要なことです。もちろん、プログラムは通常、何らかの方法で環境とやり取りする必要がありますが、多くの場合、コードのごく一部に減らすことができます。残りの部分については、純粋に機能的な作業ができ、遅延があると推論がずっと簡単になります。
左辺約

6
タイトルの質問(「遅延評価を使用する理由」)は、本文の質問(「遅延評価の使用方法」)とは大きく異なります。前者については、この関連する質問に対する私の回答を参照しください。
ダニエルワグナー

1
怠inessが役立つ場合の例:Haskell head . sortではO(n)、怠lazのために複雑ではありません(ではありませんO(n log n))。遅延評価と時間計算量を参照してください。
ペトルプドラク

回答:


62

答えの多くは、無限のリストや計算の未評価の部分からのパフォーマンス向上などに関係していますが、これには怠largerの大きな動機であるモジュール性が欠けています。

古典的な議論は、John Hughesの引用された論文「Why Functional Programming Matters」(PDFリンク)に記載されています。その論文の重要な例(セクション5)は、alpha-beta検索アルゴリズムを使用してTic-Tac-Toeを再生しています。重要な点は(p。9):

[遅延評価]を使用すると、プログラムを、多数の可能な回答を作成するジェネレーター、および適切なものを選択するセレクターとしてモジュール化することが実用的になります。

Tic-Tac-Toeプログラムは、特定の位置から始まるゲームツリー全体を生成する関数と、それを使用する別の関数として作成できます。実行時に、これは本質的にゲームツリー全体を生成するのではなく、消費者が実際に必要とするサブパートのみを生成します。消費者を変更することで、代替品が生産される順序と組み合わせを変更できます。ジェネレータを変更する必要はまったくありません。

熱心な言語では、おそらくツリーを生成するのに時間とメモリを費やしすぎるため、このように書くことはできません。したがって、次のいずれかになります。

  1. 生成と消費を同じ機能に結合します。
  2. 特定の消費者に対してのみ最適に動作するプロデューサーを作成します。
  3. 独自バージョンの遅延を実装します。

詳細または例をご覧ください。これは興味深いですね。
アレックスナイ

1
@AlexNye:John Hughesの論文には詳細があります。学術論文であるにもかかわらず、恐らく恐ろしいことですが、実際には非常にアクセス可能で読みやすいものです。その長さでなければ、おそらくここに答えとして収まるでしょう!
ティコンジェルビス

おそらくこの答えを理解するために、ヒューズの論文を読む必要があります...まだ読んでいないのに、怠とモジュール性がどのように、なぜ関連しているのか、まだわかりません。
stakx 14年

@stakxより良い説明がなければ、偶然を除いてそれらは関連しているようには見えません。この例の遅延の利点は、レイジージェネレーターがゲームのすべての可能な状態を生成できることですが、発生するもののみが消費されるため、時間/メモリを無駄にしないということです。ジェネレーターは、レイジージェネレーターにならずにコンシューマーから分離でき、コンシューマーから分離せずにレイジーにすることが(より困難ですが)可能です。
イズカタ14年

@Iztaka:「ジェネレーターは、レイジージェネレーターにならずにコンシューマーから分離できます。コンシューマーから分離せずにレイジーにすることは(より困難ですが)可能です。」このルートに行くと、**過度に特殊化されたジェネレーター**になることがあります。ジェネレーターは1つのコンシューマーを最適化するために作成され、他のコンシューマーに再利用されると最適ではないことに注意してください。一般的な例は、ルートオブジェクトから1つの文字列が必要なためにオブジェクトグラフ全体を取得して構築するオブジェクトリレーショナルマッパーです。怠azineはそのようなケースの多くを回避します(すべてではありません)。
sacundim 14年

32

式がいつどこで評価されるかが保証されていない場合、このパラダイムを使用して、意図したとおりに動作する予測可能なソフトウェアを構築するにはどうすればよいですか?

式に副作用がない場合、式が評価される順序は値に影響しないため、プログラムの動作は順序の影響を受けません。したがって、動作は完全に予測可能です。

現在、副作用は別の問題です。副作用が任意の順序で発生する可能性がある場合、プログラムの動作は実際には予測できません。しかし、実際にはそうではありません。Haskellなどの遅延言語は、参照を透過的にすることを重要視しています。つまり、式が評価される順序が結果に影響を与えないようにします。Haskellでは、これはユーザーに見える副作用を持つすべての操作をIOモナド内で強制的に実行することで実現されます。これにより、すべての副作用が期待どおりの順序で発生するようになります。


15
これが、Haskellのような「純粋さを強制」した言語のみがデフォルトでどこでも怠inessをサポートする理由です。Scalaのような「奨励された純粋さ」言語では、プログラマーが怠wantが必要な場所を明示的に言う必要があります。デフォルト怠であり、追跡されていない副作用がある言語は、実際に予測可能にプログラミングするのは難しいでしょう。
ベン

1
確かにIO以外のモナドも副作用を引き起こす可能性があります
jk。

1
@jk IOのみが外部副作用を引き起こす可能性があります。
dave4420

@ dave4420はい、しかし、それはこの答えの言うことではありません
jk。

1
@jk Haskellでは、実際にはありません。IO(またはIOに基づいたもの)以外のモナドには副作用がありません。これは、コンパイラがIOを異なる方法で処理するためです。IOは「Immutable Off」と見なされます。モナドは、特定の実行順序を保証する(巧妙な)方法です(したがって、ユーザーが「はい」と入力した後にのみファイルが削除されます)。
スカーフリッジ

22

データベースに精通している場合、非常に頻繁にデータを処理する方法は次のとおりです。

  • のような質問をする select * from foobar
  • より多くのデータがありますが、実行:結果の次の行を取得して処理します

結果の生成方法と方法(インデックス?全テーブルスキャン?)、またはいつ(要求時にすべてのデータを一度に、または増分的に生成するか)を制御しません。あなたが知っているすべては、次のとおりです。場合は、より多くのデータがあり、あなたがそれを求めるとき、あなたはそれを取得します。

遅延評価は同じものにかなり近いです。ieとして定義された無限リストがあるとします。フィボナッチ数列-5つの数値が必要な場合、5つの数値が計算されます。1000が必要な場合、1000を取得します。トリックは、ランタイムが何をいつどこで提供するかを知っていることです。とても便利です。

(Javaプログラマーは、イテレーターを使用してこの動作をエミュレートできます-他の言語にも同様のものがあります)


いい視点ね。たとえば、Collection2.filter()(そのクラスの他のメソッドと同様に)遅延評価を実装しています:結果は通常のように見えますCollectionが、実行順序は直感的でない(または少なくとも非自明)場合があります。また、yieldPython(およびC#の類似の機能)があり、通常のイテレーターよりも遅延評価のサポートにさらに近いものです。
ヨアヒムザウアー

C#の収量見返りに、またはコースの@JoachimSauerあなたは怠惰であるの約半分を、事前に作成LINQのopreratorsを使用することができます
JK。

+1:命令型/オブジェクト指向言語でイテレータに言及するため。Javaでストリームとストリーム関数を実装するために、同様のソリューションを使用しました。イテレータを使用すると、長さが不明な入力ストリームでtake(n)、dropWhile()などの関数を使用できます。
ジョルジオ

13

名前が「Ab」で始まり、20年以上前の最初の2000人のユーザーのリストをデータベースに問い合わせることを検討してください。また、彼らは男性でなければなりません。

これが小さな図です。

You                                            Program Processor
------------------------------------------------------------------------------
Get the first 2000 users ---------->---------- OK!
                         --------------------- So I'll go get those records...
WAIT! Also, they have to ---------->---------- Gotcha!
start with "Ab"
                         --------------------- NOW I'll get them...
WAIT! Make sure they're  ---------->---------- Good idea Boss!
over 20!
                         --------------------- Let's go then...
And one more thing! Make ---------->---------- Anything else? Ugh!
sure they're male!

No that is all. :(       ---------->---------- FINE! Getting records!

                         --------------------- Here you go. 
Thanks Postgres, you're  ---------->----------  ...
my only friend.

このひどい相互作用でわかるように、「データベース」は、すべての条件を処理する準備ができるまで、実際には何もしていません。各ステップで結果を遅延ロードし、毎回新しい条件を適用します。

最初の2000人のユーザーを取得するのではなく、それらを返し、「Ab」でフィルタリングし、それらを返し、20を超えてフィルタリングし、男性をフィルタリングし、最終的にそれらを返します。

簡単に言えば、遅延読み込み。


1
これは本当にお粗末な説明です。残念ながら、この特定のSEサイトには、反対票を投じるほど十分な担当者がいません。遅延評価の本当のポイントは、つまりどれも何か他のものは、それらを消費する準備ができるまで、これらの結果のは、実際に作製されていません。
アルニタック

私が投稿した答えは、あなたのコメントとまったく同じことを言っています。
sergserg

それは非常に丁寧なプログラムプロセッサです。
ジュリアン

9

式の遅延評価により、特定のコードの設計者は、コードが実行されるシーケンスを制御できなくなります。

結果が同じであれば、設計者は式が評価される順序を気にするべきではありません。評価を延期することで、一部の式の評価を完全に回避することができ、時間を節約できます。

下位レベルで同じアイデアを仕事で見ることができます。多くのマイクロプロセッサは命令を順不同で実行できるため、さまざまな実行ユニットをより効率的に使用できます。重要なのは、命令間の依存関係を調べて、結果が変わる場所の順序を変更しないことです。


5

遅延評価には説得力があると思ういくつかの議論があります

  1. モジュール性遅延評価を使用すると、コードを部分に分割できます。たとえば、「リストリスト内の要素の最初の10個の逆数を見つけて、その逆数が1未満になる」という問題があるとします。Haskellのようなもので書くことができます

    take 10 . filter (<1) . map (1/)
    

    しかし、これは厳密な言語では正しくあり[2,3,4,5,6,7,8,9,10,11,12,0]ません。指定するとゼロで除​​算されるためです。これが実際に素晴らしい理由については、sacundimの回答を参照してください

  2. より多くのものが機能します厳密に(しゃれた)厳密な評価よりも厳密でない評価で終了するプログラムが多くなります。プログラムが「熱心な」評価戦略で終了する場合、「怠lazな」評価戦略で終了しますが、正反対ではありません。この現象の具体例としては、無限のデータ構造(実際にはちょっとクールなもの)のようなものがあります。遅延言語で動作するプログラムが増えています。

  3. 最適性 Call-by- Need評価は、時間に関して漸近的に最適です。主要な遅延言語(基本的にHaskellとHaskellである)はcall-by-needを約束しませんが、多かれ少なかれ最適なコストモデルを期待できます。厳密性アナライザー(および投機的評価)は、実際にオーバーヘッドを抑えます。スペースはより複雑な問題です。

  4. zyな評価を使用したForces Purityは、あなたがそれを置くとプログラマがコントロールを失うため、副作用を無秩序な方法で処理することは完全な苦痛になります。これは良いことです。参照の透明性により、プログラムのプログラミング、屈折、推論が非常に簡単になります。厳密な言語は、不純なビットを持つというプレッシャーに必然的に陥ります。HaskellとCleanが美しく抵抗しました。これは、副作用が常に悪であると言うことではありませんが、副作用を制御することは非常に有用なので、この理由だけで怠aloneな言語を使用するのに十分です。


2

多数の高価な計算が提供されているが、実際に必要な計算や順序がわからない場合を考えます。複雑なmother-may-iプロトコルを追加して、消費者に利用可能なものを見つけさせ、まだ実行されていない計算をトリガーさせることができます。または、計算がすべて行われたかのように機能するインターフェイスを提供することもできます。

また、無限の結果があるとします。たとえば、すべての素数のセット。事前に集合を計算できないことは明らかなので、素数の領域での操作はすべて怠zyでなければなりません。


1

遅延評価を使用すると、コード実行に関する制御を失うことはありませんが、依然として完全に決定論的です。しかし、それに慣れるのは難しいです。

遅延評価は、熱心な評価が失敗する場合もあるが、その逆ではない場合に終了するラムダ項の削減方法であるため便利です。これには、1)実際に計算を実行する前に計算結果にリンクする必要がある場合、たとえば、循環グラフ構造を構築するが、機能スタイルでそれを行いたい場合2)無限のデータ構造を定義するが、この構造フィードを機能させる場合データ構造の一部のみを使用する。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.