• Tidak ada hasil yang ditemukan

Rekursi Ekor (Tail Recursion)

Dalam dokumen Buku bhs pemrograman Q Equational 2018 (Halaman 78-80)

7 Evaluasi Ekspresi dan Persamaan

7.10 Rekursi Ekor (Tail Recursion)

Penyamaan di atas untuk fungsi add dan fac adalah contoh untuk definisi berulang. Proses perhitungan/Computational diturunkan dari definisi seperti ditandai dengan rantai aturan yang dipenjarakan di mana aturan yang sama diaktipkan berulang-ulang kali. Sebagai contoh, suatu evaluasi fac N dengan N>0 memerlukan pengaktifan berulang N menyangkut aturan:

fac N = N*fac (N-1) if N>0;

Karenanya, jika N menjadi sangat besar, definisi fac yang berulang dalam bahaya kehabisan ruang tumpukan. Di dalam berikut ini, kita menunjukkan bagaimana untuk menghindari cacat ini dengan memanfaatkan teknik“tail-recursive programming”.

Ini merupakan suatu fakta terkenal bahwa fungsi faktorial juga mempunyai suatu implementasi

“iterative” yang dapat dieksekusi di dalam ruang memori tetap. Gagasannya adalah untuk memelihara suatu “running produk” P dan suatu “counter” I yang menghitung mundur dari N ke

1. Algoritma Iterative dapat dituliskan di dalam suatu bahasa pemrograman konvensional seperti Pascal sebagai berikut:

P := 1; I := N; while I>0 do begin

P := P*I; I := I-1; end;

Selama bahasa Q tidak menghasilkan constructs pengulangan khusus manapun (dan [itu] juga kekurangan suatu operasi tugas), ada suatu definisi alternatif fac yang mengambil gagasan untuk running produk dan counter dan mengimplementasikan fungsi faktorial di dalam suatu pertunjukan iterative:

fac N = fac2 1 N;

fac2 P I = fac2 (P*I) (I-1) if I>0; = P otherwise;

Di sini, “variable state” P dan I diterapkan sebagai argument dari suatu fungsi “auxiliary/alat

bantu” fac2 dari fac. Lagi-lagi, ini adalah suatu definisi berulang; [itu] memerlukan aplikasi berulang N menyangkut aturan fac2 P I = fac2 (P*I) (I-1) jika I>0; ketika fac N dihitung. Bagaimanapun, dalam perbedaan kepada definisi fac kita sebelumnya, aturan berulang selalu

diterapkan “di atas” ungkapan target. Aturan seperti (itu) disebut tailrecursive. (Nama “recursi ekor (tail recursion)” datang dari fakta bahwa aplikasi berulang fac2 adalah operasi terakhir yang dipertimbangkan selama evaluasi leftmost-innermost dari sisi righthand.) Sebagai contoh, evaluasi fac 3 sekarang berproses sebagai berikut (menyingkat pengurangan dengan aturan built-in untuk ‘-’ dan ‘*’):

fac 3 => fac2 1 3 => fac2 (1*3) (3-1) => fac2 3 2 => fac2 (3*2) (2-1) => fac2 6 1 => fac2 (6*1) (1-1) => fac2 6 0 => 6

Definisi Tail-Recursive dapat dipekerjakan untuk menerapkan bermacam-macam fungsi yang

dapat dihitung pada suatu mesin dengan suatu satuan set yang ditetapkan “registers” dan tidak

ada memori bantu. Sebagai contoh, di sini adalah suatu implementasi tail-recursive menyangkut fungsi Fibonacci:

fib N = fib2 1 0 N;

fib2 A B N = fib2 (A+B) A (N-1) if N>0; = B otherwise;

(Definisi ini juga mempunyai akibat sampingan yang diinginkan bahwa itu mengurangi running

time yang bersifat exponen dari definisi “naive” diberikan di Bagian 7.1 [Penyamaan

(Equations)], halaman 51, untuk linier.)

Interpreter Q mempekerjakan suatu trik optimisasi yang pandai, biasanya dikenal sebagai optimisasi recursi ekor/tail (lihat e.g., [Steele/Sussman 1975]), untuk benar-benar melaksanakan definisi tail-recursive di dalam ruang tumpukan tetap. Karenanya tidak ada constructs pengulangan khusus diperlukan untuk menerapkan algoritma iterative secara efisien di dalam bahasa Q.

