Tampaknya saya telah menemukan cara umum untuk mengubah prosedur rekursif apa pun menjadi rekursi ekor:
- Tentukan sub-prosedur pembantu dengan parameter "hasil" tambahan.
- Terapkan apa yang akan diterapkan pada nilai pengembalian prosedur ke parameter itu.
- Hubungi prosedur bantuan ini untuk memulai. Nilai awal untuk parameter "hasil" adalah nilai untuk titik keluar dari proses rekursif, sehingga proses iteratif yang dihasilkan dimulai dari tempat proses rekursif mulai menyusut.
Sebagai contoh, berikut adalah prosedur rekursif asli yang akan dikonversi ( latihan SICP 1.17 ):
(define (fast-multiply a b)
(define (double num)
(* num 2))
(define (half num)
(/ num 2))
(cond ((= b 0) 0)
((even? b) (double (fast-multiply a (half b))))
(else (+ (fast-multiply a (- b 1)) a))))
Berikut adalah prosedur konversi ekor-rekursif ( latihan SICP 1.18 ):
(define (fast-multiply a b)
(define (double n)
(* n 2))
(define (half n)
(/ n 2))
(define (multi-iter a b product)
(cond ((= b 0) product)
((even? b) (multi-iter a (half b) (double product)))
(else (multi-iter a (- b 1) (+ product a)))))
(multi-iter a b 0))
Adakah yang bisa membuktikan atau menyangkal ini?
algorithms
logic
recursion
lisp
nalzok
sumber
sumber
b
menjadi kekuatan 2 menunjukkan bahwa awalnya menetapkanproduct
ke 0 tidak cukup benar; tetapi mengubahnya menjadi 1 tidak berfungsi ketikab
aneh. Mungkin Anda membutuhkan 2 parameter akumulator yang berbeda?Jawaban:
Deskripsi algoritma Anda terlalu samar untuk dievaluasi saat ini. Tapi, berikut beberapa hal yang perlu dipertimbangkan.
CPS
Bahkan, ada cara untuk mengubah kode apa pun menjadi bentuk yang hanya menggunakan panggilan ekor. Ini adalah transformasi CPS. CPS ( Continuation-Passing Style ) adalah bentuk kode pengekspresian dengan meneruskan setiap fungsi sebagai kelanjutan. Kelanjutan adalah gagasan abstrak yang mewakili "sisa dari suatu kompuasi". Dalam kode yang dinyatakan dalam bentuk CPS, cara alami untuk memverifikasi kelanjutan adalah sebagai fungsi yang menerima nilai. Dalam CPS, alih-alih fungsi yang mengembalikan nilai, melainkan menerapkan fungsi yang mewakili kelanjutan saat ini untuk "dikembalikan" oleh fungsi.
Misalnya, pertimbangkan fungsi berikut:
Ini dapat diungkapkan dalam CPS sebagai berikut:
Itu jelek, dan sering lambat, tetapi memang memiliki keuntungan tertentu:
TCO
Tampak bagi saya bahwa satu-satunya alasan untuk khawatir dengan ekor-rekursi (atau ekor-panggilan pada umumnya) adalah untuk keperluan optimasi ekor-panggilan (TCO). Jadi saya pikir pertanyaan yang lebih baik untuk ditanyakan adalah "apakah kode hasil transformasi saya yang ekor-panggilan dioptimalkan?".
Jika kita sekali lagi mempertimbangkan CPS, salah satu karakteristiknya adalah bahwa kode yang diekspresikan dalam CPS hanya terdiri dari ekor-panggilan. Karena semuanya adalah panggilan ekor, kita tidak perlu menyimpan titik kembali ke stack. Jadi semua kode dalam bentuk CPS harus dioptimalkan untuk panggilan ekor, bukan?
Ya tidak cukup. Anda tahu, sementara itu mungkin tampak bahwa kita telah menghilangkan tumpukan, semua yang telah kita lakukan hanyalah mengubah cara kita mewakilinya. Tumpukan sekarang merupakan bagian dari penutupan yang mewakili kelanjutan. Jadi CPS tidak secara ajaib membuat semua kode panggilan ekor kami dioptimalkan.
Jadi jika CPS tidak bisa membuat semuanya TCO, apakah ada transformasi khusus untuk rekursi langsung yang bisa? Tidak, tidak secara umum. Beberapa rekursi adalah linier, tetapi beberapa tidak. Rekursi non-linear (misalnya, pohon) hanya harus mempertahankan jumlah variabel dari suatu tempat.
sumber