Saya merasa bahwa efek samping adalah fenomena alam. Tapi itu seperti tabu dalam bahasa fungsional. Apa alasannya?
Pertanyaan saya khusus untuk gaya pemrograman fungsional. Tidak semua bahasa / paradigma pemrograman.
Saya merasa bahwa efek samping adalah fenomena alam. Tapi itu seperti tabu dalam bahasa fungsional. Apa alasannya?
Pertanyaan saya khusus untuk gaya pemrograman fungsional. Tidak semua bahasa / paradigma pemrograman.
Jawaban:
Menulis fungsi / metode Anda tanpa efek samping - jadi itu adalah fungsi murni - membuatnya lebih mudah untuk beralasan tentang kebenaran program Anda.
Itu juga membuatnya mudah untuk menyusun fungsi-fungsi itu untuk menciptakan perilaku baru.
Ini juga memungkinkan optimasi tertentu, di mana kompiler dapat misalnya memoise hasil fungsi, atau menggunakan Penghapusan Subekspresi Umum.
Sunting: atas permintaan Benjol: Karena banyak negara Anda disimpan dalam tumpukan (aliran data, bukan aliran kontrol, seperti yang disebut Jonas di sini ), Anda dapat memparalelkan atau menyusun ulang pelaksanaan bagian-bagian perhitungan Anda yang independen dari satu sama lain. Anda dapat dengan mudah menemukan bagian-bagian independen itu karena satu bagian tidak memberikan masukan kepada yang lain.
Dalam lingkungan dengan debugger yang memungkinkan Anda memutar kembali tumpukan dan melanjutkan komputasi (seperti Smalltalk), memiliki fungsi murni berarti Anda dapat dengan mudah melihat bagaimana suatu perubahan nilai, karena status sebelumnya tersedia untuk diperiksa. Dalam perhitungan yang berat mutasi, kecuali jika Anda secara eksplisit menambahkan tindakan lakukan / batalkan ke struktur atau algoritma Anda, Anda tidak dapat melihat sejarah perhitungan. (Ini terkait dengan paragraf pertama: menulis fungsi murni membuatnya lebih mudah untuk memeriksa kebenaran program Anda.)
Dari artikel tentang pemrograman Fungsional :
Dalam praktiknya, aplikasi perlu memiliki beberapa efek samping. Simon Peyton-Jones, kontributor utama untuk bahasa pemrograman fungsional Haskell, mengatakan hal berikut: "Pada akhirnya, setiap program harus memanipulasi keadaan. Program yang tidak memiliki efek samping apa pun adalah semacam kotak hitam. Yang bisa Anda katakan hanyalah bahwa kotak semakin panas. " ( http://oscon.blip.tv/file/324976 ) Kuncinya adalah untuk membatasi efek samping, mengidentifikasi dengan jelas, dan menghindari menyebarkannya di seluruh kode.
Anda salah, pemrograman fungsional mempromosikan efek samping yang terbatas untuk membuat program mudah dipahami dan dioptimalkan. Bahkan Haskell memungkinkan Anda menulis ke file.
Pada dasarnya apa yang saya katakan adalah bahwa pemrogram fungsional tidak menganggap efek samping itu jahat, mereka hanya berpikir membatasi penggunaan efek samping itu baik. Saya tahu itu mungkin tampak seperti perbedaan yang sederhana tetapi membuat semua perbedaan.
readFile
sedang dilakukan adalah mendefinisikan urutan tindakan. urutan ini secara fungsional murni dan seperti pohon abstrak yang menggambarkan APA yang harus dilakukan. efek samping kotor yang sebenarnya kemudian dilakukan oleh runtime.
Beberapa catatan:
Fungsi tanpa efek samping dapat dengan mudah dieksekusi secara paralel, sedangkan fungsi dengan efek samping biasanya memerlukan semacam sinkronisasi.
Fungsi tanpa efek samping memungkinkan pengoptimalan yang lebih agresif (misalnya dengan menggunakan cache hasil secara transparan), karena selama kita mendapatkan hasil yang benar, bahkan tidak masalah apakah fungsi itu benar - benar dieksekusi atau tidak.
deterministic
klausa untuk fungsi tanpa efek samping, sehingga mereka tidak dieksekusi lebih sering daripada yang diperlukan.
deterministic
klausul hanya kata kunci yang memberitahu compiler bahwa ini adalah fungsi deterministik, sebanding dengan bagaimana final
kata kunci di Jawa memberitahu compiler bahwa variabel tidak dapat berubah.
Saya terutama bekerja dalam kode fungsional sekarang, dan dari perspektif itu tampak sangat jelas. Efek samping membuat besar beban mental pada programmer mencoba membaca dan memahami kode. Anda tidak menyadari beban itu sampai Anda bebas darinya untuk sementara waktu, lalu tiba-tiba harus membaca kode dengan efek samping lagi.
Pertimbangkan contoh sederhana ini:
val foo = 42
// Several lines of code you don't really care about, but that contain a
// lot of function calls that use foo and may or may not change its value
// by side effect.
// Code you are troubleshooting
// What's the expected value of foo here?
Dalam bahasa fungsional, saya tahu itu foo
masih 42. Saya bahkan tidak perlu melihat kode di antaranya, apalagi memahaminya, atau melihat implementasi dari fungsi yang dipanggil.
Semua hal tentang konkurensi dan paralelisasi dan optimisasi itu bagus, tetapi itulah yang dimasukkan oleh para ilmuwan komputer pada brosur. Tidak perlu bertanya-tanya siapa yang mengubah variabel Anda dan kapan hal yang benar-benar saya nikmati dalam latihan sehari-hari.
Sedikit bahasa yang tidak memungkinkan menyebabkan efek samping. Bahasa yang sepenuhnya bebas efek samping akan sangat sulit (hampir tidak mungkin) untuk digunakan, kecuali dalam kapasitas yang sangat terbatas.
Mengapa efek samping dianggap jahat?
Karena mereka membuatnya jauh lebih sulit untuk berpikir tentang apa yang dilakukan suatu program, dan untuk membuktikan bahwa itu melakukan apa yang Anda harapkan.
Pada tingkat yang sangat tinggi, bayangkan menguji seluruh situs web 3-tier dengan hanya pengujian kotak hitam. Tentu, itu bisa dilakukan, tergantung pada skalanya. Tetapi tentu saja ada banyak duplikasi yang terjadi. Dan jika ada yang bug (yang berhubungan dengan efek samping), maka Anda bisa berpotensi merusak seluruh sistem untuk pengujian lebih lanjut, sampai bug ini didiagnosis dan tetap, dan memperbaiki dikerahkan untuk lingkungan pengujian.
Manfaat
Sekarang, turunkan itu. Jika Anda cukup pandai menulis kode bebas efek samping, seberapa cepat Anda akan mempertimbangkan apa yang dilakukan oleh beberapa kode yang ada? Seberapa cepat Anda bisa menulis unit test? Seberapa yakin Anda akan merasa bahwa kode tanpa efek samping dijamin bebas bug, dan bahwa pengguna dapat membatasi eksposur mereka untuk bug itu tidak memiliki?
Jika kode tidak memiliki efek samping, kompiler juga dapat memiliki optimasi tambahan yang dapat dilakukan. Mungkin lebih mudah untuk mengimplementasikan optimasi tersebut. Mungkin jauh lebih mudah untuk bahkan membuat konsep optimasi untuk kode bebas efek samping, yang berarti bahwa vendor kompiler Anda mungkin menerapkan optimasi yang sulit untuk mustahil dalam kode dengan efek samping.
Concurrency juga secara drastis lebih mudah untuk diimplementasikan, untuk secara otomatis menghasilkan, dan untuk mengoptimalkan ketika kode tidak memiliki efek samping. Ini karena semua bagian dapat dievaluasi dengan aman dalam urutan apa pun. Mengizinkan pemrogram untuk menulis kode yang sangat konkuren secara luas dianggap sebagai tantangan besar berikutnya yang perlu ditangani oleh Ilmu Komputer, dan salah satu dari sedikit lindung nilai yang tersisa terhadap Hukum Moore .
Efek samping seperti "kebocoran" dalam kode Anda yang perlu ditangani nanti, baik oleh Anda atau rekan kerja yang tidak curiga.
Bahasa fungsional menghindari variabel keadaan dan data yang bisa berubah sebagai cara membuat kode kurang tergantung konteks dan lebih modular. Modularitas memastikan bahwa pekerjaan satu pengembang tidak akan memengaruhi / merusak pekerjaan pengembang lain.
Penskalaan laju pengembangan dengan ukuran tim, adalah "cawan suci" pengembangan perangkat lunak saat ini. Ketika bekerja dengan programmer lain, beberapa hal sama pentingnya dengan modularitas. Bahkan efek samping logis yang paling sederhana pun membuat kolaborasi menjadi sangat sulit.
Nah, IMHO, ini sangat munafik. Tidak ada yang suka efek samping, tetapi semua orang membutuhkannya.
Apa yang sangat berbahaya tentang efek samping adalah bahwa jika Anda memanggil suatu fungsi, maka ini mungkin memiliki efek tidak hanya pada cara fungsi berperilaku ketika dipanggil lain kali, tetapi mungkin memiliki efek ini pada fungsi lain. Dengan demikian efek samping menimbulkan perilaku yang tidak terduga dan ketergantungan nontrivial.
Pemrograman paradigma seperti OO dan fungsional keduanya mengatasi masalah ini. OO mengurangi masalah dengan memaksakan pemisahan kekhawatiran. Ini berarti status aplikasi, yang terdiri dari banyak data yang dapat diubah, dienkapsulasi menjadi objek, yang masing-masing bertanggung jawab untuk mempertahankan statusnya sendiri saja. Dengan cara ini risiko ketergantungan berkurang dan masalah jauh lebih terisolasi dan lebih mudah dilacak.
Pemrograman fungsional mengambil pendekatan yang jauh lebih radikal, di mana status aplikasi tidak dapat diubah dari perspektif pemrogram. Ini adalah ide yang bagus, tetapi menjadikan bahasa itu sendiri tidak berguna. Mengapa? Karena SETIAP I / O-operasi memiliki efek samping. Segera setelah Anda membaca dari aliran input apa pun, status aplikasi Anda kemungkinan akan berubah, karena kali berikutnya Anda menjalankan fungsi yang sama, hasilnya kemungkinan akan berbeda. Anda mungkin membaca data yang berbeda, atau - juga kemungkinan - operasi mungkin gagal. Hal yang sama berlaku untuk output. Bahkan keluaran adalah operasi dengan efek samping. Ini bukan apa-apa yang sering Anda sadari saat ini, tetapi bayangkan Anda hanya memiliki 20K untuk output Anda dan jika Anda output lagi, aplikasi Anda macet karena Anda kehabisan ruang disk atau apa pun.
Jadi ya, efek sampingnya buruk dan berbahaya dari sudut pandang seorang programmer. Sebagian besar bug berasal dari cara bagian-bagian tertentu dari keadaan aplikasi yang saling terkait dalam cara yang hampir tidak jelas, melalui efek samping yang tidak dipertimbangkan dan seringkali tidak perlu. Dari perspektif pengguna, efek samping adalah titik menggunakan komputer. Mereka tidak peduli dengan apa yang terjadi di dalam atau bagaimana hal itu diatur. Mereka melakukan sesuatu dan berharap komputer untuk MENGUBAH sesuai.
Setiap efek samping memperkenalkan parameter input / output ekstra yang harus diperhitungkan saat pengujian.
Ini membuat validasi kode jauh lebih kompleks karena lingkungan tidak dapat dibatasi hanya pada kode yang divalidasi, tetapi harus membawa sebagian atau semua lingkungan sekitarnya (global yang diperbarui tinggal dalam kode itu di sana, yang pada gilirannya tergantung pada kode, yang pada gilirannya tergantung pada hidup di dalam server Java EE lengkap ....)
Dengan mencoba menghindari efek samping, Anda membatasi jumlah eksternalisme yang diperlukan untuk menjalankan kode.
Dalam pengalaman saya desain yang baik dalam pemrograman Object Orientated mengamanatkan penggunaan fungsi yang memiliki efek samping.
Misalnya, ambil aplikasi desktop UI dasar. Saya mungkin memiliki program yang sedang berjalan yang memiliki tumpukan objek grafik yang mewakili keadaan saat ini dari model domain program saya. Pesan tiba ke objek dalam grafik itu (misalnya, melalui metode panggilan dipanggil dari pengontrol lapisan UI). Grafik objek (model domain) pada heap dimodifikasi sebagai respons terhadap pesan. Pengamat model diberitahu tentang perubahan, UI dan mungkin sumber daya lain dimodifikasi.
Jauh dari kejahatan, pengaturan yang benar dari efek samping heap-modifying dan modifying screen ini adalah inti dari desain OO (dalam hal ini pola MVC).
Tentu saja, itu tidak berarti bahwa metode Anda harus memiliki efek samping arbiter. Dan fungsi bebas efek samping memang memiliki tempat dalam meningkatkan keterbacaan dan kadang-kadang kinerja, kode Anda.
Seperti yang ditunjukkan oleh pertanyaan di atas, bahasa fungsional tidak terlalu mencegah kode dari memiliki efek samping seperti menyediakan alat untuk mengelola efek samping apa yang dapat terjadi dalam bagian kode tertentu dan kapan.
Ini ternyata memiliki konsekuensi yang sangat menarik. Pertama, dan yang paling jelas, ada banyak hal yang dapat Anda lakukan dengan kode bebas efek samping, yang telah dijelaskan. Tetapi ada hal-hal lain yang dapat kita lakukan juga, bahkan ketika bekerja dengan kode yang memiliki efek samping:
Dalam basis kode yang kompleks, interaksi kompleks dari efek samping adalah hal paling sulit yang saya temukan untuk dipikirkan. Saya hanya bisa berbicara secara pribadi dengan cara otak saya bekerja. Efek samping dan keadaan persisten dan input bermutasi dan sebagainya membuat saya harus berpikir tentang "kapan" dan "di mana" sesuatu terjadi dengan alasan tentang kebenaran, bukan hanya "apa" yang terjadi di setiap fungsi individu.
Saya tidak bisa hanya fokus pada "apa". Saya tidak dapat menyimpulkan setelah benar-benar menguji suatu fungsi yang menyebabkan efek samping yang akan menyebarkan udara keandalan di seluruh kode yang menggunakannya, karena penelepon mungkin masih menyalahgunakannya dengan memanggilnya pada waktu yang salah, dari utas yang salah, di salah memesan. Sementara itu fungsi yang tidak menyebabkan efek samping dan hanya mengembalikan output baru yang diberikan input (tanpa menyentuh input) sangat tidak mungkin untuk disalahgunakan dengan cara ini.
Tapi saya tipe pragmatis, saya pikir, atau setidaknya mencoba untuk menjadi, dan saya tidak berpikir kita harus menghapus semua efek samping seminimal mungkin untuk alasan tentang kebenaran kode kita (paling tidak paling tidak Saya akan menemukan ini sangat sulit dilakukan dalam bahasa seperti C). Di mana saya merasa sangat sulit untuk beralasan tentang kebenaran adalah ketika kita memiliki kombinasi aliran kontrol yang kompleks dan efek samping.
Alur kontrol kompleks kepada saya adalah yang bersifat seperti grafik, sering rekursif atau rekursif (antrian acara, misalnya, yang tidak secara langsung menyebut peristiwa secara rekursif tetapi bersifat "rekursif-suka"), mungkin melakukan hal-hal dalam proses melintasi struktur grafik tertaut yang sebenarnya, atau memproses antrian peristiwa non-homogen yang berisi campuran eklektik peristiwa untuk diproses membawa kita ke semua jenis bagian basis kode yang berbeda dan semua memicu efek samping yang berbeda. Jika Anda mencoba untuk menggambar semua tempat yang pada akhirnya Anda akan berakhir dalam kode, itu akan menyerupai grafik yang kompleks dan berpotensi dengan node dalam grafik yang Anda tidak pernah harapkan akan ada di sana pada saat itu, dan mengingat bahwa mereka semua menyebabkan efek samping,
Bahasa fungsional dapat memiliki aliran kontrol yang sangat kompleks dan rekursif, tetapi hasilnya sangat mudah dipahami dalam hal kebenaran karena tidak ada segala macam efek samping eklektik yang terjadi dalam proses. Hanya ketika aliran kontrol yang kompleks memenuhi efek samping eklektik, saya merasa sakit kepala untuk mencoba memahami keseluruhan dari apa yang terjadi dan apakah itu akan selalu melakukan hal yang benar.
Jadi ketika saya memiliki kasus-kasus itu, saya sering merasa sangat sulit, jika bukan tidak mungkin, merasa sangat yakin tentang kebenaran kode tersebut, apalagi sangat yakin bahwa saya dapat membuat perubahan pada kode tersebut tanpa tersandung pada sesuatu yang tidak terduga. Jadi solusinya bagi saya ada yang menyederhanakan aliran kontrol atau meminimalkan / menyatukan efek samping (dengan menyatukan, maksud saya seperti hanya menyebabkan satu jenis efek samping ke banyak hal selama fase tertentu dalam sistem, bukan dua atau tiga atau lusin). Saya perlu salah satu dari dua hal itu terjadi untuk memungkinkan otak bodoh saya merasa yakin tentang kebenaran kode yang ada dan kebenaran perubahan yang saya perkenalkan. Sangat mudah untuk merasa yakin tentang kebenaran kode yang memperkenalkan efek samping jika efek sampingnya seragam dan sederhana bersama dengan aliran kontrol, seperti:
for each pixel in an image:
make it red
Sangat mudah untuk alasan tentang kebenaran kode tersebut, tetapi terutama karena efek sampingnya sangat seragam dan aliran kontrolnya sangat sederhana. Tetapi katakanlah kita memiliki kode seperti ini:
for each vertex to remove in a mesh:
start removing vertex from connected edges():
start removing connected edges from connected faces():
rebuild connected faces excluding edges to remove():
if face has less than 3 edges:
remove face
remove edge
remove vertex
Maka ini adalah pseudocode yang terlalu sederhana yang biasanya melibatkan lebih banyak fungsi dan loop bersarang dan lebih banyak hal yang harus dilakukan (memperbarui beberapa peta tekstur, bobot tulang, keadaan pemilihan, dll), tetapi bahkan kodesemu membuatnya sangat sulit untuk alasan tentang kebenaran karena interaksi aliran kontrol seperti grafik yang kompleks dan efek samping yang terjadi. Jadi satu strategi untuk menyederhanakan itu adalah menunda pemrosesan dan hanya fokus pada satu jenis efek samping pada suatu waktu:
for each vertex to remove:
mark connected edges
for each marked edge:
mark connected faces
for each marked face:
remove marked edges from face
if num_edges < 3:
remove face
for each marked edge:
remove edge
for each vertex to remove:
remove vertex
... sesuatu untuk efek ini sebagai satu iterasi penyederhanaan. Itu berarti kita melewati data beberapa kali yang pasti menimbulkan biaya komputasi, tetapi kita sering menemukan kita dapat multithread kode yang dihasilkan lebih mudah, sekarang efek samping dan aliran kontrol telah mengambil sifat yang seragam dan lebih sederhana ini. Lebih jauh, setiap loop dapat dibuat lebih ramah-cache daripada melintasi grafik yang terhubung dan menyebabkan efek samping saat berjalan (mis: gunakan bit paralel yang disetel untuk menandai apa yang perlu dilalui sehingga kita dapat melakukan pass yang ditangguhkan dalam urutan berurutan yang diurutkan) menggunakan bitmasks dan FFS). Tetapi yang paling penting, saya menemukan versi kedua jauh lebih mudah untuk dipikirkan dalam hal kebenaran serta perubahan tanpa menyebabkan bug. Yang seperti itu'
Lagi pula, kita perlu efek samping terjadi di beberapa titik, atau kita hanya akan memiliki fungsi yang menghasilkan data tanpa tempat. Seringkali kita perlu merekam sesuatu ke file, menampilkan sesuatu ke layar, mengirim data melalui soket, sesuatu seperti ini, dan semua ini adalah efek samping. Tetapi kita pasti dapat mengurangi jumlah efek samping berlebihan yang terjadi, dan juga mengurangi jumlah efek samping yang terjadi ketika kontrol mengalir sangat rumit, dan saya pikir akan jauh lebih mudah untuk menghindari bug jika kita melakukannya.
Itu tidak jahat. Pendapat saya, perlu untuk membedakan dua jenis fungsi - dengan efek samping dan tanpa. Fungsi tanpa efek samping: - mengembalikan selalu sama dengan argumen yang sama, jadi misalnya fungsi tersebut tanpa argumen tidak masuk akal. - Itu juga berarti, bahwa urutan fungsi yang disebut beberapa fungsi tidak dimainkan - harus dapat dijalankan dan dapat di-debug hanya sendiri (!), Tanpa kode lain. Dan sekarang, lol, lihat apa yang dibuat JUnit. Sebuah fungsi dengan efek samping: - memiliki semacam "kebocoran", yang dapat disorot secara otomatis - sangat penting dengan men-debug dan mencari kesalahan, yang umumnya disebabkan oleh efek samping. - Setiap fungsi dengan efek samping juga memiliki "bagian" dari dirinya sendiri tanpa efek samping, yang juga dapat dipisahkan secara otomatis. Jadi kejahatan adalah efek sampingnya,