Subscribed unsubscribe Subscribe Subscribe

Pattern synonymsの使い道

haskell

もうすぐ出る出ると言われ続けて久しいGHC 7.8は、大きめの新機能がいくつも入る。そのうちの一つがpattern synonymsという言語拡張で、これまで第一級市民でなかったパターンを、再利用可能な部品として扱えるようにする仕組み。チケットが登録されてから3年越しに実現された。

参考資料としては

あたりを読むと良い。

先日たまたまpattern synonymsのよい使いどころに遭遇したので紹介する。きっかけは


というツイートに対して、
と返ってきたのでOlegさんのList m aの使用例を元に書いてみたらpattern synonymsがちょうど良かったという流れ。

もとのOlegさんのコードと比べると、List m aの定義が異なっているのに、

pattern Nil = Pure ()
pattern Cons x mxs = Free (F x mxs)

という2つのpattern synonymsを使うことで、他の関数は(些末なスタイルの違いを除けば)まったく同じになっていることがわかる。ここから2つの利点が挙げられる。

  • 繰り返し出てくる複雑なパターンの簡略化
    • ここではFree (F x mxs)のようなネストしたパターンをCons x mxsと書ける。またこのpattern synonymsがbidirectionalなのでFree (F x mxs)という値もまたCons x mxsと書ける。
  • パターンマッチのインタフェースを保ったまま、データ型の内部表現を変更できる
    • 従来の代数的データ構造のみでは、パターンマッチするにはデータコンストラクタを公開する必要があった。これはつまりデータ構造を変更するとパターンマッチ部分も壊れることを意味する。先の例のList m aではOlegさんのコードからfreeを使ったコードに変えると、NilとConsを使うすべての関数が壊れてしまう。pattern synonymsを使えばこの問題は回避できる。データコンストラクタは非公開のまま、pattern synonymsだけを公開すればよい。

Pattern guardsやview patternsとの違い

GHCのパターンに関する言語拡張としてpattern guardsとview patternsというものもある。どちらもpattern synonymsとは別の機能で、特にview patternsはpattern synonymsと併用することもできる。

次のコードは同じ関数zipZagを言語拡張無し、pattern guards、view patterns、pattern synonymsを使って書いてみた。

{-# LANGUAGE PatternGuards #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ViewPatterns #-}
import Data.Sequence (Seq)
import qualified Data.Sequence as Seq

-- No language extensions
zipZag :: Seq a -> Seq b -> Seq (a, b)
zipZag xxs yys =
  case Seq.viewl xxs of
    x Seq.:< xs -> case Seq.viewr yys of
      ys Seq.:> y -> (x, y) Seq.<| zipZag xs ys
      _ -> Seq.empty
    _ -> Seq.empty

-- Pattern guards
zipZag' :: Seq a -> Seq b -> Seq (a, b)
zipZag' xxs yys
  | x Seq.:< xs <- Seq.viewl xxs
  , ys Seq.:> y <- Seq.viewr yys = (x, y) Seq.<| zipZag' xs ys
  | otherwise = Seq.empty

-- View patterns
zipZag'' :: Seq a -> Seq b -> Seq (a, b)
zipZag'' (Seq.viewl -> x Seq.:< xs) (Seq.viewr -> ys Seq.:> y) =
  (x, y) Seq.<| zipZag'' xs ys
zipZag'' _ _ = Seq.empty 

-- Unidirectional pattern synonyms + view patterns
pattern Empty <- (Seq.viewl -> Seq.EmptyL)
pattern x :< xs <- (Seq.viewl -> x Seq.:< xs)
pattern xs :> x <- (Seq.viewr -> xs Seq.:> x)

zipZag''' :: Seq a -> Seq b -> Seq (a, b)
zipZag''' (x :< xs) (ys :> y) = (x, y) Seq.<| zipZag''' xs ys
zipZag''' _ _ = Seq.empty

pattern guardsもview patternsも、関数適用(ここではSeq.viewlとSeq.viewr)した後の結果の値に対してパターンマッチするコードを書きやすくする。一方、この例でpattern synonymsはunidirectionalでzipZag''とzipZag'''を比べてみればわかるように、単にパターンに別名を付けるために使われている。

pattern synonymsは、zipZag側が本当のデータ型に対してパターンマッチしているかのように書ける点がpattern guardsとview patternsだけを使った場合に対して優れている。