Apakah penutupan dengan efek samping dianggap "gaya fungsional"?

9

Banyak bahasa pemrograman modern mendukung beberapa konsep penutupan , yaitu sepotong kode (blok atau fungsi) itu

  1. Dapat diperlakukan sebagai nilai, dan karena itu disimpan dalam variabel, diedarkan ke berbagai bagian kode, didefinisikan dalam satu bagian dari program dan dipanggil dalam bagian yang sama sekali berbeda dari program yang sama.
  2. Dapat menangkap variabel dari konteks di mana ia didefinisikan, dan mengaksesnya ketika nanti dipanggil (mungkin dalam konteks yang sama sekali berbeda).

Berikut adalah contoh penutupan yang ditulis dalam Scala:

def filterList(xs: List[Int], lowerBound: Int): List[Int] =
  xs.filter(x => x >= lowerBound)

Fungsi literal x => x >= lowerBoundberisi variabel bebas lowerBound, yang ditutup (terikat) oleh argumen fungsi filterListyang memiliki nama yang sama. Penutupan diteruskan ke metode perpustakaan filter, yang dapat memanggilnya berulang kali sebagai fungsi normal.

Saya telah membaca banyak pertanyaan dan jawaban di situs ini dan, sejauh yang saya mengerti, istilah penutupan sering secara otomatis dikaitkan dengan pemrograman fungsional dan gaya pemrograman fungsional.

Definisi pemrograman fungsi pada wikipedia berbunyi:

Dalam ilmu komputer, pemrograman fungsional adalah paradigma pemrograman yang memperlakukan komputasi sebagai evaluasi fungsi matematika dan menghindari keadaan dan data yang bisa berubah. Ini menekankan penerapan fungsi, berbeda dengan gaya pemrograman imperatif, yang menekankan perubahan dalam keadaan.

dan selanjutnya

[...] dalam kode fungsional, nilai output suatu fungsi hanya bergantung pada argumen yang diinput ke fungsi [...]. Menghilangkan efek samping dapat membuatnya lebih mudah untuk memahami dan memprediksi perilaku suatu program, yang merupakan salah satu motivasi utama untuk pengembangan pemrograman fungsional.

Di sisi lain, banyak konstruksi penutupan yang disediakan oleh bahasa pemrograman memungkinkan penutupan untuk menangkap variabel non-lokal dan mengubahnya ketika penutupan itu dipanggil, sehingga menghasilkan efek samping pada lingkungan di mana mereka didefinisikan.

Dalam hal ini, closure mengimplementasikan ide pertama pemrograman fungsional (fungsi adalah entitas kelas satu yang dapat digerakkan seperti nilai-nilai lainnya) tetapi mengabaikan ide kedua (menghindari efek samping).

Apakah penggunaan penutupan dengan efek samping ini dianggap sebagai gaya fungsional atau apakah penutupan dianggap sebagai konstruksi yang lebih umum yang dapat digunakan baik untuk gaya pemrograman fungsional maupun non-fungsional? Apakah ada literatur tentang topik ini?

CATATAN PENTING

Saya tidak mempertanyakan kegunaan efek samping atau memiliki penutupan dengan efek samping. Juga, saya tidak tertarik dalam diskusi tentang keuntungan / kerugian penutupan dengan atau tanpa efek samping.

Saya hanya tertarik untuk mengetahui apakah menggunakan penutupan seperti itu masih dianggap gaya fungsional oleh pendukung pemrograman fungsional atau jika, sebaliknya, penggunaannya tidak disarankan ketika menggunakan gaya fungsional.

Giorgio
sumber
3
Dalam gaya / bahasa yang murni fungsional, efek samping tidak mungkin ... jadi saya kira itu akan menjadi masalah: pada tingkat kemurnian apa seseorang menganggap kode berfungsi?
Steven Evers
@SnOrfus: 100%?
Giorgio
1
Efek samping tidak mungkin dalam bahasa yang murni fungsional, sehingga harus menjawab pertanyaan Anda.
Steven Evers
@SnOrfus: Dalam banyak bahasa, penutupan dianggap sebagai konstruksi pemrograman fungsional, jadi saya agak bingung. Tampak bagi saya bahwa (1) penggunaannya lebih umum daripada hanya melakukan FP, dan (2) menggunakan penutupan tidak secara otomatis menjamin seseorang menggunakan gaya fungsional.
Giorgio
Bisakah pemberi downvot meninggalkan catatan tentang cara meningkatkan pertanyaan ini?
Giorgio

Jawaban:

7

Tidak; definisi paradigma fungsional adalah tentang kurangnya keadaan dan secara implisit kurangnya efek samping. Ini bukan tentang fungsi tingkat tinggi, penutupan, manipulasi daftar yang didukung bahasa atau fitur bahasa lainnya ...

