Saya telah melakukan beberapa JavaScript fungsional. Saya mengira bahwa Tail-Call Optimization telah diterapkan, tetapi ternyata saya salah. Jadi, saya harus belajar sendiri Trampolining . Setelah sedikit membaca di sini dan di tempat lain, saya bisa mendapatkan dasar-dasarnya dan membangun trampolin pertama saya:
/*not the fanciest, it's just meant to
reenforce that I know what I'm doing.*/
function loopy(x){
if (x<10000000){
return function(){
return loopy(x+1)
}
}else{
return x;
}
};
function trampoline(foo){
while(foo && typeof foo === 'function'){
foo = foo();
}
return foo;
/*I've seen trampolines without this,
mine wouldn't return anything unless
I had it though. Just goes to show I
only half know what I'm doing.*/
};
alert(trampoline(loopy(0)));
Masalah terbesar saya, adalah saya tidak tahu mengapa ini berhasil. Saya mendapatkan ide untuk menjalankan kembali fungsi dalam loop sementara daripada menggunakan loop rekursif. Kecuali, secara teknis fungsi basis saya sudah memiliki loop rekursif. Saya tidak menjalankan loopy
fungsi dasar , tetapi saya menjalankan fungsi di dalamnya. Apa yang berhenti foo = foo()
menyebabkan stack overflow? Dan bukankah secara foo = foo()
teknis bermutasi, atau apakah saya melewatkan sesuatu? Mungkin itu hanya kejahatan yang perlu. Atau beberapa sintaks yang hilang.
Apakah ada cara untuk memahaminya? Atau hanya beberapa retasan yang entah bagaimana berhasil? Saya sudah bisa melewati semua yang lain, tetapi yang ini membuat saya bingung.
loopy
tidak meluap karena tidak menyebut dirinya .Jawaban:
Alasan otak Anda memberontak terhadap fungsi tersebut
loopy()
adalah karena ia merupakan jenis yang tidak konsisten :Cukup banyak bahasa bahkan tidak membiarkan Anda melakukan hal-hal seperti ini, atau setidaknya menuntut lebih banyak mengetik untuk menjelaskan bagaimana ini seharusnya masuk akal. Karena sebenarnya tidak. Fungsi dan bilangan bulat adalah jenis objek yang sama sekali berbeda.
Jadi mari kita lalui loop sementara, dengan hati-hati:
Awalnya,
foo
sama denganloopy(0)
. Apaloopy(0)
? Yah, itu kurang dari 10.000.000, jadi kita dapatkanfunction(){return loopy(1)}
. Itu nilai yang benar, dan itu adalah fungsi, jadi perulangan terus berjalan.Sekarang kita sampai
foo = foo()
.foo()
sama denganloopy(1)
. Karena 1 masih kurang dari 10.000.000, itu kembalifunction(){return loopy(2)}
, yang kemudian kami tetapkanfoo
.foo
masih berfungsi, jadi kami terus berjalan ... sampai akhirnya foo sama denganfunction(){return loopy(10000000)}
. Itu fungsi, jadi kita melakukanfoo = foo()
sekali lagi, tapi kali ini, ketika kita memanggilloopy(10000000)
, x tidak kurang dari 10000000 jadi kita hanya mendapatkan x kembali. Karena 10000000 juga bukan fungsi, ini mengakhiri loop sementara juga.sumber
Kevin dengan singkat menunjukkan bagaimana potongan kode khusus ini bekerja (bersama dengan mengapa itu cukup tidak bisa dipahami), tetapi saya ingin menambahkan beberapa informasi tentang bagaimana trampolin pada umumnya bekerja.
Tanpa optimasi tail-call (TCO), setiap panggilan fungsi menambahkan bingkai tumpukan ke tumpukan eksekusi saat ini. Misalkan kita memiliki fungsi untuk mencetak hitungan mundur angka:
Jika kita menelepon
countdown(3)
, mari kita menganalisis bagaimana tumpukan panggilan akan terlihat tanpa TCO.Dengan TCO, setiap panggilan rekursif
countdown
berada di posisi ekor (tidak ada yang tersisa untuk dilakukan selain mengembalikan hasil panggilan) sehingga tidak ada bingkai tumpukan yang dialokasikan. Tanpa TCO, tumpukan meledak bahkan sedikit besarn
.Trampolining mengatasi batasan ini dengan memasukkan pembungkus di sekitar
countdown
fungsi. Kemudian,countdown
tidak melakukan panggilan rekursif dan sebaliknya segera mengembalikan fungsi untuk memanggil. Berikut ini contoh implementasi:Untuk lebih memahami bagaimana ini bekerja, mari kita lihat tumpukan panggilan:
Pada setiap langkah
countdownHop
fungsi mengabaikan kontrol langsung dari apa yang terjadi selanjutnya, alih-alih mengembalikan fungsi untuk memanggil yang menggambarkan apa yang ingin terjadi selanjutnya. Fungsi trampolin kemudian mengambil ini dan menyebutnya, lalu memanggil fungsi apa pun yang kembali, dan seterusnya hingga tidak ada "langkah selanjutnya". Ini disebut trampolining karena aliran kontrol "memantul" antara setiap panggilan rekursif dan implementasi trampolin, alih-alih fungsi yang berulang secara langsung. Dengan mengabaikan kontrol siapa yang melakukan panggilan rekursif, fungsi trampolin dapat memastikan tumpukan tidak terlalu besar. Catatan: implementasi initrampoline
menghilangkan nilai yang dikembalikan untuk kesederhanaan.Sulit untuk mengetahui apakah ini ide yang bagus. Kinerja dapat menurun karena setiap langkah mengalokasikan penutupan baru. Optimalisasi yang cerdik dapat membuat ini layak, tetapi Anda tidak pernah tahu. Trampolining sebagian besar berguna untuk mengatasi batas rekursi keras, misalnya ketika implementasi bahasa menetapkan ukuran tumpukan panggilan maksimum.
sumber
Mungkin menjadi lebih mudah untuk dipahami jika trampolin diimplementasikan dengan tipe pengembalian khusus (alih-alih menyalahgunakan fungsi):
Bandingkan ini dengan versi Anda
trampoline
, di mana case rekursi adalah ketika fungsi mengembalikan fungsi lain, dan case dasar adalah ketika ia mengembalikan sesuatu yang lain.Itu tidak menyebut dirinya lagi. Sebagai gantinya, ia mengembalikan hasil (dalam implementasi saya, secara harfiah a
Result
) yang menyampaikan apakah akan melanjutkan rekursi atau apakah akan keluar.Ya, inilah persisnya kejahatan yang diperlukan dalam lingkaran itu. Seseorang dapat menulis
trampoline
tanpa mutasi juga, tetapi itu akan membutuhkan rekursi lagi:Tetap saja, ini menunjukkan gagasan tentang apa fungsi trampolin bahkan lebih baik.
Titik trampol adalah abstrak panggilan ekor-rekursif dari fungsi yang ingin menggunakan rekursi menjadi nilai kembali, dan melakukan rekursi aktual hanya di satu tempat -
trampoline
fungsi, yang kemudian dapat dioptimalkan di satu tempat untuk menggunakan lingkaran.sumber
foo = foo()
adalah mutasi dalam arti mengubah keadaan lokal, tetapi saya biasanya akan mempertimbangkan penugasan kembali itu karena Anda tidak benar-benar mengubah objek fungsi yang mendasarinya, Anda menggantinya dengan fungsi (atau nilai) yang dikembalikan.foo
berisi, hanya variabel yang dimodifikasi. Sebuahwhile
loop memerlukan beberapa keadaan yang bisa berubah jika Anda ingin mengakhiri, dalam hal ini variabelfoo
ataux
.fn
menjadi panggilan rekursif ketrampoline
- Saya tidak yakin itu peningkatan.