Haskellの厳格なポイントは何ですか?


90

Haskellがデフォルトで怠惰であることを私たちは皆知っています(または知っているはずです)。評価する必要があるまで、何も評価されません。それでは、いつ何かを評価する必要がありますか?Haskellが厳密でなければならない点があります。私はこれらを「厳格なポイント」と呼びますが、この特定の用語は思ったほど広くはありません。私によると:

Haskellでの削減(または評価)は、厳密な点でのみ発生します。

だから問題は:Haskellの厳格なポイントは正確には何ですか?私の直感ではmainseq/ビッグバンパターン、パターンマッチング、およびIOを介しmainて実行されるすべてのアクションが主要な厳密ポイントであると述べていますが、なぜそれを知っているのか本当にわかりません。

(また、これらは「厳密ポイント」と呼ばれていない場合、何をしている彼らが呼ばれます?)

良い答えには、WHNFに関する議論などが含まれると思います。また、ラムダ計算に影響を与えるかもしれないと思います。


編集:この質問に関する追加の考え。

私がこの質問を反映したように、私は厳密さの点の定義に何かを追加するほうがより明確になると思います。厳密なポイントは、さまざまなコンテキストとさまざまな深さ(または厳密さ)を持つことができます。「Haskellでの削減は厳密なポイントでのみ発生する」という私の定義に戻り、その定義にこの句を追加しましょう:「厳密なポイントは、周囲のコンテキストが評価または削減されたときにのみトリガーされます」。

それで、私があなたに私が欲しい種類の答えから始めさせようと思います。main厳格なポイントです。これは、そのコンテキストの主要な厳格なポイントであるプログラムに特別に指定されています。プログラム(mainのコンテキスト)が評価されると、mainの厳密性ポイントがアクティブになります。メインの深さは最大です。完全に評価する必要があります。メインは通常、IOアクションで構成されます。IOアクションは厳密性のポイントでもあり、そのコンテキストはmainです。

seqでは、これらの用語でパターンマッチングについて話し合い、パターンマッチングを試してください。関数適用のニュアンスを説明してください:どのように厳密ですか?いかがですか?どうdeepseqですか?letcaseステートメント?unsafePerformIODebug.Trace?トップレベルの定義?厳密なデータ型?バンパターン?等seqまたはパターンマッチングの観点から説明できるこれらの項目の数は?


10
あなたの直感的なリストはおそらくあまり直交的ではありません。seqとパターンマッチングで十分だと思います。残りはそれらで定義されます。IOたとえば、パターンマッチングは、アクションの脊椎の厳格さを保証すると思います。
カリフォルニア州マッキャン

+組み込みの数値型などのプリミティブも厳密性を強制します。これは、純粋なFFI呼び出しにも同じことが当てはまると思います。
ハマー2011

4
ここでは、2つの概念が混同されているようです。パターンマッチングとseqおよびbangパターンは、式がそのサブ式で厳密になる可能性がある方法です。つまり、最上位の式が評価される場合、サブ式も評価されます。一方、主なIOアクションは、評価の方法です。開始です。これらは異なるものであり、同じリストに含めるのは一種のタイプエラーです。
Chris Smith

@ChrisSmith私はこれら2つの異なるケースを混同しようとはしていません。どちらかと言えば、私は彼らがどのように相互作用するかについてのさらなる明確化を求めています。厳格さはどういうわけか発生し、両方のケースは重要ではありますが、厳密さの「ハプニング」の部分は異なります。(そして@モナディック:ಠ_ಠ)
Dan Burton、

完全な回答を試みずにこの質問の側面について話し合う余地がある場合は、/ r / haskellの投稿にあるこの質問へ
Dan Burton

回答:


46

まず、このペーパーを理解することから始めてください:遅延評価のための自然な意味論(Launchbury)。これにより、GHCのコアに似た小さな言語で式が評価されるタイミングがわかります。次に、残りの質問は、Haskell全体をCoreにマッピングする方法です。その変換のほとんどは、Haskellレポート自体によって行われます。GHCでは、このプロセスを「脱糖」と呼んでいます。

まあ、それはすべての話ではありません。GHCには、デシュガリングとコード生成の間の多数の最適化が含まれており、これらの変換の多くはコアを再配置して、異なるときに評価されるようにします(特に厳密性分析により、評価されることになります)ついさっき)。したがって プログラムがどのように評価されるかを本当に理解するには、GHCによって生成されたコアを調べる必要があります。

おそらく、この答えはあなたには少し抽象的なようです(私は特にbangパターンやseqについて言及していませんでした)が、あなたは何かを求めました 正確な、これは私たちができる最善のことです。


18
GHCが "desugaring"と呼んでいるもので、削除されている構文糖にはHaskell言語自体の実際の構文が含まれていることをいつも私は面白がっていました...偶然にも、HaskellをCoreに翻訳するための非常に手の込んだフロントエンドが含まれているコア言語。:]
CAマッキャン、2011

