Apakah bahasa pemrograman fungsional melarang efek samping?

10

Menurut Wikipedia, bahasa pemrograman fungsional , yaitu Deklaratif, mereka tidak mengizinkan efek samping. Pemrograman deklaratif secara umum, berupaya meminimalkan atau menghilangkan efek samping.

Juga, menurut Wikipedia, efek samping terkait dengan perubahan status. Jadi, bahasa pemrograman fungsional, dalam arti itu, mereka benar-benar menghilangkan efek samping, karena mereka tidak menyelamatkan negara.

Tetapi, di samping itu, efek samping memiliki definisi lain. Efek samping

memiliki interaksi yang dapat diamati dengan fungsi panggilannya atau dunia luar selain mengembalikan nilai. Misalnya, fungsi tertentu dapat mengubah variabel global atau variabel statis, memodifikasi salah satu argumennya, membuat pengecualian, menulis data ke tampilan atau file, membaca data, atau memanggil fungsi efek samping lainnya.

Dalam pengertian itu, bahasa pemrograman Fungsional sebenarnya memungkinkan efek samping, karena ada banyak sekali contoh fungsi yang mempengaruhi dunia luar mereka, memanggil fungsi lain, menimbulkan pengecualian, menulis dalam file dll.

Jadi, akhirnya, apakah bahasa pemrograman Fungsional memungkinkan efek samping atau tidak?

Atau, saya tidak mengerti apa yang memenuhi syarat sebagai "efek samping", jadi bahasa Imperatif mengizinkannya dan Deklaratif tidak. Menurut hal di atas dan apa yang saya dapatkan, tidak ada bahasa yang menghilangkan efek samping, jadi saya kehilangan sesuatu tentang efek samping, atau definisi Wikipedia salah luas.

codebot
sumber

Jawaban:

26

Pemrograman fungsional mencakup banyak teknik yang berbeda. Beberapa teknik baik-baik saja dengan efek samping. Tetapi satu aspek penting adalah penalaran yang sama : Jika saya memanggil suatu fungsi dengan nilai yang sama, saya selalu mendapatkan hasil yang sama. Jadi saya bisa mengganti panggilan fungsi dengan nilai balik, dan mendapatkan perilaku yang setara. Ini membuatnya lebih mudah untuk beralasan tentang program, terutama ketika debugging.

Jika fungsi tersebut memiliki efek samping, ini tidak cukup berlaku. Nilai kembali tidak setara dengan panggilan fungsi, karena nilai kembali tidak mengandung efek samping.

Solusinya adalah berhenti menggunakan efek samping dan menyandikan efek ini dalam nilai kembali . Bahasa yang berbeda memiliki sistem efek yang berbeda. Misalnya Haskell menggunakan monad untuk menyandikan efek tertentu seperti IO atau mutasi Negara. Bahasa C / C ++ / Rust memiliki sistem tipe yang dapat melarang mutasi beberapa nilai.

Dalam bahasa imperatif, suatu print("foo")fungsi akan mencetak sesuatu dan tidak mengembalikan apa pun. Dalam bahasa fungsional murni seperti Haskell, printfungsi juga mengambil objek yang mewakili keadaan dunia luar, dan mengembalikan objek baru yang mewakili negara setelah melakukan output ini. Sesuatu yang mirip dengan newState = print "foo" oldState. Saya dapat membuat banyak negara baru dari negara lama yang saya inginkan. Namun, hanya satu yang akan digunakan oleh fungsi utama. Jadi saya perlu mengurutkan status dari beberapa tindakan dengan merantai fungsi. Untuk mencetak foo bar, saya mungkin mengatakan sesuatu seperti print "bar" (print "foo" originalState).

Jika keadaan keluaran tidak digunakan, Haskell tidak melakukan tindakan yang mengarah ke keadaan itu, karena itu adalah bahasa malas. Sebaliknya, kemalasan ini hanya dimungkinkan karena semua efek dikodekan secara eksplisit sebagai nilai pengembalian.

Perhatikan bahwa Haskell adalah satu - satunya bahasa fungsional yang umum digunakan yang menggunakan rute ini. Bahasa fungsional lainnya termasuk keluarga Lisp, keluarga ML, dan bahasa fungsional yang lebih baru seperti Scala mencegah tetapi memungkinkan efek samping - mereka bisa disebut bahasa fungsional-imperatif.

Menggunakan efek samping untuk I / O mungkin baik-baik saja. Seringkali, I / O (selain logging) hanya dilakukan di batas luar sistem Anda. Tidak ada komunikasi eksternal yang terjadi dalam logika bisnis Anda. Maka dimungkinkan untuk menulis inti dari perangkat lunak Anda dalam gaya murni, sambil tetap melakukan I / O yang tidak murni di kulit terluar. Ini juga berarti bahwa inti dapat menjadi tanpa kewarganegaraan.

