Untuk keperluan jawaban ini saya mendefinisikan "bahasa murni fungsional" yang berarti bahasa fungsional di mana fungsi-fungsi transparan secara referensial, yaitu memanggil fungsi yang sama beberapa kali dengan argumen yang sama akan selalu menghasilkan hasil yang sama. Saya percaya ini adalah definisi umum dari bahasa yang murni fungsional.
Bahasa pemrograman fungsional murni tidak memungkinkan efek samping (dan karena itu tidak banyak digunakan dalam praktik karena setiap program yang bermanfaat memang memiliki efek samping, misalnya ketika berinteraksi dengan dunia luar).
Cara termudah untuk mencapai transparansi referensial memang dengan melarang efek samping dan memang ada bahasa di mana itu terjadi (kebanyakan yang spesifik domain). Namun itu tentu saja bukan satu-satunya cara dan tujuan paling umum bahasa murni fungsional (Haskell, Clean, ...) memungkinkan efek samping.
Juga mengatakan bahwa bahasa pemrograman tanpa efek samping sedikit digunakan dalam praktiknya tidak benar-benar adil saya pikir - tentu saja tidak untuk bahasa domain spesifik, tetapi bahkan untuk bahasa tujuan umum, saya membayangkan sebuah bahasa bisa sangat berguna tanpa memberikan efek samping . Mungkin bukan untuk aplikasi konsol, tapi saya pikir aplikasi GUI dapat diimplementasikan dengan baik tanpa efek samping, katakanlah, paradigma reaktif fungsional.
Mengenai poin 1, Anda dapat berinteraksi dengan lingkungan dalam bahasa murni fungsional tetapi Anda harus secara eksplisit menandai kode (fungsi) yang memperkenalkan mereka (misalnya dalam Haskell dengan cara tipe monadik).
Itu sedikit terlalu menyederhanakannya. Hanya memiliki sistem di mana fungsi efek samping perlu ditandai seperti itu (mirip dengan koreksi-benar di C ++, tetapi dengan efek samping umum) tidak cukup untuk memastikan transparansi referensial. Anda perlu memastikan bahwa suatu program tidak pernah dapat memanggil fungsi beberapa kali dengan argumen yang sama dan mendapatkan hasil yang berbeda. Anda bisa melakukannya dengan membuat hal-hal sepertireadLine
menjadi sesuatu yang bukan fungsi (itulah yang Haskell lakukan dengan IO monad) atau Anda bisa membuatnya mustahil untuk memanggil fungsi efek samping beberapa kali dengan argumen yang sama (itulah yang dilakukan Clean). Dalam kasus terakhir kompiler akan memastikan bahwa setiap kali Anda memanggil fungsi efek samping, Anda melakukannya dengan argumen baru, dan itu akan menolak program apa pun di mana Anda meneruskan argumen yang sama ke fungsi efek samping dua kali.
Bahasa pemrograman fungsional murni tidak memungkinkan untuk menulis sebuah program yang mempertahankan keadaan (yang membuat pemrograman sangat canggung karena dalam banyak aplikasi Anda membutuhkan keadaan).
Sekali lagi, bahasa yang murni fungsional mungkin sangat tidak memungkinkan keadaan yang bisa berubah, tetapi tentu saja mungkin untuk menjadi murni dan masih memiliki keadaan yang bisa berubah, jika Anda menerapkannya dengan cara yang sama seperti yang saya jelaskan dengan efek samping di atas. Keadaan yang benar-benar bisa berubah hanyalah bentuk lain dari efek samping.
Yang mengatakan, bahasa pemrograman fungsional pasti mencegah negara bisa berubah - yang murni terutama begitu. Dan saya tidak berpikir itu membuat pemrograman canggung - justru sebaliknya. Kadang-kadang (tetapi tidak semua yang sering) keadaan bisa berubah tidak dapat dihindari tanpa kehilangan kinerja atau kejelasan (itulah sebabnya bahasa seperti Haskell memang memiliki fasilitas untuk keadaan bisa berubah), tetapi paling sering itu bisa.
Jika mereka salah paham, bagaimana hal itu terjadi?
Saya pikir banyak orang hanya membaca "suatu fungsi harus menghasilkan hasil yang sama ketika dipanggil dengan argumen yang sama" dan menyimpulkan bahwa itu tidak mungkin untuk mengimplementasikan sesuatu seperti readLine
atau kode yang mempertahankan keadaan bisa berubah. Jadi mereka sama sekali tidak menyadari "cheat" yang dapat digunakan bahasa murni untuk memperkenalkan hal-hal ini tanpa melanggar transparansi referensial.
Keadaan yang bisa berubah juga sangat tidak mendukung dalam bahasa fungsional, sehingga tidak terlalu banyak lompatan untuk menganggap itu tidak diperbolehkan sama sekali dalam bahasa yang murni fungsional.
Bisakah Anda menulis potongan kode (mungkin kecil) yang menggambarkan cara Haskell idiomatis untuk (1) menerapkan efek samping dan (2) menerapkan perhitungan dengan status?
Berikut aplikasi di Pseudo-Haskell yang meminta nama pengguna dan menyapanya. Pseudo-Haskell adalah bahasa yang baru saja saya temukan, yang memiliki sistem IO Haskell, tetapi menggunakan sintaksis yang lebih konvensional, nama fungsi yang lebih deskriptif dan tidak memiliki do
notasi (karena itu hanya akan mengalihkan perhatian dari bagaimana tepatnya IO monad bekerja):
greet(name) = print("Hello, " ++ name ++ "!")
main = composeMonad(readLine, greet)
Petunjuk di sini adalah bahwa itu readLine
adalah nilai tipe IO<String>
dan composeMonad
merupakan fungsi yang mengambil argumen tipe IO<T>
(untuk beberapa tipe T
) dan argumen lain yang merupakan fungsi yang mengambil argumen tipe T
dan mengembalikan nilai tipe IO<U>
(untuk beberapa tipe U
). print
adalah fungsi yang mengambil string dan mengembalikan nilai tipe IO<void>
.
Nilai tipe IO<A>
adalah nilai yang "menyandikan" tindakan yang diberikan yang menghasilkan nilai tipe A
. composeMonad(m, f)
menghasilkan IO
nilai baru yang mengkodekan tindakan m
diikuti oleh tindakan f(x)
, di mana x
nilai menghasilkan dengan melakukan tindakan m
.
Status yang bisa berubah akan terlihat seperti ini:
counter = mutableVariable(0)
increaseCounter(cnt) =
setIncreasedValue(oldValue) = setValue(cnt, oldValue + 1)
composeMonad(getValue(cnt), setIncreasedValue)
printCounter(cnt) = composeMonad( getValue(cnt), print )
main = composeVoidMonad( increaseCounter(counter), printCounter(counter) )
Berikut mutableVariable
adalah fungsi yang mengambil nilai dari jenis apa pun T
dan menghasilkan a MutableVariable<T>
. Fungsi getValue
mengambil MutableVariable
dan mengembalikan suatu IO<T>
yang menghasilkan nilai saat ini. setValue
mengambil a MutableVariable<T>
dan a T
dan mengembalikan sebuah IO<void>
yang menetapkan nilai. composeVoidMonad
sama seperti composeMonad
kecuali bahwa argumen pertama adalah argumen IO
yang tidak menghasilkan nilai yang masuk akal dan argumen kedua adalah monad lain, bukan fungsi yang mengembalikan monad.
Dalam Haskell ada beberapa gula sintaksis, yang membuat seluruh cobaan ini tidak terlalu menyakitkan, tetapi masih jelas bahwa keadaan yang bisa berubah adalah sesuatu yang tidak benar-benar ingin Anda lakukan bahasa.