実際のプロジェクトを使ってHaskellを学ぼうとすると、次の定義に出くわしました。それぞれの議論の前にある感嘆符が何を意味するのか理解できず、私の本はそれについて言及していないようでした。
data MidiMessage = MidiMessage !Int !MidiMessage
実際のプロジェクトを使ってHaskellを学ぼうとすると、次の定義に出くわしました。それぞれの議論の前にある感嘆符が何を意味するのか理解できず、私の本はそれについて言及していないようでした。
data MidiMessage = MidiMessage !Int !MidiMessage
厳格宣言です。基本的には、データ構造の値を作成する際に、いわゆる「弱い頭の正規形」に評価する必要があるということです。これが何を意味するのかを理解できるように、例を見てみましょう。
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 が作成されます (存在しないと中を見ることができないため)。1 つ目は、テストしているので、4
一致しないことがわかる まで評価する必要があります。
2 番目はテストしていないため、評価する必要はありません。したがって、そのメモリ ロケーションに保存するのではなく、6
後で評価できるようにコードを保存します(3+3)
。誰かがそれを見た場合にのみ、それは 6 になります。
ただし、3 番目のパラメーターは!
前に があるため、厳密に評価されます。つまり(4+4)
、実行8
され、そのメモリ位置に格納されます。
4 番目のパラメーターも厳密に評価されます。しかし、ここで少しトリッキーになります。完全に評価するのではなく、弱い通常の頭の形だけを評価します。これは、それが何かであるかどうかを判断し、それを保存することを意味しますNothing
がJust
、それ以上は行いません. つまりJust 10
、実際には ではなく を格納Just (5+5)
し、内部のサンクは評価せずに残します。これを知ることは重要ですが、これが意味することはすべて、この質問の範囲を超えていると思います.
BangPatterns
言語拡張機能を有効にすると、同じ方法で関数の引数に注釈を付けることができます。
f x !y = x*y
f (1+1) (2+2)
thunk を返し(1+1)*4
ます。
厳密なコンストラクター引数と厳密でないコンストラクター引数の違いを確認する簡単な方法は、未定義の場合の動作です。与えられた
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
厳密さの注釈だと思います。
Haskellは純粋で怠惰な関数型言語ですが、怠惰のオーバーヘッドが多すぎたり無駄になったりすることがあります。したがって、これに対処するために、サンクを解析する代わりに、関数への引数を完全に評価するようにコンパイラーに要求できます。
このページには、パフォーマンス/厳密性に関する詳細情報があります。