Una delle poche cose che non mi piacciono del libro di Okasaki su strutture di dati puramente funzionali è che il suo codice è disseminato di un pattern inesauribile. Ad esempio, darò la sua implementazione di code in tempo reale (refactored per eliminare inutili sospensioni):
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
Come si può vedere rotate
non è esaustivo, perché non copre il caso in cui l'elenco posteriore è vuoto. La maggior parte delle implementazioni ML standard genererà un avviso al riguardo. Sappiamo che l'elenco posteriore non può essere vuoto, perché rotate
il presupposto è che l'elenco posteriore abbia un elemento più lungo del flusso anteriore. Ma il controllo del tipo non lo sa - e non può assolutamente saperlo, perché questo fatto è inesprimibile nel sistema di tipi di ML.
In questo momento, la mia soluzione per sopprimere questo avviso è il seguente hack non elegante:
fun rotate (x ::: $xs, y :: ys, zs) = x ::: $rotate (xs, ys, y ::: $zs)
| rotate (_, ys, zs) = foldl (fn (x, xs) => x ::: $xs) zs ys
Ma quello che voglio davvero è un sistema di tipi in grado di capire che non tutte le terzine sono un valido argomento rotate
. Vorrei che il sistema dei tipi mi permettesse di definire tipi come:
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)
E quindi dedurre:
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
Tuttavia, non voglio tipi dipendenti in piena regola, o persino GADT, o nessuna delle altre cose folli che alcuni programmatori usano. Voglio solo definire i sottotipi "scolpendo" sottoinsiemi induttivamente definiti di tipi ML esistenti. È fattibile?