Nama pemrograman fungsional berasal dari pengertian matematika tentang fungsi - panggilan berulang pada input yang sama selalu memberikan output yang sama - fungsi nullipotent. Ini hanya dapat dicapai jika data tidak dapat diubah . Untuk memudahkan pengembangan, fungsi-fungsi menjadi bisa berubah (fungsi berubah, data masih tidak berubah) dan dengan demikian pengertian fungsi tingkat tinggi (fungsional dalam matematika, sebagai turunan misalnya) - fungsi yang mengambil input fungsi lain. Untuk kemungkinan fungsi untuk dibawa-bawa dan diteruskan sebagai argumen, fungsi kelas satu diadopsi; mengikuti ini, untuk lebih meningkatkan produktivitas, penutupan muncul.

Ini, tentu saja, pandangan yang sangat sederhana.

m3th0dman
sumber
3
Fungsi tingkat tinggi sama sekali tidak ada hubungannya dengan kemampuan berubah, begitu pula fungsi kelas satu. Saya dapat mengedarkan fungsi-fungsi dan membangun fungsi-fungsi lain dari mereka di Haskell dengan sangat mudah, tanpa memiliki status efek maupun efek samping yang dapat berubah.
tdammers
@tdammers Saya mungkin tidak mengungkapkan dengan jelas; ketika saya mengatakan fungsi menjadi bisa berubah saya tidak merujuk pada data yang bisa berubah tetapi pada kenyataan bahwa perilaku fungsi dapat diubah (oleh fungsi tingkat tinggi).
m3th0dman
Fungsi tingkat tinggi tidak harus mengubah fungsi yang ada; sebagian besar contoh buku teks menggabungkan fungsi yang ada menjadi fungsi baru tanpa mengubahnya sama sekali - ambil map, misalnya, yang mengambil fungsi, menerapkannya ke daftar, dan mengembalikan daftar hasil. maptidak mengubah salah satu argumennya, itu tidak mengubah perilaku fungsi yang dibutuhkan sebagai argumen, tetapi jelas merupakan fungsi tingkat tinggi - jika Anda menerapkannya sebagian, hanya dengan parameter fungsi, Anda telah membangun sebuah fungsi baru yang beroperasi pada daftar, tetapi masih belum terjadi mutasi.
tdammers
@tdammers: Tepat: mutabilitas hanya digunakan dalam bahasa imperatif atau multi-paradigma. Meskipun ini dapat memiliki konsep fungsi (atau metode) tingkat tinggi dan penutupan, mereka menyerah kekekalan. Ini bisa berguna (saya tidak mengatakan tidak boleh melakukannya) tetapi pertanyaan saya adalah apakah Anda masih bisa menyebut ini fungsional. Yang berarti bahwa, secara umum, penutupan bukanlah konsep yang sepenuhnya fungsional.
Giorgio
@Iorgio: sebagian besar bahasa FP klasik memang memiliki kemampuan berubah-ubah; Haskell adalah satu-satunya yang bisa kupikirkan dari atas kepalaku yang sama sekali tidak memungkinkan untuk berubah. Namun, menghindari keadaan yang bisa berubah adalah nilai penting dalam FP.
tdammers
9

Tidak. "Fungsional-gaya" menyiratkan pemrograman bebas efek samping.

Untuk mengetahui alasannya, lihat entri blog Eric Lippert tentang ForEach<T>metode ekstensi, dan mengapa Microsoft tidak menyertakan metode urutan seperti itu di Linq :

Secara filosofis saya menentang menyediakan metode seperti itu, karena dua alasan.

Alasan pertama adalah bahwa hal itu melanggar prinsip pemrograman fungsional yang menjadi dasar semua operator urutan lainnya. Jelas tujuan tunggal panggilan ke metode ini adalah untuk menimbulkan efek samping. Tujuan dari ekspresi adalah untuk menghitung nilai, bukan untuk menimbulkan efek samping. Tujuan dari suatu pernyataan adalah untuk menimbulkan efek samping. Situs panggilan dari hal ini akan terlihat sangat mengerikan seperti sebuah ekspresi (walaupun, diakui, karena metode ini batal-kembali, ekspresi tersebut hanya dapat digunakan dalam konteks "ekspresi pernyataan"). Itu tidak cocok bagi saya untuk buat satu-satunya operator urutan yang hanya berguna untuk efek sampingnya.

Alasan kedua adalah bahwa hal itu menambah nol kekuatan representasional baru ke bahasa. Dengan melakukan ini, Anda dapat menulis ulang kode yang sangat jelas ini:

foreach(Foo foo in foos){ statement involving foo; }

ke dalam kode ini:

