びっくりパターン

GHCはびっくりパターンと呼ばれるパターン照合の拡張をサポートしている。これは!patと書く。びっくりパターンはHaskell Primeへの採用が検討されている。Haskell primeの特徴まとめには、以下にあるのよりも多くの議論と例が載っている。

鍵となる変更は、Haskell98レポート中のパターン照合の意味論(和訳)に規則を一つ追加するというものである。第十項目として次を付け加える。「パターン!patの値vへの照合は、次のように振る舞う」

びっくりパターンはフラグ-XBangPatternsで有効になる。

びっくりパターンについての非形式的な説明

この拡張の要点は、パターンの構文に一つの新しい生成規則を追加することである。

  pat ::= !pat

eをパターン!pに対して照合するとき、まずeが(WHNFまで)評価され、その結果がpに対して照合される。例を挙げる。

f1 !x = True

この定義では、f1xについて正格になっている。びっくりパターンがなければ遅延であったところだ。もちろん、びっくりパターンは入れ子にすることができる。

f2 (!x, y) = [x,y]

ここで、f2xについて正格だが、yについては正格でない。びっくりパターンが実質的に効果を持つのは、変数パターンやワイルドカードパターンの前に置かれたときだけである。

f3 !(x,y) = [x,y]
f4 (x,y)  = [x,y]

ここでは、f3f4は等しい。もともと評価を強制するパターンの前にびっくりマークを置いても何も変わらない。

この、「びっくりマークは変数かワイルドカードの前に置かれたときのみ違いがある」という一般的な法則には、(見掛け上の)例外が一つある。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

この束縛は遅延するが、xyのいずれかが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 }

関数g5g6は全く同じである。一方、g7では(f x)が評価され、その結果がyに束縛された後、bodyが評価される。

構文と意味論

パターンの構文に一つの新しい生成規則を加える。

  pat ::= !pat

これには一つ構文的な曖昧さの問題がある。以下の例を考えよ。

f !x = 3

これは中置関数「(!)」の定義なのだろうか。それともびっくりパターンを使った「f」の定義なのだろうか。GHCは、この曖昧さを、後者を優先することで解決する。びっくりパターンが有効な状態で(!)を定義したいなら、前置記法を使わなければならない。

(!) f x = 3

Haskellのパターン照合の意味論はHaskellレポートの3.17.2節(和訳)に書かれている。この記述に、第十項目として以下のものを付け加える。

  • パターン!patの値vへの照合は、次のように振る舞う:

    • もし、vがボトムなら、照合は発散する。

    • そうでなければ、patvと照合する。

同様に、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)と同等である。

びっくりマークが最も外側にあるようなパターンは、モジュールの最上位では許されない。