Saya membaca "The Why of Y" dari Richard P. Gabriel . Ini adalah artikel yang mudah dibaca tentang Y combinator, yang sangat jarang. Artikel dimulai dengan definisi rekursif dari fungsi faktorial:
(letrec ((f (lambda (n)
(if (< n 2) 1 (* n (f (- n 1)))))))
(f 10))
Dan menjelaskan bahwa letrec
dapat didefinisikan dengan efek samping:
(let ((f #f))
(set! f (lambda (n)
(if (< n 2) 1 (* n (f (- n 1))))))
(f 10))
Dan sisa artikel menjelaskan, bahwa dimungkinkan juga untuk mendefinisikan letrec
dengan kombinator Y:
(define (Y f)
(let ((g (lambda (h)
(lambda (x)
((f (h h)) x)))))
(g g)))
(let ((f (Y (lambda (fact)
(lambda (n)
(if (< n 2) 1 (* n (fact (- n 1)))))))))
(f 10))
Jelas ini jauh lebih rumit daripada versi dengan efek sampingnya. Alasan mengapa lebih disukai untuk memilih Y combinator daripada efek samping diberikan hanya dengan pernyataan:
Lebih mudah untuk berpikir tentang bahasa pemrograman dan program yang tidak memiliki efek samping.
Ini tidak dijelaskan lebih lanjut. Saya mencoba mencari penjelasan.
optimization
side-effect
ceving
sumber
sumber
Jawaban:
Jelas, Anda dapat menemukan contoh fungsi murni yang sangat sulit dibaca yang melakukan perhitungan yang sama dengan fungsi dengan efek samping yang jauh lebih mudah dibaca. Terutama ketika Anda menggunakan transformasi mekanis seperti Y-combinator untuk sampai pada solusi. Bukan itu yang dimaksud dengan "lebih mudah untuk berpikir tentang."
Alasan mengapa lebih mudah untuk beralasan tentang fungsi tanpa efek samping adalah Anda hanya perlu khawatir dengan input dan output. Dengan efek samping, Anda juga harus khawatir tentang berapa kali fungsi dipanggil, urutan apa fungsi itu dipanggil, data apa yang dibuat dalam fungsi, data apa yang dibagikan, dan data apa yang disalin. Dan semua informasi itu untuk fungsi apa pun yang dapat dipanggil internal ke fungsi yang Anda panggil, dan secara internal rekursif untuk fungsi-fungsi itu, dan sebagainya.
Efek ini jauh lebih mudah dilihat dalam kode produksi dengan beberapa lapisan daripada pada fungsi contoh mainan. Sebagian besar itu berarti Anda dapat lebih mengandalkan hanya pada tanda tangan jenis fungsi. Anda benar-benar memperhatikan beban efek samping jika Anda melakukan pemrograman fungsional murni untuk sementara waktu kemudian kembali ke sana.
sumber
Properti bahasa yang menarik tanpa efek samping adalah memperkenalkan paralelisme, konkurensi, atau asinkron tidak dapat mengubah arti program. Itu bisa membuatnya lebih cepat. Atau itu bisa membuatnya lebih lambat. Tapi itu tidak bisa salah.
Ini membuatnya sepele untuk secara otomatis memparalelkan program. Jadi sepele, pada kenyataannya, bahwa Anda biasanya berakhir dengan terlalu banyak paralelisme! Tim GHC bereksperimen dengan paralelisasi otomatis. Mereka menemukan bahwa bahkan program sederhana dapat diuraikan menjadi ratusan, bahkan ribuan utas. Overhead dari semua utas tersebut akan membanjiri potensi percepatan oleh beberapa urutan besarnya.
Jadi, untuk paralelisasi otomatis dari program fungsional, masalahnya menjadi "bagaimana Anda mengelompokkan operasi atom kecil menjadi ukuran yang berguna dari potongan paralel", sebagai lawan dari program yang tidak murni, di mana masalahnya adalah "bagaimana Anda memecah operasi monolitik besar menjadi berguna ukuran potongan paralel ". Hal yang menyenangkan tentang ini adalah bahwa yang pertama dapat dilakukan secara heuristik (ingat: jika Anda salah, hal terburuk yang dapat terjadi adalah bahwa program berjalan sedikit lebih lambat daripada yang seharusnya), sedangkan yang terakhir setara dengan menyelesaikan Menghentikan Masalah (dalam kasus umum), dan jika Anda salah, program Anda hanya akan crash (jika Anda beruntung!) Atau mengembalikan hasil yang salah secara halus (dalam kasus terburuk).
sumber
Bahasa dengan efek samping menggunakan analisis aliasing untuk melihat apakah lokasi memori mungkin perlu dimuat ulang setelah pemanggilan fungsi. Seberapa konservatif analisis ini tergantung pada bahasanya.
Untuk C, ini harus sangat konservatif, karena bahasa tidak aman.
Untuk Java dan C # ini tidak harus sekonservatif karena peningkatan keamanan jenisnya.
Menjadi terlalu konservatif mencegah optimalisasi pemuatan.
Analisis seperti itu tidak perlu (atau sepele tergantung pada bagaimana Anda melihatnya) dalam bahasa tanpa efek samping.
sumber
Selalu ada optimisasi untuk mengambil keuntungan dari asumsi apa pun yang Anda berikan. Operasi penataan ulang muncul di pikiran.
Satu contoh lucu yang muncul di benak sebenarnya muncul dalam beberapa bahasa majelis lama. Secara khusus MIPS memiliki aturan bahwa instruksi setelah lompatan bersyarat dieksekusi, terlepas dari cabang mana yang diambil. Jika Anda tidak menginginkan ini, Anda meletakkan NOP setelah melompat. Ini dilakukan karena cara pipa MIPS terstruktur. Ada warung 1 siklus alami yang dibangun ke dalam eksekusi lompat bersyarat, jadi Anda mungkin juga melakukan sesuatu yang berguna dengan siklus itu!
Compiler akan sering mencari operasi yang perlu dilakukan pada kedua cabang dan geser ke dalam slot itu, untuk menghasilkan kinerja yang lebih sedikit. Namun, jika kompiler tidak dapat melakukan itu, tetapi dapat menunjukkan bahwa tidak ada efek samping pada operasi, kompiler secara oportunis dapat memasukkannya ke tempat itu. Jadi, pada satu jalur, kode akan menjalankan satu instruksi lebih cepat. Di jalur lain, tidak ada salahnya dilakukan.
sumber
"letrec dapat didefinisikan dengan efek samping ..." Saya tidak melihat efek samping dalam definisi Anda. Ya, ini menggunakan
set!
cara khas untuk menghasilkan efek samping dalam Skema, tetapi dalam kasus ini tidak ada efek samping - karenaf
murni lokal, tidak dapat dirujuk oleh fungsi apa pun selain lokal. Oleh karena itu bukan efek samping yang terlihat dari ruang lingkup eksternal. Apa yang dilakukan kode ini adalah meretas batasan dalam cakupan Skema yang secara default tidak mengizinkan definisi lambda merujuk pada dirinya sendiri.Beberapa bahasa lain memiliki deklarasi di mana ekspresi yang digunakan untuk menghasilkan nilai konstanta dapat merujuk ke konstanta itu sendiri. Dalam bahasa seperti itu, definisi ekuivalen yang tepat dapat digunakan, tetapi jelas ini tidak menghasilkan efek samping, karena hanya konstanta yang digunakan. Lihat, misalnya, program Haskell yang setara ini:
(yang mengevaluasi hingga 120).
Ini jelas tidak memiliki efek samping (seperti agar fungsi di Haskell memiliki efek samping, ia harus mengembalikan hasilnya dengan membungkus Monad, tetapi tipe yang dikembalikan di sini adalah tipe numerik biasa), tetapi secara struktural identik dengan kode kode yang Anda kutip.
sumber
let
bisa mengembalikan fungsi lokal.Itu adalah sesuatu yang melekat pada banyak dari kita yang telah mendebug basis kode besar tetapi Anda harus berurusan dengan skala yang cukup besar di tingkat pengawas untuk waktu yang cukup lama untuk menghargainya. Ini seperti memahami pentingnya berada di posisi dalam Poker. Pada awalnya itu tidak tampak seperti keuntungan yang berguna untuk pergi terakhir di akhir setiap belokan sampai Anda mencatat sejarah tangan sejuta tangan dan menyadari bahwa Anda memenangkan jauh lebih banyak uang di posisi daripada keluar.
Yang mengatakan, saya tidak setuju dengan gagasan bahwa perubahan ke variabel lokal adalah efek samping. Dari pandangan saya, suatu fungsi tidak menyebabkan efek samping jika tidak memodifikasi apa pun di luar ruang lingkupnya, bahwa apa pun yang disentuhnya dan dirusak tidak akan memengaruhi apa pun di bawah tumpukan panggilan atau memori atau sumber daya apa pun yang tidak diperoleh fungsi itu sendiri. .
Secara umum, hal tersulit untuk dipikirkan dalam basis kode yang rumit dan berskala besar yang tidak memiliki prosedur pengujian paling sempurna yang bisa dibayangkan adalah manajemen negara yang gigih, seperti semua perubahan pada objek granular dalam dunia video game saat Anda mengarungi puluhan ribuan fungsi saat mencoba mempersempit daftar tersangka yang tak berujung yang mana sebenarnya menyebabkan invarian seluruh sistem dilanggar ("ini seharusnya tidak pernah terjadi, siapa yang melakukannya?"). Jika tidak ada yang berubah di luar fungsi, maka tidak mungkin menyebabkan kerusakan pusat.
Tentu saja ini tidak mungkin dilakukan dalam semua kasus. Setiap aplikasi yang memperbarui basis data yang disimpan pada mesin yang berbeda, pada dasarnya, dirancang untuk menyebabkan efek samping, serta aplikasi apa pun yang dirancang untuk memuat dan menulis file. Tetapi ada banyak lagi yang dapat kita lakukan tanpa efek samping dalam banyak fungsi dan banyak program jika, misalnya, kita memiliki pustaka yang kaya akan struktur data yang tidak dapat diubah dan merangkul pola pikir ini lebih jauh.
Lucunya John Carmack telah melompat pada LISP dan ikut-ikutan meskipun mulai pada hari-hari C coding paling mikro-tuned. Saya menemukan diri saya melakukan hal yang serupa, meskipun saya masih menggunakan banyak C. Begitulah sifat pragmatis, saya pikir, yang telah menghabiskan bertahun-tahun melakukan debugging dan mencoba mencari alasan tentang sistem berskala besar yang kompleks secara keseluruhan dari tingkat pengawas. Bahkan yang secara mengejutkan kuat dan tanpa bug dalam jumlah besar masih dapat membuat Anda merasa tidak nyaman bahwa ada sesuatu yang salah yang bersembunyi di sudut jika ada banyak kondisi persisten kompleks yang dimodifikasi di antara grafik panggilan fungsi yang saling terhubung paling kompleks di antara jutaan baris kode. Bahkan jika setiap antarmuka diuji dengan unit test dan semua lulus, ada
Dalam praktiknya saya sering menemukan pemrograman fungsional membuatnya lebih sulit untuk memahami suatu fungsi. Itu masih memutar otak saya menjadi tikungan dan simpul, terutama dengan logika rekursif yang kompleks. Tetapi semua overhead intelektual yang terkait dengan mencari tahu beberapa fungsi yang ditulis dalam bahasa fungsional dikerdilkan oleh sistem yang kompleks dengan status persisten yang diubah di puluhan ribu fungsi, di mana setiap fungsi yang menyebabkan efek samping menambah total kompleksitas penalaran tentang kebenaran seluruh sistem secara keseluruhan.
Perhatikan bahwa Anda tidak memerlukan bahasa fungsional murni untuk membuat fungsi menghindari efek samping. Status lokal yang diubah dalam fungsi tidak dihitung sebagai efek samping, seperti
for
variabel penghitung loop lokal ke fungsi tidak dihitung sebagai efek samping. Saya bahkan menulis kode C saat ini dengan tujuan menghindari efek samping bila mungkin dan telah membuat sendiri perpustakaan struktur data yang tidak dapat diubah dan aman yang dapat dimodifikasi sebagian sementara sisa data disalin dangkal, dan itu telah membantu saya banyak alasan tentang kebenaran sistem saya. Saya sangat tidak setuju dengan penulis dalam hal itu. Setidaknya dalam C dan C ++ dalam perangkat lunak mission-critical, suatu fungsi dapat didokumentasikan sebagai tidak memiliki efek samping jika tidak menyentuh apa pun yang dapat mempengaruhi dunia di luar fungsi.sumber