Mengapa fungsi di F # dan Ocaml (dan mungkin bahasa lain) tidak secara default rekursif?
Dengan kata lain, mengapa desainer bahasa memutuskan bahwa sebaiknya Anda secara eksplisit membuat Anda mengetik rec
dalam deklarasi seperti:
let rec foo ... = ...
dan tidak memberikan kemampuan rekursif fungsi secara default? Mengapa perlunya rec
konstruksi eksplisit ?
Jawaban:
Keturunan Prancis dan Inggris dari ML asli membuat pilihan yang berbeda dan pilihan mereka telah diwarisi selama beberapa dekade ke varian modern. Jadi ini hanya warisan, tetapi memengaruhi idiom dalam bahasa-bahasa ini.
Fungsi tidak rekursif secara default dalam keluarga bahasa CAML Prancis (termasuk OCaml). Pilihan ini memudahkan untuk mengganti definisi fungsi (dan variabel) yang digunakan
let
dalam bahasa tersebut karena Anda dapat merujuk ke definisi sebelumnya di dalam isi definisi baru. F # mewarisi sintaks ini dari OCaml.Misalnya, menggantikan fungsi
p
saat menghitung entropi Shannon dari suatu urutan di OCaml:Perhatikan bagaimana argumen
p
ke fungsi tingkat tinggishannon
digantikan oleh argumen lainp
di baris pertama badan dan kemudian argumen lain di baris badanp
kedua.Sebaliknya, cabang bahasa Inggris SML dari keluarga bahasa ML mengambil pilihan lain dan
fun
fungsi -bound SML bersifat rekursif secara default. Ketika sebagian besar definisi fungsi tidak memerlukan akses ke binding sebelumnya dari nama fungsinya, ini menghasilkan kode yang lebih sederhana. Namun, fungsi superceded dibuat untuk menggunakan nama yang berbeda (f1
,f2
dll.) Yang mengotori ruang lingkup dan memungkinkan untuk secara tidak sengaja memanggil "versi" yang salah dari suatu fungsi. Dan sekarang ada perbedaan antarafun
fungsi yang terikat secara implisit-rekursif dan fungsi yang terikat secara non-rekursifval
.Haskell memungkinkan untuk menyimpulkan dependensi di antara definisi dengan membatasinya menjadi murni. Hal ini membuat sampel mainan terlihat lebih sederhana tetapi harus dibayar mahal di tempat lain.
Perhatikan bahwa jawaban yang diberikan oleh Ganesh dan Eddie adalah pengalih perhatian. Mereka menjelaskan mengapa kelompok fungsi tidak dapat ditempatkan di dalam raksasa
let rec ... and ...
karena itu mempengaruhi ketika variabel tipe digeneralisasi. Ini tidak ada hubungannya denganrec
default di SML tetapi tidak di OCaml.sumber
Salah satu alasan penting untuk penggunaan eksplisit
rec
adalah berkaitan dengan inferensi tipe Hindley-Milner, yang mendasari semua bahasa pemrograman fungsional yang diketik secara statis (meskipun diubah dan diperluas dalam berbagai cara).Jika Anda memiliki definisi
let f x = x
, Anda mengharapkan definisi tersebut memiliki tipe'a -> 'a
dan dapat diterapkan pada'a
tipe yang berbeda pada titik yang berbeda. Tetapi sama halnya , jika Anda menulislet g x = (x + 1) + ...
, Anda akan berharapx
diperlakukan sebagaiint
bagian tubuh lainnyag
.Cara inferensi Hindley-Milner menangani perbedaan ini adalah melalui langkah generalisasi eksplisit . Pada titik-titik tertentu saat memproses program Anda, sistem tipe berhenti dan berkata "ok, jenis definisi ini akan digeneralisasi pada titik ini, sehingga ketika seseorang menggunakannya, variabel jenis bebas apa pun dalam tipenya akan baru dibuat , dan karenanya tidak akan mengganggu penggunaan lain dari definisi ini. "
Ternyata tempat yang masuk akal untuk melakukan generalisasi ini adalah setelah memeriksa sekumpulan fungsi yang saling rekursif. Lebih awal, dan Anda akan menggeneralisasi terlalu banyak, yang mengarah ke situasi di mana tipe sebenarnya bisa bertabrakan. Nanti, dan Anda akan menggeneralisasi terlalu sedikit, membuat definisi yang tidak dapat digunakan dengan beberapa jenis contoh.
Jadi, mengingat bahwa pemeriksa tipe perlu mengetahui tentang kumpulan definisi mana yang saling rekursif, apa yang dapat dilakukannya? Salah satu kemungkinannya adalah dengan hanya melakukan analisis ketergantungan pada semua definisi dalam ruang lingkup, dan menyusunnya kembali menjadi kelompok sekecil mungkin. Haskell sebenarnya melakukan ini, tetapi dalam bahasa seperti F # (dan OCaml dan SML) yang memiliki efek samping tak terbatas, ini adalah ide yang buruk karena mungkin menyusun ulang efek samping juga. Jadi sebaliknya ia meminta pengguna untuk secara eksplisit menandai definisi mana yang saling rekursif, dan dengan demikian dengan ekstensi di mana generalisasi harus terjadi.
sumber
rec
tetapi SML tidak merupakan contoh tandingan yang jelas. Jika jenis inferensi adalah masalah karena alasan yang Anda gambarkan, OCaml dan SML tidak dapat memilih solusi yang berbeda seperti yang mereka lakukan. Alasannya, tentu saja, yang Anda bicarakanand
untuk membuat Haskell relevan.Ada dua alasan utama mengapa ini adalah ide yang bagus:
Pertama, jika Anda mengaktifkan definisi rekursif maka Anda tidak dapat merujuk ke pengikatan sebelumnya dari nilai dengan nama yang sama. Ini sering kali merupakan idiom yang berguna ketika Anda melakukan sesuatu seperti memperluas modul yang sudah ada.
Kedua, nilai rekursif, dan terutama kumpulan nilai rekursif yang saling menguntungkan, jauh lebih sulit untuk dipikirkan kemudian adalah definisi yang diproses secara berurutan, setiap definisi baru dibangun di atas apa yang telah didefinisikan. Sangat menyenangkan ketika membaca kode seperti itu untuk mendapatkan jaminan bahwa, kecuali untuk definisi yang secara eksplisit ditandai sebagai rekursif, definisi baru hanya dapat merujuk ke definisi sebelumnya.
sumber
Beberapa tebakan:
let
tidak hanya digunakan untuk mengikat fungsi, tetapi juga nilai reguler lainnya. Sebagian besar bentuk nilai tidak boleh rekursif. Bentuk nilai rekursif tertentu diperbolehkan (misalnya fungsi, ekspresi malas, dll.), Jadi diperlukan sintaks eksplisit untuk menunjukkannya.let
membangun mirip denganlet
membangun di Lisp dan Scheme; yang non-rekursif. Adaletrec
konstruksi terpisah dalam Skema untuk rekursif marisumber
let rec xs = 0::ys and ys = 1::xs
.Diberikan ini:
Membandingkan:
Dengan ini:
Mantan mengubah
f
menerapkan ditetapkan sebelumnyaf
dengan hasil dari penerapang
untuka
. The mengubah terakhirf
loop selamanya menerapkang
untuka
, yang biasanya tidak apa yang Anda inginkan dalam varian ML.Meskipun demikian, ini adalah gaya desainer bahasa. Lakukan saja.
sumber
Sebagian besar darinya adalah memberikan programmer lebih banyak kendali atas kompleksitas cakupan lokal mereka. Spektrum
let
,let*
danlet rec
menawarkan tingkat daya dan biaya yang meningkat.let*
danlet rec
pada dasarnya adalah versi bersarang dari yang sederhanalet
, jadi menggunakan salah satunya lebih mahal. Grading ini memungkinkan Anda untuk mengatur optimalisasi program Anda karena Anda dapat memilih level mana yang Anda perlukan untuk tugas yang ada. Jika Anda tidak memerlukan rekursi atau kemampuan untuk merujuk ke binding sebelumnya, maka Anda dapat menggunakan metode biarkan sederhana untuk menghemat sedikit performa.Ini mirip dengan predikat kesetaraan bertingkat dalam Skema. (yaitu
eq?
,eqv?
danequal?
)sumber