GHCはびっくりパターンと呼ばれるパターン照合の拡張をサポートしている。これは!
と書く。びっくりパターンはHaskell Primeへの採用が検討されている。Haskell primeの特徴まとめには、以下にあるのよりも多くの議論と例が載っている。
pat
鍵となる変更は、Haskell98レポート中のパターン照合の意味論(和訳)に規則を一つ追加するというものである。第十項目として次を付け加える。「パターン!pat
の値v
への照合は、次のように振る舞う」
もしv
がボトムなら、照合は発散する
そうでなければ、pat
がv
へと照合される
びっくりパターンはフラグ-XBangPatterns
で有効になる。
この拡張の要点は、パターンの構文に一つの新しい生成規則を追加することである。
pat ::= !pat
式e
をパターン!p
に対して照合するとき、まずe
が(WHNFまで)評価され、その結果がp
に対して照合される。例を挙げる。
f1 !x = True
この定義では、f1
がx
について正格になっている。びっくりパターンがなければ遅延であったところだ。もちろん、びっくりパターンは入れ子にすることができる。
f2 (!x, y) = [x,y]
ここで、f2
はx
について正格だが、y
については正格でない。びっくりパターンが実質的に効果を持つのは、変数パターンやワイルドカードパターンの前に置かれたときだけである。
f3 !(x,y) = [x,y] f4 (x,y) = [x,y]
ここでは、f3
とf4
は等しい。もともと評価を強制するパターンの前にびっくりマークを置いても何も変わらない。
この、「びっくりマークは変数かワイルドカードの前に置かれたときのみ違いがある」という一般的な法則には、(見掛け上の)例外が一つある。let
またはwhere
束縛の最上位におけるびっくりマークは、パターンに関係なく、その束縛を正格にする。(「見掛け上の」例外と言ったのは、束縛の最上位にあるびっくりマークはパターンの一部ではないと考えるのが正しいからである。そうではなく、これは束縛の構文の一部であり、「びっくりパターン束縛」を作る。)
例を示す。
let ![x,y] = e in b
これはびっくりパターン束縛である。操作的には、これは次のcase式とまったく同様に振る舞う。
case e of [x,y] -> b
case式と同様に、びっくりパターン束縛は非再帰的かつ単相的でなければならない。一方、パターン束縛中に入れ子になったびっくりパターンは、他のあらゆる形式のパターンと同様に振る舞う。例を挙げる。
let (!x,[y]) = e in b
これは以下と同等である。
let { t = case e of (x,[y]) -> x `seq` (x,y) x = fst t y = snd t } in b
この束縛は遅延するが、x
かy
のいずれかがb
によって評価されると、パターン全体が照合され、それに伴ってx
の評価が強制される。
もちろん、びっくりパターンはcase
式でも使える。
g5 x = let y = f x in body g6 x = case f x of { y -> body } g7 x = case f x of { !y -> body }
関数g5
とg6
は全く同じである。一方、g7
では(f x)
が評価され、その結果がy
に束縛された後、body
が評価される。
パターンの構文に一つの新しい生成規則を加える。
pat ::= !pat
これには一つ構文的な曖昧さの問題がある。以下の例を考えよ。
f !x = 3
これは中置関数「(!)
」の定義なのだろうか。それともびっくりパターンを使った「f
」の定義なのだろうか。GHCは、この曖昧さを、後者を優先することで解決する。びっくりパターンが有効な状態で(!)
を定義したいなら、前置記法を使わなければならない。
(!) f x = 3
Haskellのパターン照合の意味論はHaskellレポートの3.17.2節(和訳)に書かれている。この記述に、第十項目として以下のものを付け加える。
パターン!pat
の値v
への照合は、次のように振る舞う:
もし、v
がボトムなら、照合は発散する。
そうでなければ、pat
はv
と照合する。
同様に、3.17.3節(和訳)の図4に、新しい場合(t)を追加する。
case v of { !pat -> e; _ -> e' } = v `seq` case v of { pat -> e; _ -> e' }
残りはlet式である。これの変換はHaskellレポートの3.12節(和訳)で与えられている。そこにある変換の箱において、まず以下の変換を施す。!qi = ei
という形をした全てのパターンpi
について、これを(xi,!qi) = ((),ei)
に変換し、e0
を(xi `seq` e0)
で置き換える。次に、左辺のパターンがどれも先頭にびっくりマークを持たないようになったら、今ある箱の中にある規則を適用する。
このlet規則の効果は、本体の評価が始まる前に、パターンqi
の照合を完全に終わらせることである。以下のように、qi
が変数である場合に備えて、びっくりマークは変換後の形においても保持される。
let !y = f x in b
このようなlet束縛は再帰的になり得る。しかし、非再帰的であることの方がずっと多く、その場合以下の法則が成り立つ。
(let !p = rhs in body)
は、(case rhs of !p -> body)
と同等である。
びっくりマークが最も外側にあるようなパターンは、モジュールの最上位では許されない。