純粋に機能的なデータ構造に関する岡崎の本で私が嫌いないくつかのことの1つは、彼のコードが網羅的なパターンマッチングで散らかされていることです。例として、彼にリアルタイムキューの実装を示します(不要な中断を排除するためにリファクタリングしました)。
infixr 5 :::
datatype 'a stream = Nil | ::: of 'a * 'a stream lazy
structure RealTimeQueue :> QUEUE =
struct
(* front stream, rear list, schedule stream *)
type 'a queue = 'a stream * 'a list * 'a stream
(* the front stream is one element shorter than the rear list *)
fun rotate (x ::: $xs, y :: ys, zs) = x ::: $rotate (xs, ys, y ::: $zs)
| rotate (Nil, y :: nil, zs) = y ::: $zs
fun exec (xs, ys, _ ::: $zs) = (xs, ys, zs)
| exec args = let val xs = rotate args in (xs, nil, xs) end
(* public operations *)
val empty = (Nil, nil, Nil)
fun snoc ((xs, ys, zs), y) = exec (xs, y :: ys, zs)
fun uncons (x ::: $xs, ys, zs) = SOME (x, exec (xs, ys, zs))
| uncons _ = NONE
end
図から分かるようにrotate
、それはリアリストが空の場合をカバーしていないので、網羅的なものではありません。ほとんどの標準ML実装は、それに関する警告を生成します。我々はので、リアリストはおそらく空にすることはできませんことを知っているrotate
の前提条件は、そのリアリスト1の要素長いフロントストリームを超えています。しかし、型チェッカーは知りません。そして、MLの型システムではこの事実を表現できないため、それを知ることはできません。
現在、この警告を抑制するための私の解決策は、次の洗練されていないハックです。
fun rotate (x ::: $xs, y :: ys, zs) = x ::: $rotate (xs, ys, y ::: $zs)
| rotate (_, ys, zs) = foldl (fn (x, xs) => x ::: $xs) zs ys
しかし、私が本当に望んでいるのは、すべてのトリプレットがの有効な引数であるとは限らないことを理解できる型システムですrotate
。型システムに次のような型を定義させたい:
type 'a triplet = 'a stream * 'a list * 'a stream
subtype 'a queue of 'a triplet
= (Nil, nil, Nil)
| (xs, ys, zs) : 'a queue => (_ ::: $xs, _ :: ys, zs)
| (xs, ys, zs) : 'a queue => (_ ::: $xs, ys, _ ::: $zs)
そして推論:
subtype 'a rotatable of 'a triplet
= (xs, ys, _) : 'a rotatable => (_ ::: $xs, _ :: ys, _)
| (Nil, y :: nil, _)
subtype 'a executable of 'a triplet
= (xs, ys, zs) : 'a queue => (xs, ys, _ ::: $zs)
| (xs, ys, Nil) : 'a rotatable => (xs, ys, Nil)
val rotate : 'a rotatable -> 'a stream
val exec : 'a executable -> 'a queue
ただし、本格的な依存型、GADT、または特定のプログラマーが使用するその他のクレイジーなものは必要ありません。私は、既存のMLタイプの帰納的に定義されたサブセットを「切り出す」ことによってサブタイプを定義したいだけです。これは実現可能ですか?