Haskell宣言で感嘆符はどういう意味ですか?


262

Haskellを実際のプロジェクトで動かそうと試みたときに、次の定義に出会いました。それぞれの議論の前にある感嘆符が何を意味するのか理解できず、私の本にはそれが記載されていないようです。

data MidiMessage = MidiMessage !Int !MidiMessage

13
これは非常によくある質問だと思います。私は自分自身とまったく同じことをいつのことか疑問に思ったことをはっきりと覚えています。
cjs

回答:


314

それは厳格な宣言です。基本的には、データ構造の値が作成されるときに、「弱い頭の正規形」と呼ばれるものに評価される必要があることを意味します。これが何を意味するのかがわかるように、例を見てみましょう。

data Foo = Foo Int Int !Int !(Maybe Int)

f = Foo (2+2) (3+3) (4+4) (Just (5+5))

上記の関数fは、評価されると「サンク」を返します。つまり、その値を計算するために実行するコードです。その時点では、Fooはまだ存在していません。コードだけです。

しかし、ある時点で、おそらくパターンマッチによって誰かが内部を調べようとする可能性があります。

case f of
     Foo 0 _ _ _ -> "first arg is zero"
     _           -> "first arge is something else"

これにより、必要なことを実行するのに十分なコードが実行されます。したがって、4つのパラメータを持つFooが作成されます(Fooが存在しないと内部を見ることができないため)。1つ目は、テストしているため、すべての方法を評価する必要があります。4、が一致しないことに気づくます。

2番目はテストしないので、評価する必要はありません。したがって、6そのメモリ位置に保存されるのではなく、後で評価できるようにコードを保存します(3+3)。誰かがそれを見る場合にのみ、それは6に変わります。

ただし、3番目のパラメーターの!前にはがあるため、厳密に評価されます。(4+4)実行8され、そのメモリー位置に格納されます。

4番目のパラメーターも厳密に評価されます。ただし、ここで少し注意が必要です。完全に評価するのではなく、弱い通常の頭の形のみを評価しています。我々は、それはだかどうかを把握することをこれが意味NothingJustという何か、と店が、我々はそれ以上に行くん。つまりJust 10、実際に保存するのではなくJust (5+5)に、サンクを評価ます。これは知っておくべき重要なことですが、これによるすべての影響は、この質問の範囲を超えていると思います。

BangPatterns言語拡張機能を有効にすると、同じ方法で関数の引数に注釈を付けることができます。

f x !y = x*y

f (1+1) (2+2)サンクを返し(1+1)*4ます。


16
これは非常に役立ちます。しかし、Haskellの用語が「弱い通常の頭の形」、「厳密な」などの用語で人々が理解しやすいようになっているのかどうか疑問に思わずにはいられません。私があなたを正しく理解しているなら、それは!演算子は、後で評価するために匿名ブロックを格納するのではなく、式の評価された値を格納することを単に意味します。それは合理的な解釈ですか、それとも何か他にありますか?
デビッド

71
@David問題は、値をどこまで評価するかです。弱頭正規形とは、最も外側のコンストラクタに到達するまで評価することです。正規形とは、未評価のコンポーネントがなくなるまで値全体を評価することを意味します。Haskellはあらゆるレベルの評価の深さを許容するため、これを説明するための豊富な用語があります。値渡しのセマンティクスのみをサポートする言語では、これらの違いを見つける傾向はありません。
Don Stewart、

8
@David:私はここでより詳細な説明を書きました:Haskell:Weak Head Normal Formとは何ですか?。強打のパターンや厳密性の注釈については触れませんが、これらはを使用するのと同じseqです。
Hammar

1
私が理解していることを確認するためだけに:その怠惰はコンパイル時の未知の引数にのみ関連していますか?つまり、ソースコードに実際にステートメント(2 + 2)がある場合でも、既知の数値を追加するコードではなく、4に最適化されますか?
Hi-Angel

1
Hi-Angel、それはコンパイラが最適化したいところまでです。強打を使用して、特定のものを弱い通常の頭の形に強制したいと言いますが、「このサンクのものをまだ評価していないことを確認してください」という言い方はありません(必要もありません)さらに一歩。」セマンティックの観点から、サンク "n = 2 + 2"が与えられた場合、nへの特定の参照が現在評価されているのか、以前に評価されたのかさえわかりません。
cjs 2015年

92

厳密なコンストラクタ引数と厳密でないコンストラクタ引数の違いを確認する簡単な方法は、未定義の場合の動作です。与えられた

data Foo = Foo Int !Int

first (Foo x _) = x
second (Foo _ y) = y

非厳密引数はによって評価されないためsecond、渡してundefinedも問題は発生しません。

> second (Foo undefined 1)
1

ただし、undefined値を使用しない場合でも、厳密な引数をにすることはできません。

> first (Foo 1 undefined)
*** Exception: Prelude.undefined

2
これは!、内部実装の詳細を掘り下げるのではなく、シンボルが持つユーザー観察可能な効果を実際に説明するため、Curt Sampsonの回答よりも優れています。
David Grayson

26

厳密な注釈だと思います。

Haskellは純粋で遅延のある関数型言語ですが、遅延のオーバーヘッドが多すぎたり無駄になったりする場合があります。したがって、それに対処するには、サンクを解析する代わりに、関数の引数を完全に評価するようコンパイラーに要求できます。

このページには、パフォーマンス/厳密性に関する詳細情報があります。


うーん、あなたが参照したそのページの例は、!関数を呼び出すとき。しかし、それは型宣言にそれを置くこととは違うように見えます。何が欠けていますか?
デビッド

タイプのインスタンスの作成も式です。型コンストラクタは、指定された引数を指定すると、指定された型の新しいインスタンスを返す関数と考えることができます。
クリスベスト

4
実際、あらゆる目的で、型コンストラクタ関数です。それらを部分的に適用したり、他の関数に渡したりすることができます(たとえば、map Just [1,2,3][Just 1、Just 2、Just 3]を取得するなど)。それらとパターンマッチングを行う機能と、まったく関連のない機能について考えるとわかりやすいでしょう。
cjs
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.