Y combinator dan optimisasi panggilan ekor

20

Definisi kombinator Y dalam F # adalah

let rec y f x = f (y f) x

f berharap sebagai argumen pertama beberapa kelanjutan untuk subproblem rekursif. Menggunakan yf sebagai kelanjutan, kita melihat bahwa f akan diterapkan pada panggilan yang berurutan yang dapat kita kembangkan

let y f x = f (y f) x = f (f (y f)) x = f (f (f (y f))) x etc...

Masalahnya adalah, apriori, skema ini menghalangi penggunaan optimasi panggilan ekor: memang, mungkin ada beberapa operasi yang tertunda di f, dalam hal ini kita tidak bisa hanya memutasi frame stack lokal yang terkait dengan f.

Jadi:

  • di satu sisi, menggunakan kombinator Y membutuhkan kelanjutan yang berbeda secara eksplisit dari fungsi itu sendiri.
  • pada saat yang lain untuk menerapkan TCO, kami ingin tidak ada operasi yang tertunda di f dan hanya memanggil f itu sendiri.

Apakah Anda tahu cara apa pun yang bisa dilakukan keduanya? Seperti Y dengan trik akumulator, atau Y dengan trik CPS? Atau argumen yang membuktikan bahwa tidak mungkin dilakukan?

nicolas
sumber
Sudahkah Anda menambahkan pekerjaan kunci ke implementasi Anda? Saya pikir itu perlu dari bacaan saya ..
Jimmy Hoffa
Apakah Anda memiliki bukti bahwa itu tidak mengoptimalkan panggilan ekor? Saya harus berpikir Anda mungkin ingin membaca IL untuk fungsi itu dan lihat, saya tidak akan terkejut jika kompiler cukup pintar untuk datang dengan sesuatu ..
Jimmy Hoffa
dalam kasus rekursi lurus yang tidak diikat: namun Anda dapat menulis ulang untuk memungkinkan hal semacam itu tunduk pada fakta bahwa frame stack digunakan kembali melalui panggilan y. ya mungkin perlu melihat IL, tidak ada pengalaman dalam hal itu.
nicolas
5
Saya membuat akun dan mendapat 50 poin hanya untuk berkomentar di sini. Pertanyaan ini sangat menarik. Saya pikir itu sepenuhnya tergantung pada f. Kita dapat melihat bahwa ydapat mengeklik fdengan pukulan (y f), tetapi seperti yang Anda katakan fmungkin memiliki beberapa operasi yang tertunda. Saya pikir itu akan menarik untuk mengetahui apakah ada kombinator terpisah yang lebih ramah tailcall. Saya ingin tahu apakah pertanyaan ini akan mendapatkan perhatian yang lebih baik di situs CS Stackexchange?
John Cartwright

Jawaban:

4

Apakah Anda tahu cara apa pun yang bisa dilakukan keduanya?

Tidak, dan dengan alasan yang bagus, IMHO.

Y-combinator adalah konstruksi teoretis dan hanya diperlukan untuk membuat kalkulus lambda turing lengkap (ingat, tidak ada loop dalam kalkulus lambda, juga tidak lambda memiliki nama yang bisa kita gunakan untuk rekursi).

Dengan demikian, kombinator Y benar-benar menarik.

Tapi : Tidak ada yang benar-benar menggunakan Y-combinator untuk rekursi aktual! (Kecuali mungkin untuk bersenang-senang, untuk menunjukkan bahwa itu benar-benar berhasil.)

Optimasi tail-call, OTOH, adalah, seperti namanya, sebuah optimasi. Ia tidak menambahkan apa pun pada ekspresi bahasa, hanya karena pertimbangan praktis seperti ruang stack dan kinerja kode rekursif yang kami pedulikan.

Jadi pertanyaan Anda seperti: Apakah ada dukungan perangkat keras untuk pengurangan beta? (Pengurangan beta adalah bagaimana ekspresi lambda dikurangi, Anda tahu.) Tapi tidak ada bahasa fungsional (sejauh yang saya ketahui) mengkompilasi kode sumbernya ke representasi ekspresi lambda yang akan dikurangi beta saat runtime.

