(Terinspirasi oleh jawaban saya untuk pertanyaan ini .)
Pertimbangkan kode ini (seharusnya menemukan elemen terbesar yang kurang dari atau sama dengan input yang diberikan):
data TreeMap v = Leaf | Node Integer v (TreeMap v) (TreeMap v) deriving (Show, Read, Eq, Ord)
closestLess :: Integer -> TreeMap v -> Maybe (Integer, v)
closestLess i = precise Nothing where
precise :: Maybe (Integer, v) -> TreeMap v -> Maybe (Integer, v)
precise closestSoFar Leaf = closestSoFar
precise closestSoFar (Node k v l r) = case i `compare` k of
LT -> precise closestSoFar l
EQ -> Just (k, v)
GT -> precise (Just (k, v)) r
Ini tidak terlalu malas. Setelah GT
kasing dimasukkan, kami tahu pasti bahwa nilai pengembalian akhir akan menjadi Just
sesuatu yang bukan Nothing
, tetapi Just
masih belum tersedia sampai akhir. Saya ingin membuat ini lebih malas sehingga Just
tersedia segera setelah GT
kasing dimasukkan. Kasus pengujian saya untuk ini adalah bahwa saya ingin Data.Maybe.isJust $ closestLess 5 (Node 3 () Leaf undefined)
mengevaluasi True
daripada bottoming. Inilah satu cara yang bisa saya pikirkan untuk melakukan ini:
data TreeMap v = Leaf | Node Integer v (TreeMap v) (TreeMap v) deriving (Show, Read, Eq, Ord)
closestLess :: Integer -> TreeMap v -> Maybe (Integer, v)
closestLess _ Leaf = Nothing
closestLess i (Node k v l r) = case i `compare` k of
LT -> closestLess i l
EQ -> Just (k, v)
GT -> Just (precise (k, v) r)
where
precise :: (Integer, v) -> TreeMap v -> (Integer, v)
precise closestSoFar Leaf = closestSoFar
precise closestSoFar (Node k v l r) = case i `compare` k of
LT -> precise closestSoFar l
EQ -> (k, v)
GT -> precise (k, v) r
Namun, saya sekarang mengulangi diri saya sendiri: logika inti sekarang ada di dalam closestLess
dan di precise
. Bagaimana saya bisa menulis ini sehingga malas tetapi tanpa mengulangi sendiri?
sumber
Mulai dari implementasi tanpa-malas saya, saya pertama kali refactored
precise
untuk menerimaJust
sebagai argumen, dan menggeneralisasikan tipenya sesuai:Kemudian, aku berubah untuk melakukan
wrap
awal dan menyebut dirinya denganid
dalamGT
kasus:Ini masih bekerja persis seperti sebelumnya, kecuali untuk manfaat kemalasan tambahan.
sumber
id
di antaraJust
dan final(k,v)
dihilangkan oleh kompiler? mungkin tidak, fungsi yang seharusnya buram, dan Anda bisa (ketik-layak) digunakanfirst (1+)
daripadaid
semua kompiler tahu. tapi itu membuat kode ringkas ... tentu saja, kode saya adalah penguraian dan spesifikasi Anda di sini, dengan penyederhanaan tambahan (penghapusanid
s). juga sangat menarik bagaimana tipe yang lebih umum berfungsi sebagai kendala, hubungan antara nilai yang terlibat (meskipun tidak cukup ketat, denganfirst (1+)
diizinkan sebagaiwrap
).precise
digunakan pada dua jenis, yang secara langsung berhubungan dengan dua fungsi khusus yang digunakan dalam varian yang lebih verbose. interaksi yang bagus di sana. Juga, saya tidak akan menyebut CPS ini,wrap
tidak digunakan sebagai kelanjutan, itu tidak dibangun "di dalam", itu ditumpuk - dengan rekursi - di luar. Mungkin jika itu yang digunakan sebagai kelanjutan Anda bisa menyingkirkan orang-orang asingid
s ... btw kita bisa lihat di sini sekali lagi bahwa pola lama argumen fungsional digunakan sebagai indikator apa yang harus dilakukan, beralih antara dua program aksi (Just
atauid
).Saya pikir versi CPS yang Anda jawab sendiri adalah yang terbaik, tetapi untuk kelengkapannya ada beberapa ide lagi. (EDIT: Jawaban Buhr sekarang adalah yang paling berprestasi.)
Gagasan pertama adalah menyingkirkan
closestSoFar
akumulator, dan " " membiarkanGT
kasus menangani semua logika memilih nilai paling kanan paling kecil dari argumen. Dalam formulir ini,GT
kasing dapat langsung mengembalikanJust
:Ini lebih sederhana, tetapi membutuhkan lebih banyak ruang di tumpukan ketika Anda menekan banyak
GT
kasing. Secara teknis Anda bahkan bisa menggunakannyafromMaybe
dalam bentuk akumulator (yaitu, mengganti yangfromJust
tersirat dalam jawaban luqui), tetapi itu akan menjadi cabang yang redundan dan tidak terjangkau.Gagasan lain bahwa benar-benar ada dua "fase" dari algoritma, satu sebelum dan satu setelah Anda menekan
GT
, jadi Anda parameterkan oleh boolean untuk mewakili dua fase ini, dan gunakan tipe dependen untuk menyandikan invarian bahwa akan selalu ada menghasilkan fase kedua.sumber
Bagaimana tentang
?
sumber
Just
namun total. Saya tahu bahwa solusi Anda sebagai tertulis sebenarnya total, tetapi rapuh karena modifikasi yang tampaknya aman dapat menghasilkan bottoming.Just
, sehingga akan menambah tes untuk memastikan itu tidakNothing
setiap kali berulang.Kita tidak hanya selalu tahu
Just
, setelah penemuan pertamanya, kita juga selalu tahuNothing
sampai saat itu. Itu sebenarnya dua "logika" yang berbeda.Jadi, kita belok kiri dulu, jadi buat itu eksplisit:
Harganya kami ulangi paling banyak satu langkah paling banyak sekali.
sumber