Salah satu dari beberapa hal yang saya tidak suka tentang buku Okasaki tentang struktur data yang murni fungsional adalah bahwa kodenya dipenuhi dengan pencocokan pola yang tidak lengkap. Sebagai contoh, saya akan memberikan implementasi antrian waktu-nyata (refactored untuk menghilangkan penangguhan yang tidak perlu):
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
Seperti dapat dilihat rotate
tidak lengkap, karena tidak mencakup kasing di mana daftar belakang kosong. Sebagian besar implementasi Standar ML akan menghasilkan peringatan tentang hal itu. Kita tahu bahwa daftar belakang tidak mungkin kosong, karena rotate
prasyaratnya adalah bahwa daftar belakang satu elemen lebih panjang dari aliran depan. Tetapi pemeriksa tipe tidak tahu - dan itu tidak mungkin tahu, karena fakta ini tidak dapat diungkapkan dalam sistem tipe ML.
Saat ini, solusi saya untuk menekan peringatan ini adalah peretasan inelegant berikut:
fun rotate (x ::: $xs, y :: ys, zs) = x ::: $rotate (xs, ys, y ::: $zs)
| rotate (_, ys, zs) = foldl (fn (x, xs) => x ::: $xs) zs ys
Tapi yang saya inginkan adalah sistem tipe yang dapat memahami bahwa tidak setiap triplet adalah argumen yang valid rotate
. Saya ingin sistem tipe untuk membiarkan saya mendefinisikan tipe seperti:
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)
Dan kemudian menyimpulkan:
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
Namun, saya tidak ingin tipe bergantung penuh, atau bahkan GADT, atau hal-hal gila lainnya yang digunakan programmer. Saya hanya ingin mendefinisikan subtipe dengan "mengukir" subset yang didefinisikan secara induktif dari tipe ML yang ada. Apakah ini layak?
sumber
Saya dapat menggunakan GADT, TypeFamilies, DataKinds, dan TypeOperators (hanya untuk estetika) dan menciptakan apa yang Anda cari:
sumber
TypeFamilies
murni berdasarkan alasan berprinsip: itu menghancurkan parametrik dan teorema bebas. Saya juga tidak terlalu nyaman dengan GADT, karena diberi GADTFoo a
, Anda dapat memiliki dua tipe isomorfikBar
danQux
, sedemikian sehinggaFoo Bar
danFoo Qux
bukan isomorfik. Itu bertentangan dengan intuisi matematis yang berfungsi memetakan sama dengan sama - dan, pada level tipenya, isomorfisme adalah gagasan yang tepat tentang kesetaraan.