foos.ForEach((Foo foo)=>{ statement involving foo; });

yang menggunakan karakter yang hampir sama persis dalam urutan yang sedikit berbeda. Namun versi kedua lebih sulit untuk dipahami, lebih sulit untuk di-debug, dan memperkenalkan semantik penutupan, sehingga berpotensi mengubah masa hidup objek dengan cara yang halus.

Robert Harvey
sumber
1
Meskipun, saya setuju dengannya ... argumennya sedikit melemah ketika mempertimbangkan ParallelQuery<T>.ForAll(...). Implementasi seperti IEnumerable<T>.ForEach(...)itu sangat berguna untuk men -debug ForAllpernyataan (ganti ForAlldengan ForEachdan hapus AsParallel()dan Anda bisa lebih mudah melangkah / debug itu)
Steven Evers
Jelas Joe Duffy punya ide berbeda. : D
Robert Harvey
Scala juga tidak menegakkan kemurnian: Anda dapat memberikan penutupan non-murni ke fungsi tingkat tinggi. Kesan saya adalah bahwa ide penutupan tidak spesifik untuk pemrograman fungsional tetapi itu adalah ide yang lebih umum.
Giorgio
5
@Iorgio: Penutupan tidak harus murni untuk tetap dianggap penutupan. Namun, mereka harus murni untuk dianggap "Gaya Fungsional."
Robert Harvey
3
@Giorgio: Ini benar-benar berguna untuk dapat membuat penutupan di sekitar keadaan berubah dan efek samping dan meneruskannya ke fungsi lain. Itu hanya bertentangan dengan tujuan pemrograman fungsional. Saya pikir banyak kebingungan berasal dari dukungan bahasa yang elegan untuk lambdas yang umum dalam bahasa fungsional.
GlenPeterson
0

Pemrograman fungsional membawa fungsi kelas satu ke tingkat konseptual berikutnya, tetapi mendeklarasikan fungsi anonim atau meneruskan fungsi ke fungsi lain tidak selalu merupakan hal pemrograman fungsional. Di C, semuanya bilangan bulat. Sejumlah, penunjuk ke data, penunjuk ke fungsi ... semua hanya int. Anda bisa meneruskan pointer fungsi ke fungsi lain, membuat daftar pointer fungsi ... Heck, jika Anda bekerja dalam bahasa assembly, fungsi sebenarnya hanya alamat di memori tempat blok instruksi mesin disimpan. Memberi fungsi nama adalah overhead tambahan bagi orang-orang yang membutuhkan kompiler untuk menulis kode. Jadi fungsi adalah "kelas satu" dalam arti dalam bahasa yang sama sekali tidak berfungsi.

Jika satu-satunya hal yang Anda lakukan adalah menghitung rumus matematika dalam REPL, maka Anda dapat secara fungsional murni dengan bahasa Anda. Tetapi kebanyakan pemrograman bisnis memiliki efek samping. Kehilangan uang sambil menunggu program jangka panjang selesai adalah efek sampingnya. Mengambil tindakan eksternal apa pun: menulis ke file, memperbarui basis data, mencatat aktivitas secara berurutan, dll. Memerlukan perubahan status. Kami dapat memperdebatkan apakah keadaan benar-benar berubah jika Anda merangkum tindakan ini dalam pembungkus abadi yang mendorong efek samping sehingga kode Anda tidak perlu khawatir tentang hal itu. Tapi itu seperti mendiskusikan apakah pohon mengeluarkan suara jika jatuh di hutan tanpa ada yang mendengarnya. Faktanya adalah bahwa pohon itu mulai tegak dan berakhir di tanah. Status berubah ketika hal-hal dilakukan, bahkan jika hanya melaporkan bahwa hal-hal itu dilakukan.

Jadi kita dibiarkan dengan skala kemurnian fungsional, bukan hitam dan putih, tetapi nuansa abu-abu. Dan pada skala itu, semakin sedikit efek samping, semakin sedikit sifat berubah-ubah, semakin baik (lebih fungsional).

Jika Anda benar-benar memerlukan efek samping atau keadaan yang dapat berubah dalam kode fungsional Anda, Anda berusaha untuk merangkum atau membuatnya dari sisa program Anda sebaik mungkin. Menggunakan penutupan (atau apa pun) untuk menyuntikkan efek samping atau keadaan bisa berubah menjadi fungsi murni sebaliknya adalah kebalikan dari pemrograman fungsional. Satu-satunya pengecualian mungkin jika penutupan adalah cara paling efektif untuk merangkum efek samping dari kode yang diteruskan. Ini masih bukan "pemrograman fungsional" tetapi mungkin lemari yang bisa Anda dapatkan dalam situasi tertentu.

GlenPeterson
sumber