パターンマッチの実装メモ
資料
- Augustsson, L. (1985). Compiling pattern matching. In: Jouannaud, JP. (eds) Functional Programming Languages and Computer Architecture. FPCA 1985. Lecture Notes in Computer Science, vol 201. Springer, Berlin, Heidelberg. https://doi.org/10.1007/3-540-15975-4_48
- Simon L. Peyton Jones, The Implementation of Functional Programming Languages, Prentice Hall, 1987
- 第5章がPhilip WadlerによるEfficient Compilation of Pattern-Matching
- バックトラック法っぽい
- Fabrice Le Fessant and Luc Maranget. 2001. Optimizing pattern matching. In Proceedings of the sixth ACM SIGPLAN international conference on Functional programming (ICFP '01). Association for Computing Machinery, New York, NY, USA, 26–37. https://doi.org/10.1145/507635.507641
- バックトラック法の改善、or-patternを実装している
- MARANGET, L. (2007). Warnings for pattern matching. Journal of Functional Programming, 17(3), 387-421. doi:10.1017/S0956796807006223
- Luc Maranget, Compiling Pattern Matching to good Decision Trees, ML'08
- κeen, コンパイル技法: パターンマッチ
- John Reppy and Mona Zahir, Compiling Successor ML Pattern Guards, ML Family Workshop 2019
Standard ML/Successor MLの場合
パターンの構成要素
- ワイルドカード、変数
- レコード
- 他の言語ではコンストラクターが複数の値をもてたりするけど、Standard MLでは複数の値を持てるのはレコードだけ
-
(pat1, pat2)
みたいに書いた時、pat2
中のnested matchではpat1
で束縛した変数が見える- ただし、
{ ... = pat1, a = pat2 }
は{ a = pat2, ... = pat1 }
の構文糖衣なので、pat2
からpat1
は見えない。実行順もpat2
が先。
- ただし、
- コンストラクター
- 定数(整数、文字、文字列)
-
Int8.int
やWord8.word
やchar
はソースコード中に全パターン列挙される可能性があるが、それ以外はコンストラクターが無限と扱って良さそう
-
-
ref
- 参照セルからの読み出しがあるので、nested matchの副作用と干渉しうる
-
true
,false
-
::
,nil
- ユーザー定義のデータ型
- コンストラクターの数は有限で、コンパイル時に判明している
- 例外
exn
- コンストラクターは無限にある(実行時にも増える可能性がある)
- 定数(整数、文字、文字列)
- conjunction:
pat1 as pat2
-
決してマッチしないパターンを書ける:→4.11.5で決してマッチしないパターンは禁止されている(ので、処理系はこういうのを検出してエラーを出す必要がある)true as false
-
pat2
からはpat1
で束縛した変数が見える(nested matchで有用) - nested matchを使うと
x as pat1 with pat2 = x
みたいに書けそう
-
- disjunction:
pat1 | pat2
-
pat2
が冗長な場合は警告が出されるべき(4.11.3)だが、pat1
が冗長な場合の警告は要求されていない
-
- nested match:
pat1 with pat2 = exp
- 副作用が起こりうるので要注意
- (拡張)vector
#[pat1, pat2, ...]
- レコードとコンストラクターの組み合わせに近そう
矛盾するデータ型と、網羅性、冗長性
signatureを使うと矛盾するデータ型の仕様を書けて、functorを使うと矛盾するデータ型に対するパターンマッチみたいなことをできる。そういうコードは決して呼ばれることはないのだが、網羅性や(非)冗長性をどうするか。例:
signature S = sig
datatype t = T
datatype u = U
sharing type t = u
val x : t
end
functor F (S : S) = struct
val y = case S.x of
S.T => "T"
| S.U => "U" (* これは冗長判定されるのかどうか *)
end;
参照セルの読み出しと副作用
nested matchがあるとパターンマッチの途中でマッチ対象の参照セルの値が変わる可能性が考えられる。
case ref 0 of
ref 42 => 1
| x with false = (x := 42; true) => 2
| ref 42 => 3
の3番目の節の ref 42
は冗長ではなく、このパターンマッチは3を返す、的な。
この件は網羅性検査にも影響する可能性があり、
datatype t = A | B | C
case ref C of
ref A => 1
| ref B => 2
| x with false = (x := A; true) => 3
| ref C => 4
は Match
例外を投げる。
conjunctionとnested match
conjunction pat1 as pat2
は「pat1
と pat2
の共通部分を表すパターン」を計算して適宜部分式に名前をつければ良いように思えるが、nested matchがあるとそう単純にはいかない。例えば、
fun f x = case x of
(_ :: a with [] = (print "A\n"; a)) as (_ :: _ :: _) => 1
| _ => 2;
val _ = f [1];
のパターンを
fun f x = case x of
(_ :: (a as (_ :: _)) with [] = (print "A\n"; a)) => 1
| _ => 2;
val _ = f [1];
と書き換えると実行結果が変わる。
LunarMLに実装するにあたって
中間言語
中間言語にjoin pointとswitchを用意する。
switchの効率的な実現
JavaScriptバックエンドではswitchを使える。
Luaにはswitchはない。if文の羅列にする必要がある。
パターンの列に対して実装したい操作
- 網羅性検査
- 特に、「単一のパターンが網羅的かどうか」という判断を束縛時またはnested matchで利用する。disjunctive patternがあると一般のマッチの検査と同じことが必要になりそう。
- 網羅的でない場合は、マッチしない値の例を出力できると良い。
- 冗長的でないパターン列にワイルドカードパターンを追加して冗長性が生まれるなら、網羅的である(冗長性検査ができれば網羅性検査ができる)。
- 冗長性検査
- 真の部分パターン列が網羅的になった場合は冗長性がある(網羅性検査ができれば冗長性検査ができる)
- disjunctive patternの各部分についても冗長性を検査したい。
- 二つのパターンに対して共通のマッチする値があるかどうか
- conjunctive patternの検査に必要。
二つのパターンに対して共通のマッチする値があるかどうか
パターンの有限列に対して、それら全てにマッチする値があるかどうかを表す述語
(Compatはcompatibleの略。)
パターンの有限列に対して、それら全てにマッチする値をマッチさせるパターン(そういう値が存在しなければ
ただし
nested matchは別途考える必要がある。
(関数の名前は、IntersectionのIを取った。)