Ingo
sumber
2
Y-combinator seperti menarik kembali simpul yang terus dilepas setelah digunakan. Kebanyakan sistem memotong jalan ini dan mengikat simpul pada tingkat meta sehingga tidak perlu ditarik kembali.
Dan D.
1
Untuk paragraf terakhir, pertimbangkan Haskell yang pada intinya menggunakan pengurangan grafik untuk melakukan evaluasi malas. Tetapi favorit saya adalah reduksi optimal yang selalu mengambil jalan di kisi Gereja-Rosser dengan pengurangan paling sedikit ke bentuk normal penuh. Seperti yang muncul di Asperti dan Guerrini, Implementasi Optimal Bahasa Pemrograman Fungsional . Lihat juga BOHM 1.1 .
Dan D.
@DanD. Terima kasih atas tautannya, saya akan mencobanya nanti di peramban sadar postscript. Tentu ada sesuatu untuk dipelajari untuk saya. Tapi, apakah Anda yakin bahwa haskell yang dikompilasi tidak mengurangi grafik? Saya meragukan ini.
Ingo
1
Sebenarnya itu memang menggunakan pengurangan grafik: "GHC mengkompilasi ke mesin G-tagless tanpa tanda (STG). Ini adalah mesin pengurangan grafik nosional (yaitu, mesin virtual yang melakukan pengurangan grafik seperti dijelaskan di atas)." Dari ... Untuk lebih lanjut tentang mesin STG, lihat Mengimplementasikan bahasa fungsional malas Simon Peyton Jones pada perangkat keras saham: mesin G-Spinless Tagless .
Dan D.
@DanD. dalam artikel yang sama yang Anda tautkan, terbaca lebih jauh bahwa GHC "melakukan sejumlah optimisasi pada representasi itu, sebelum akhirnya mengkompilasinya menjadi kode mesin nyata (mungkin melalui C menggunakan GCC)."
Ingo
0

Saya tidak sepenuhnya yakin tentang jawaban ini, tetapi itu adalah yang terbaik yang bisa saya dapatkan.

Combinator y pada dasarnya malas, dalam bahasa yang ketat kemalasan harus ditambahkan secara manual melalui lambda tambahan.

let rec y f x = f (y f) x

Definisi Anda sepertinya membutuhkan kemalasan untuk mengakhiri, atau (y f)argumen tidak akan pernah selesai mengevaluasi dan harus mengevaluasi apakah menggunakannya atau tidak f. TOC dalam konteks lazy lebih rumit, dan lebih jauh lagi hasil (y f)komposisi fungsi berulang tanpa aplikasi denganx . Saya tidak yakin ini perlu mengambil O (n) memori di mana n adalah kedalaman rekursi, tapi saya ragu Anda bisa mencapai jenis TOC yang sama seperti mungkin dengan sesuatu seperti (beralih ke Haskell karena saya tidak benar-benar tahu F #)

length acc []    = acc
length acc (a:b) = length (acc+1) b 

Jika Anda belum menyadarinya, perbedaan antara foldldan foldl'di Haskell dapat memberi petunjuk pada situasi ini. foldlditulis seperti yang akan dilakukan dalam bahasa yang bersemangat. Tetapi alih-alih menjadi TOC, ini sebenarnya lebih buruk daripada foldrkarena akulator menyimpan potensi besar yang tidak dapat dievaluasi sebagian. (Ini terkait dengan mengapa foldl dan foldl 'tidak bekerja pada daftar yang tak terbatas.) Jadi dalam versi yang lebih baru dari Haskell, foldl'ditambahkan yang memaksa evaluasi akumulator setiap kali fungsi berulang untuk memastikan tidak ada pukulan besar yang dibuat. Saya yakin http://www.haskell.org/haskellwiki/Foldr_Foldl_Foldl%27 dapat menjelaskan ini lebih baik daripada saya.

Ericson2314
sumber