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, print
fungsi 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.
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.
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,)
a
akan sama denganb
plusc
tetapi tidak memberi tahu kita apa puna
dalam arti yang lebih besar.Dalam bahasa deklaratif (Haskell, Skema, Excel, & c.) Baris kode mengatakan lebih banyak. Ini membangun hubungan invarian antara
a
dan dua objek lainnya sehingga akan selalu menjadi kasus yanga
sama denganb
plusc
. Perhatikan, bahwa saya memasukkan Excel ke dalam daftar bahasa deklaratif karena meskipunb
atauc
mengubah nilai, faktanya akan tetapa
sama 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 + c
hanya berarti bahwa untuk sesaat yang sangat singkat,a
terjadi sama dengan jumlahb
danc
.Sementara itu, dalam bahasa deklaratif setiap baris kode menetapkan kebenaran mendasar yang akan ada sepanjang masa program. Dalam bahasa ini,
a = b + c
memberi tahu Anda bahwa apa pun yang terjadi pada baris kode laina
akan selalu sama dengan jumlah darib
danc
.sumber