型システムは正確には一致しませんが、特に、私が覚えているように、型クラスを明示的な辞書に変換することに関してだけではありません。そして、私が理解しているように、すべての最新のTF / GADTは、そのギャップをさらに広げています。
sclv 2009

GCCは、いずれかのCを最適化していない:gcc.gnu.org/onlinedocs/gccint/Passes.html#Passes
ジェルジAndrasek

20

私はおそらくこの質問を次のように書き直します:どのような状況でHaskellは式を評価しますか?(おそらく、「頭が弱い通常の形に」とタックします。)

最初の近似として、これを次のように指定できます。

  • IOアクションを実行すると、「必要」な式が評価されます。(したがって、IOアクションが実行されているかどうかを知る必要があります。たとえば、名前がメインであるか、メインから呼び出されているか、そしてアクションが何を必要としているのかを知る必要があります。)
  • 評価される式(これは再帰的な定義です!)は、必要な式を評価します。

直感的なリストから、メインアクションとIOアクションは最初のカテゴリに分類され、シーケンスとパターンマッチングは2番目のカテゴリに分類されます。しかし、最初のカテゴリは、「厳密さの点」というあなたの考えにもっと一致していると思います。なぜなら、それが実際にHaskellでの評価を引き起こす方法だからです。をユーザーに観察可能な影響とする方法だからです。