Berasumsi bahwa di dalam model tumpukan/stack kita dari evaluasi ungkapan kita bekerja pada suatu pengurangan X => Y, dan bahwa kita telah secara berulang mengevaluasi semua bagian- bagian dari sisi right-hand Y, tetapi tidak Y nya sendiri. Lagipula, mengira bahwa dalam rangka mengevaluasi Y kita akan harus menerapkan aturan Y => Z berikutnya. Kemudian, sebagai ganti memelihara aturan X => Y yang dipenjarakan dan mengevaluasi Y menggunakan aturan Y => Z secara berulang, kita dapat juga dengan seketika melaksanakan pengurangan X => Y dan menggantikan aturan ini dengan Y => Z pada tumpukan/stack. Dengan begitu, aturan yang baru Y => Z tidak akan memerlukan ruang tumpukan/stack tambahan manapun sama sekali, tetapi sederhananya kita kembali menggunakan “pengaktifan record/catatan” yang ada untuk

aturan X => Y. Dengan kata lain, sebagai ganti memohon aturan Y => Z sebagai suatu

“subroutine”, kita secara efektif melaksanakan semacam “goto” untuk aturan yang baru. Kita

juga mengacu pada proses ini sebagai menyelenggarakan pengurangan ekor/tail X => Y. Evaluasi sekarang berproses seolah-olah kita tengah bekerja pada aturan Y => Z pada pokoknya.

Sebagai contoh, dengan definisi fac yang baru evaluasi fac 3 akan dilaksanakan hanya menggunakan tingkatan/level tunggal dari aturan yang dipenjarakan (suspended rules) (lagi- lagi, symbol ? isyarat pengguguran dari suatu aturan dengan menghilangkan qualifier):

fac 3 => fac2 1 3: fac2 1 3 => fac2 (1*3) (3-1): 3>0 => true 1*3 => 3 3-1 => 2 fac2 3 2 => fac2 (3*2) (2-1): 2>0 => true 3*2 => 6 2-1 => 1 fac2 6 1 => fac2 (6*1) (1-1): 1>0 => true 6*1 => 6 1-1 => 0 fac2 6 0 => fac2 (6*0) (0-1): 0>0 => false ? fac2 6 0 => 6

Di samping teknik optimisasi rekursi ekor (tail recursion) dibahas di atas, interpreter Q juga secara otomatis mengoptimalkan urutan toplevel (yaitu, aplikasi operator || yang tidak berada di dalam suatu subexpression tersarang) pada sisi right-hand dari penyamaan/equations, sedemikian sehingga imperative-style looping constructs dapat dieksekusi di dalam suatu

pertunjukan tail-recursive juga. Pertimbangkanlah, sebagai contoh, fungsi standard library melakukan yang mana menerapkan suatu fungsi kepada masing-masing anggota dari suatu list, seperti fungsi map dibahas di Bagian 7.1 [Penyamaan (Equations)], halaman 51, tetapi sebagai ganti mengumpulkan hasil di dalam suatu list keluaran sederhananya membuang-buang nilai intermediate/antara dan mengeembalikan (). Ini bermanfaat, tentu saja, hanya jika fungsi dievaluasi semata-mata untuk efek sampingnya, e.g., jika kita ingin mencetak ke luar semua elemen-elemen dari suatu list. Definisi lakukan agak secara langsung:

do F [] = ();

do F [X|Xs] = F X || do F Xs;

Sekarang definisi ini tidak tail-recursive dalam pengertian yang tegas menyinggung pengertian di atas, sebab aplikasi terakhir pada sisi right-hand dari aturan yang kedua benar-benar melibatkan (||) dan bukannya fungsi. (Mengingat bahwa suatu ungkapan sisipan (infix) seperti

X||Y adalah tak lain hanya “syntact sugar” untuk aplikasi fungsi (||) X Y.)

Bagaimanapun, sebagai perbaikan, interpreter Q benar-benar mengimplementasikan urutan toplevel pada sisi right-hand dari suatu aturan dengan mengarahkan manipulasi tumpukan/stack. Itu adalah, argument pertama dari suatu urutan yang dibuang secepatnya setelah dihitung. Dengan ini, interpreter, setelah menghitung F X, secara efektif menyelesaikan pengurangan do F [X|Xs] => do F Xs pada yang dapat melaksanakan optimisasi recursi ekor/tail seperti biasanya. Dengan begitu definisi di atas benar-benar bekerja di dalam ruang tumpukan tetap, seperti salah satu yang mungkin layak diharapkan.

Dalam dokumen Buku bhs pemrograman Q Equational 2018 (Halaman 78-80)