Statelessness memiliki sejumlah keunggulan praktis, seperti peningkatan kewajaran dan skalabilitas. Ini sangat populer untuk aplikasi web backend. Status apa pun disimpan di luar, dalam database bersama. Ini membuat load balancing mudah: Saya tidak perlu menempel sesi ke server tertentu. Bagaimana jika saya membutuhkan lebih banyak server? Tambahkan saja, karena menggunakan basis data yang sama. Bagaimana jika satu server lumpuh? Saya dapat mengulang permintaan yang tertunda di server lain. Tentu saja, masih ada keadaan - dalam database. Tetapi saya telah membuatnya secara eksplisit dan mengekstraksinya, dan dapat menggunakan pendekatan fungsional murni secara internal jika saya mau.

amon
sumber
Terima kasih atas jawaban terinci. Apa yang saya simpan sebagai kesimpulan, adalah bahwa efek samping tidak mempengaruhi nilai fungsi, karena alasan persamaan, inilah mengapa "bahasa fungsional tidak memungkinkan / meminimalkan efek samping". Efek yang tertanam dalam nilai fungsi mempengaruhi dan mengubah status yang pernah disimpan - atau disimpan di luar inti program. Juga, I / O terjadi pada batas luar logika bisnis.
codebot
3
@ Codebot, Tidak cukup, menurut saya. Jika diimplementasikan dengan benar, efek samping dalam pemrograman fungsional harus tercermin dalam tipe pengembalian fungsi. Misalnya, jika suatu fungsi mungkin gagal (jika file tertentu tidak ada atau koneksi basis data tidak dapat dibuat), maka tipe kembalinya fungsi harus merangkum kegagalan tersebut, alih-alih membuat fungsi membuang pengecualian. Lihatlah Pemrograman Berorientasi Kereta Api sebagai contoh ( fsharpforfunandprofit.com/posts/recipe-part2 ).
Aaron M. Eshbach
"... mereka bisa disebut bahasa fungsional-imperatif.": Simon Peyton Jones menulis "... Haskell adalah bahasa pemrograman imperatif terbaik di dunia.".
Giorgio
5

Tidak ada bahasa pemrograman yang menghilangkan efek samping. Saya pikir lebih baik untuk mengatakan bahwa bahasa deklaratif mengandung efek samping sementara bahasa imperatif tidak. Namun, saya tidak begitu yakin bahwa semua pembicaraan tentang efek samping ini mendapatkan perbedaan mendasar antara kedua jenis bahasa dan itu benar-benar seperti apa yang Anda cari.

Saya pikir ini membantu untuk menggambarkan perbedaan dengan sebuah contoh.

a = b + c

Baris kode di atas dapat ditulis dalam hampir semua bahasa, jadi bagaimana kita dapat menentukan apakah kita menggunakan bahasa imperatif atau deklaratif? Bagaimana sifat-sifat dari baris kode itu berbeda dalam dua kelas bahasa?

Dalam bahasa imperatif (C, Java, Javascript, & c.) Baris kode itu hanya mewakili langkah dalam suatu proses. Itu tidak memberi tahu kita apa pun tentang sifat dasar dari nilai-nilai itu. Ini memberitahu kita bahwa saat ini setelah baris kode ini (tetapi sebelum baris berikutnya,) aakan sama dengan bplus ctetapi tidak memberi tahu kita apa pun adalam arti yang lebih besar.

Dalam bahasa deklaratif (Haskell, Skema, Excel, & c.) Baris kode mengatakan lebih banyak. Ini membangun hubungan invarian antara adan dua objek lainnya sehingga akan selalu menjadi kasus yang asama dengan bplus c. Perhatikan, bahwa saya memasukkan Excel ke dalam daftar bahasa deklaratif karena meskipun batau cmengubah nilai, faktanya akan tetap asama dengan jumlah mereka.

Menurut saya , ini bukan efek samping atau keadaan, yang membuat kedua jenis bahasa ini berbeda. Dalam bahasa imperatif, setiap baris kode tertentu tidak memberi tahu Anda apa pun tentang makna keseluruhan variabel yang dipertanyakan. Dengan kata lain, a = b + chanya berarti bahwa untuk sesaat yang sangat singkat, aterjadi sama dengan jumlah bdan c.

Sementara itu, dalam bahasa deklaratif setiap baris kode menetapkan kebenaran mendasar yang akan ada sepanjang masa program. Dalam bahasa ini, a = b + cmemberi tahu Anda bahwa apa pun yang terjadi pada baris kode lain aakan selalu sama dengan jumlah dari bdan c.

Daniel T.
sumber