Haskellは大きな言語であるため、詳細のすべてを具体的に指定することは大きな作業です。最終的に結果を使用しなくても、Concurrent Haskellは投機的に物事を評価する可能性があるため、これも非常に微妙です。これは、評価を引き起こす3番目の種類の物です。2番目のカテゴリは非常によく研究れています。関連する関数の厳密さを確認したいとします。最初のカテゴリも一種の「厳格さ」と考えることができますが、実際には別物なのでevaluate x、これは少し危険seq x $ return ()です。IOモナドにある種のセマンティクスを与えると(適切にRealWorld#トークンを渡すことが単純なケースで機能する場合)、適切に処理できますが、この種の層別厳密性分析に一般的な名前があるかどうかはわかりません。


17

Cにはシーケンスポイントという概念があります。これは、特定の演算について、一方のオペランドが他方のオペランドより先に評価されることを保証します。私はこれが最も近い既存の概念だと思いますが、本質的に同等の用語であるstrictness point(またはおそらくforce point)は、Haskellの考え方とより一致しています。

実際には、Haskellは純粋に怠惰な言語ではありません。たとえば、パターンマッチングは通常厳格です(したがって、パターンマッチングを試みると、少なくとも、マッチングを受け入れるか拒否するのに十分なほど評価が行われます。

プログラマーは、 seqプリミティブを、結果が使用されるかどうかに関係なく、式に強制的に評価させるます。

$! の観点から定義されています seqます。

レイジーとノンストリクト

あなたの思考はとても!/ $!およびseq右の本質的であるが、パターンマッチングが微妙な規則に従うものとします。~もちろん、常に使用して、遅延パターンマッチングを強制できます。同じ記事からの興味深い点:

また、厳密性アナライザーは、外部式で常にサブ式が必要とされるケースを探し、それらを熱心な評価に変換します。セマンティクス(「ボトム」の観点から)は変更されないため、これを行うことができます。

ウサギの穴を下に進み、GHCによって実行される最適化のドキュメントを見てみましょう。

厳密性分析は、GHCがコンパイル時に、どのデータが必ず「常に必要」になるかを決定するプロセスです。その後、GHCは、計算を保存して後で実行する通常の(オーバーヘッドがより高い)プロセスではなく、そのようなデータを計算するだけのコードを構築できます。

GHC最適化:厳格性分析

言い換えると、データが常に必要な場合(および/または1回しか使用できない場合)にサンクを作成すると不必要にコストがかかるため、最適化として厳格なコードがどこでも生成される可能性があります。

…その値に対してこれ以上評価を行うことはできません。それは正常な形であると言われています。値に対して少なくともいくつかの評価を実行するために中間ステップのいずれかにいる場合、その値は弱頭正規形(WHNF)です。(「ヘッドノーマルフォーム」もありますが、Haskellでは使用されません。)WHNFで何かを完全に評価すると、それはノーマルフォームの何かに減少します…

ウィキブックスHaskell:怠惰

(用語はである頭部正規形無ベータ可約式は、ヘッド位置に存在しない場合は1。それが唯一の非redexesのラムダ抄録が先行する場合はAの可約式は、ヘッド可約式である2。)ですから、サンクを強制的に起動したときに、あなたはWHNFで働いています。強制するサンクがなくなると、通常の状態になります。別の興味深い点:

…ある時点で、たとえばユーザーにzを出力する必要がある場合、それを完全に評価する必要があります…

これは当然のことを意味し、実際に、任意のIOアクションから行っmain てい Haskellプログラムを行うことを考慮すると明らかである力の評価、実際には、物事を行います。で定義されたシーケンスを通過するmain必要があるものはすべて通常の形式である必要があるため、厳密な評価の対象となります。

しかし、CAマッキャンはコメントでそれを正しく理解しました。特別なのmainは、特別なものmainとして定義されていることだけです。IOモナドによって課されたシーケンスを保証するには、コンストラクターでのパターンマッチングで十分です。その点ではseq、パターンマッチングのみが基本です。


4
実際、「ある時点でユーザーにzを出力する必要がある場合、完全に評価する必要があるだろう」という引用は完全に正しくはありません。これは、出力されるShow値のインスタンスと同じくらい厳密です。
nominolo 2011

10

HaskellはAFAIKであり、純粋な怠惰な言語ではなく、厳密ではない言語です。つまり、必ずしも最後の可能な時点で用語を評価するわけではありません。

Haskellの「怠惰」のモデルの良い情報源は、http//en.wikibooks.org/wiki/Haskell/Lazinessにあります

基本的に、サンクとWHNFのウィークヘッダーの標準形式の違いを理解することが重要です。

私の理解では、haskellは命令型言語と比較して計算を逆方向に進めます。これが意味することは、「seq」およびbangパターンがない場合、最終的にサンクの評価を強制するある種の副作用となり、以前の評価を引き起こす可能性があります(真の遅延)。

これはひどいスペースリークにつながるので、コンパイラーは、スペースを節約するために事前にサンクを評価する方法とタイミングを把握します。プログラマーは、厳格な注釈(en.wikibooks.org/wiki/Haskell/Strictness、www.haskell.org / haskellwiki / Performance / Strictness)を与えることでこのプロセスをサポートし、ネストされたサンクの形でスペース使用量をさらに削減できます。

私はhaskellの運用上のセマンティクスの専門家ではないので、リンクをリソースとして残しておきます。

その他のリソース:

http://www.haskell.org/haskellwiki/Performance/Laziness

http://www.haskell.org/haskellwiki/Haskell/Lazy_Evaluation


6

怠惰は何もしないという意味ではありません。プログラムパターンがcase式と一致する場合は常に、何かを評価します-とにかく十分です。それ以外の場合は、使用するRHSを判別できません。コードにケース式がありませんか?心配しないでください。コンパイラーはコードを、使用を避けることが難しいHaskellの簡略化された形式に変換します。

初心者にとって、基本的な経験則letは怠惰であり、怠惰でcaseはありません。


2
caseGHC Coreでは常に評価を強制しますが、通常のHaskellではこれを行わないことに注意してください。たとえば、を試してくださいcase undefined of _ -> 42
ハマー:2011

2
caseGHCコアではWHNFへの引数を評価しcase、Haskell では適切なブランチを選択するために必要なだけ引数評価します。hammarの例では、それはまったくありませんが、case 1:undefined of x:y:z -> 42では、WHNFよりも評価が高くなっています。
最大

そしてまた、 case something of (y,x) -> (x,y)まったく評価する必要はありませんsomething。これは、すべての製品タイプに当てはまります。
Ingo

@Ingo-不正解です。 somethingタプルコンストラクターに到達するには、WHNFに評価される必要があります。
John L

ジョン-なぜ?タプルでなければならないことはわかっているので、それを評価するポイントはどこにあるのでしょうか。xとyがタプルを評価して適切なスロットを抽出するコードにバインドされていれば、それら自体が必要になれば十分です。
インゴ

4

これはカルマを目的とした完全な答えではなく、単なるパズルのピースです。これがセマンティクスに関する限り、同じセマンティクスを提供する複数の評価戦略があることに注意してください。ここでの良い例の1つ-そしてプロジェクトがHaskellのセマンティクスについて通常私たちがどのように考えるかについても話している-は同じセマンティクスを維持しながら評価戦略を根本的に変更したEager Haskellプロジェクトでした:http : //csg.csail.mit.edu/ pubs / haskell.html


2

Glasgow Haskellコンパイラは、コードをcoreと呼ばれるラムダ計算のような言語に変換します。この言語では、case-statementでパターンに一致させると、何かが評価されます。したがって、関数が呼び出された場合、最も外側のコンストラクターとそれのみ(強制フィールドがない場合)が評価されます。それ以外のものはサンクで缶詰にされます。(サンクはletバインディングます)。

もちろん、これは正確には実際の言語で何が起こるかではありません。コンパイラーは非常に洗練された方法でHaskellをCoreに変換し、可能な限り多くのものを遅延させ、常に遅延する必要があるものを作ります。さらに、常に厳密なボックス化されていない値とタプルがあります。

関数を手動で評価する場合、基本的には次のように考えることができます。

  • 戻り値の最も外側のコンストラクタを評価してみてください。
  • 結果を取得するために他に何かが必要な場合(ただし、本当に必要な場合のみ)も評価されます。順序は関係ありません。
  • IOの場合、最初のステートメントから最後のステートメントまでのすべてのステートメントの結果を評価する必要があります。IOモナドは特定の順序で評価を強制するいくつかのトリックを行うため、これは少し複雑です。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.