PatternSynonyms
既存のデータ構造に対して名前付きの別パターンを定義できる機能
新しい型を作るわけではない
patternという特別な予約語が導入される
GHC-7.8.1
docs
2種類の書き方
unidirectional synonyms (単方向)
bidirectional synonyms (双方向)
code:haskell
{-# LANGUAGE PatternSynonyms #-}
e.g.
Pointという型があるとする
code:haskell
data Point = Point Int Int
これに対し、patternを使って新たにOriginを定義する
code:hs
pattern Origin :: Point
pattern Origin = Point 0 0
これで、下記のようにOriginを普通のコンストラクタのようにして使える様になる
code:haskell
f :: Point -> String
f Origin = "origin"
f _ = "other"
下記と同じ意味
code:haskell
isOrigin (Point 0 0) = True
isOrigin _ = False
既存の型に対しても使える
e.g.
code:haskell
pattern Singleton x <- x
これ↑で、こう↓書ける
code:haskell
case xs of
Singleton x -> ...
_ -> ...
参考
GHC拡張ノック(Part 1) - Haskell-jp
#WIP
COMPLETEプラグマで網羅性チェックできる
docsの序盤に書いている例
こんな型とコードを考える
code:hs
data Type = App String Type
collectArgs :: Type -> Type
collectArgs (App "->" t1, t2) = t1 : collectArgs t2
collectArgs _ = []
isInt (App "Int" []) = True
isInt _ = False
このTypeという型はStringを引数に取る
Stringというと、任意の文字列を取れるわけだが、ここで想定しているものはもっと少ない
"->"とか"Int"のような数個の文字列しか許容しない
そこで、App "->" [t1, t2]とか、App "Int" []のような値に名前を付けて持ち回せるようにしたい
そこでPatternSynonymsが使える
code:hs
{-# LANGUAGE PatternSynonyms #-}
pattern Arrow t1 t2 = App "->" t1, t2
pattern Int = App "Int" []
patternという予約語を導入して、値の取りうるpatternに対して、synonymを作れる
元のコードはSynonymを使って、こう書き直せる
code:hs
collectArgs :: Type -> Type
collectArgs (Arrow t1 t2) = t1 : collectArgs t2
collectArgs _ = []
isInt :: Type -> Bool
isInt Int = True
isInt _ = False
「値コンストラクタを使った値」のsynonymを作るのね
この例、ただの設計ミスな気がするから例としてよくなさそうmrsekut.icon
PatternSynonymsの説明としてはわかりやすいから良いかmrsekut.icon
この辺でみた
code:hs
{-# LANGUAGE PatternSynonyms #-}
module NonZero
( NonZero()
, pattern NonZero
, unNonZero
, nonZero
) where
newtype NonZero a = UnsafeNonZero a
pattern NonZero a <- UnsafeNonZero a -- 疑似値コンストラクタを作って、これを公開
unNonZero :: NonZero a -> a
unNonZero (UnsafeNonZero a) = a
nonZero :: (Num a, Eq a) => a -> Maybe (NonZero a)
nonZero 0 = Nothing
nonZero i = Just (UnsafeNonZero i)
-- patternを定義していないと、この↓定義がerrorになる(これは外部module)
safeDivide :: Int -> NonZero Int -> Int
safeDivide i (NonZero j) = i div j
pattern NonZero a <- UnsafeNonZero aを定義して、これを公開することで
safeDivide i (NonZero j) = ..みたいに、外部でもパターンマッチはできる
↑このNonZeroはpatternで生成したもの
が、外部ではNonZero型を作ることはできない
なぜなら、UnsafeNonZeroが公開されていないから
つまり、
外部でもパターンマッチできるが
外部で型を作ることはできない
の2点を満たすために使われている
smart constructorを定義するためのデザパタと言える
https://gitlab.haskell.org/ghc/ghc/-/wikis/pattern-synonyms
https://kazu-yamamoto.hatenablog.jp/entry/20170919/1505787393
https://downloads.haskell.org/~ghc/8.0.2/docs/html/users_guide/glasgow_exts.html#record-patsyn
recordでもサポート
https://haskell-explained.gitlab.io/blog/posts/2019/08/27/pattern-synonyms/index.html
https://haskell.jp/blog/posts/2018/about-ghc-exts-1.html
https://kowainik.github.io/posts/arrows-zoo#pattern-synonyms
https://qiita.com/as_capabl/items/d2eb781478e26411a44c