Однією з небагатьох речей, які мені не подобаються у книзі Окасакі про суто функціональні структури даних, є те, що його код засмічений невичерпним узгодженням шаблону. Як приклад, я наведу його реалізацію черг у режимі реального часу (відновлено для усунення непотрібних призупинень):
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що попередньою умовою є те, що задній список на один елемент довший, ніж передній потік. Але перевірка типу не знає - і, можливо, не може знати, тому що цей факт є невимовними в системі типів 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. Це можливо?