Apakah kekekalan dalam pemrograman fungsional benar-benar ada?


9

Meskipun saya bekerja sebagai programmer dalam kehidupan sehari-hari saya dan menggunakan semua bahasa yang trendi (Python, Java, C, dll) saya masih tidak memiliki pandangan yang jelas tentang apa itu pemrograman fungsional. Dari apa yang saya baca, salah satu properti bahasa fungsional adalah bahwa struktur data tidak dapat diubah . Bagi saya ini saja menimbulkan banyak pertanyaan. Tetapi pertama-tama saya akan menulis sedikit tentang apa yang saya pahami tentang kekekalan dan jika saya salah, silakan perbaiki saya.

Pemahaman saya tentang kekekalan:

  • Ketika sebuah program dijalankan, ia memiliki struktur data tetap dengan data tetap
  • Seseorang tidak dapat menambahkan data baru ke struktur ini
  • Tidak ada variabel dalam kode
  • Anda hanya dapat "menyalin" dari data yang sudah ada atau data yang dihitung saat ini
  • Karena semua hal di atas, ketidakmampuan menambah kompleksitas ruang yang besar untuk suatu program

Pertanyaan saya:

  1. Jika struktur data seharusnya tetap sebagaimana adanya (tidak berubah), bagaimana seseorang menambahkan item baru dalam daftar?
  2. Apa gunanya memiliki program yang tidak bisa mendapatkan data baru? Katakanlah Anda memiliki sensor yang terpasang ke komputer Anda yang ingin memberi makan data ke program. Apakah itu berarti bahwa kami tidak dapat menyimpan data yang masuk di mana saja?
  3. Bagaimana pemrograman fungsional baik untuk pembelajaran mesin dalam kasus itu? Karena pembelajaran mesin dibangun dari asumsi memperbarui "persepsi" program tentang hal-hal - sehingga menyimpan data baru.

2
Saya tidak setuju dengan Anda ketika Anda mengatakan tidak ada variabel dalam kode fungsional. Ada variabel dalam arti matematis "Kuantitas yang dapat mengasumsikan salah satu dari sekumpulan nilai". Mereka tidak bisa berubah , tentu saja, tetapi mereka juga tidak dalam matematika.
Édouard

1
Saya pikir kebingungan Anda adalah karena Anda berpikir tentang bahasa fungsional secara abstrak. Ambillah program apa pun di Haskell - mis. Program yang membaca daftar angka dari konsol, mengurutkannya dengan cepat dan mengeluarkannya - dan mencari tahu cara kerjanya dan bagaimana hal itu membuktikan kecurigaan Anda. Tidak ada cara untuk benar-benar membersihkan tanpa melihat contoh program aktual daripada berfilsafat. Anda akan menemukan banyak program di setiap tutorial Haskell.
jkff

@ jkff Apa yang ingin Anda katakan? Haskel itu memiliki fitur non fungsional. Pertanyaannya bukan tentang Haskell, tetapi tentang pemrograman fungsional. Atau apakah Anda menyatakan bahwa semua itu fungsional? Bagaimana? Jadi apa yang salah dengan berfilsafat, seperti yang Anda katakan. Dalam hal apa abstraksi membingungkan? Pertanyaan OP adalah pertanyaan yang sangat masuk akal.
babou

@ Babou Saya mencoba mengatakan bahwa cara terbaik untuk memahami bagaimana bahasa pemrograman yang murni fungsional dapat secara efisien mengimplementasikan algoritma dan struktur data adalah dengan melihat contoh algoritma dan struktur data yang diimplementasikan secara efisien dalam bahasa pemrograman fungsional. Tampaknya bagi saya bahwa OP berusaha memahami bagaimana hal itu mungkin secara konseptual - Saya pikir cara tercepat untuk memahami itu adalah dengan melihat contoh, daripada membaca penjelasan konseptual, tidak peduli seberapa detailnya.
jkff

Salah satu cara untuk melihat pemrograman fungsional adalah dengan mengatakan bahwa itu pemrograman tanpa efek samping. Anda dapat melakukannya dalam bahasa pilihan Anda yang "trendi". Hanya saja, jangan menghindari semua penugasan kembali: misalnya di Jawa, semua variabel Anda akan bersifat final dan semua metode Anda akan menjadi hanya-baca.
reinierpost

Jawaban:


10

Ketika sebuah program dijalankan, ia memiliki struktur data tetap dengan data tetap

Ini sedikit kesalahpahaman. Ini memiliki bentuk tetap dan satu set tetap aturan penulisan ulang tetapi aturan penulisan ulang ini bisa meledak menjadi sesuatu yang jauh lebih besar. Misalnya ekspresi [1..100000000] di Haskell diwakili oleh sejumlah kecil kode tetapi bentuk normalnya masif.

Seseorang tidak dapat menambahkan data baru ke struktur ini

Iya dan tidak. Subset yang sepenuhnya fungsional dari bahasa seperti Haskell atau ML tidak bisa mendapatkan data dari dunia luar tetapi bahasa apa pun untuk pemrograman praktis memiliki mekanisme untuk memasukkan data dari dunia luar ke dalam subset yang sepenuhnya fungsional. Di Haskell ini dilakukan dengan sangat hati-hati tetapi dalam ML Anda dapat melakukan ini kapan pun Anda mau.

Tidak ada variabel dalam kode

Ini cukup benar, tetapi jangan bingung dengan gagasan bahwa tidak ada yang bisa disebutkan namanya. Anda menyebutkan ekspresi yang berguna setiap saat dan terus menggunakannya kembali. Juga ML dan Haskell, setiap Lisp yang saya coba, dan hibrida seperti Scala, semuanya memiliki sarana untuk menciptakan variabel. Mereka hanya tidak umum digunakan. Dan lagi, himpunan bagian fungsional murni dari bahasa tersebut tidak memilikinya.

Anda hanya dapat "menyalin" dari data yang sudah ada atau data yang dihitung saat ini

Anda dapat melakukan perhitungan dengan mereduksi ke bentuk normal. Hal terbaik untuk dilakukan adalah menulis program dalam bahasa fungsional untuk melihat bagaimana mereka melakukan perhitungan.

Misalnya "jumlah [1..1000]" bukan perhitungan yang ingin saya lakukan tetapi cukup mudah dilakukan oleh Haskell. Kami memberikannya ekspresi kecil yang memiliki arti bagi kami dan Haskell memberi kami nomor yang sesuai. Jadi pasti melakukan perhitungan.

Jika struktur data seharusnya tetap sebagaimana adanya (tidak berubah), bagaimana seseorang menambahkan item baru dalam daftar?

Anda tidak menambahkan item baru ke daftar, Anda membuat daftar baru dari yang lama. Karena yang lama tidak dapat dimutasi, sangat aman untuk menggunakannya dalam daftar baru, atau di mana pun Anda inginkan. Lebih banyak data yang dapat dibagikan dengan aman dalam skema ini.

Apa gunanya memiliki program yang tidak bisa mendapatkan data baru? Katakanlah Anda memiliki sensor yang terpasang ke komputer Anda yang ingin memberi makan data ke program. Apakah itu berarti bahwa kami tidak dapat menyimpan data yang masuk di mana saja?

Sejauh input pengguna, bahasa pemrograman praktis apa pun memiliki cara untuk mendapatkan input pengguna. Ini terjadi. Namun ada subset yang berfungsi penuh dari bahasa-bahasa ini yang Anda tulis sebagian besar kode Anda dan Anda menuai keuntungan dengan cara ini.

Bagaimana pemrograman fungsional baik untuk pembelajaran mesin dalam kasus itu? Karena pembelajaran mesin dibangun dari asumsi memperbarui "persepsi" program tentang hal-hal - sehingga menyimpan data baru.

Ini akan menjadi kasus untuk pembelajaran aktif tetapi kebanyakan pembelajaran mesin saya telah bekerja dengan (saya bekerja sebagai kode monyet dalam kelompok pembelajaran mesin dan telah melakukannya selama beberapa tahun) memiliki proses pembelajaran satu kali di mana semua data pelatihan dimuat sekaligus. Tetapi untuk pembelajaran aktif Anda tidak dapat melakukan hal-hal 100% murni secara fungsional. Anda harus membaca beberapa data dari dunia luar.


Saya merasa seperti Anda dengan mudah mengabaikan apa yang mungkin bisa dibilang titik paling penting dalam pos @ Pithikos, yang merupakan masalah ruang - program fungsional menggunakan lebih banyak ruang daripada yang penting (Anda tidak dapat menulis algoritma di tempat dan semacamnya)
user541686

2
Ini tidak benar. Kurangnya mutasi sebagian besar dibuat dengan berbagi dan di atas semua itu perbedaan ukuran yang Anda rujuk adalah sangat kecil dengan kompiler modern. Sebagian besar kode pada daftar di haskell efisien di tempat atau tidak menggunakan memori sama sekali.
Jake

1
Saya pikir Anda salah menggambarkan ML. Ya, I / O dapat terjadi di mana saja, tetapi cara informasi baru dimasukkan ke dalam struktur yang ada dikontrol dengan ketat.
dfeuer

@Pithikos, Ada variabel di semua tempat; mereka hanya berbeda dari apa yang biasa Anda lakukan, seperti yang ditunjukkan Édouard. Dan hal-hal terus menerus dialokasikan dan sampah dikumpulkan. Setelah Anda benar-benar masuk ke pemrograman fungsional, Anda akan mendapatkan pemahaman yang lebih baik tentang bagaimana sebenarnya berjalan.
dfeuer

1
Memang benar bahwa ada algoritma yang tidak memiliki implementasi fungsional murni dengan kompleksitas waktu yang sama dengan implementasi imperatif yang paling dikenal - misalnya datastruktur Union-Find (dan, um, array :)) Saya membayangkan ada juga kasus seperti ini untuk ruang kompleksitas. Tetapi ini adalah pengecualian - kebanyakan algoritma / struktur data memiliki implementasi dengan kompleksitas waktu dan ruang yang setara. Ini masalah subjektif dari gaya pemrograman dan (untuk faktor konstan) kualitas kompiler.
jkff

4

Immutability atau mutability bukanlah konsep yang masuk akal dalam pemrograman fungsional.

Konteks komputasi

Ini adalah pertanyaan yang sangat bagus yang merupakan tindak lanjut yang menarik (bukan duplikat) ke yang lain baru-baru ini: Apa perbedaan antara penugasan, penilaian dan pengikatan nama?

Alih-alih menjawab pernyataan Anda satu per satu, saya mencoba di sini untuk memberi Anda gambaran umum terstruktur tentang apa yang dipertaruhkan.

Ada beberapa masalah yang harus dipertimbangkan untuk menjawab Anda, termasuk:

  • Apa itu model perhitungan, dan konsep apa yang masuk akal untuk model yang diberikan

  • Apa arti dari kata-kata yang Anda gunakan, dan bagaimana kata itu tergantung pada konteksnya

Gaya pemrograman fungsional tampaknya konyol karena Anda melihatnya dengan mata programmer yang sangat penting. Tetapi ini adalah paradigma yang berbeda, dan konsep serta persepsi Anda yang imperatif adalah asing, tidak pada tempatnya. Para penyusun tidak memiliki prasangka seperti itu.

Tetapi kesimpulan akhirnya adalah bahwa adalah mungkin untuk menulis program dengan cara fungsional murni, termasuk untuk pembelajaran mesin, pemikiran pemrograman fungsional tidak memiliki konsep menyimpan data. Saya tampaknya tidak setuju pada poin ini dengan jawaban lain.

Dengan harapan beberapa orang akan tertarik meskipun jawaban ini panjang.

Paradigma komputasi

Pertanyaannya adalah tentang pemrograman fungsional (alias pemrograman aplikatif), model komputasi spesifik, yang representatif teoretis dan paling sederhana adalah kalkulus lambda.

Jika Anda tetap pada level teoretis, ada banyak model perhitungan: mesin Turing (TM), mesin RAM dan lainnya , kalkulus lambda, logika kombinatori, teori fungsi rekursif, sistem semi-Thue, dll. Komputasi yang lebih kuat model telah terbukti setara dalam hal apa yang dapat mereka tangani, dan itulah inti dari tesis Gereja-Turing .

Konsep penting adalah mereduksi model satu sama lain, yang merupakan dasar untuk menetapkan kesetaraan yang mengarah pada tesis Church-Turing. Dilihat dari perspektif pemrogram, mereduksi satu model ke model lain adalah apa yang biasanya disebut sebagai kompiler. Jika Anda menggunakan pemrograman logika sebagai model perhitungan Anda, ini sangat berbeda dengan model yang disediakan oleh PC yang Anda beli di toko, dan kompiler menerjemahkan program yang ditulis dalam bahasa pemrograman logika ke model komputasi yang diwakili oleh PC Anda (cukup banyak komputer RAM).

β

Dalam praktiknya, bahasa pemrograman yang kami gunakan cenderung untuk memadukan konsep-konsep dari asal-usul teoretis yang berbeda, mencoba melakukannya sehingga bagian-bagian tertentu dari suatu program dapat memperoleh manfaat dari sifat-sifat beberapa model yang sesuai. Demikian pula, orang yang membangun sistem dapat memilih bahasa yang berbeda untuk komponen yang berbeda, untuk menyesuaikan bahasa dengan tugas yang ada.

Karenanya, Anda jarang melihat paradigma pemrograman dalam keadaan murni dalam bahasa pemrograman. Bahasa pemrograman masih diklasifikasikan menurut paradigma dominan, tetapi sifat-sifat bahasa dapat dipengaruhi ketika konsep dari paradigma lain terlibat, sering mengaburkan perbedaan dan masalah konseptual.

Biasanya, bahasa seperti Haskell dan ML atau CAML dianggap fungsional, tetapi mereka dapat memungkinkan untuk perilaku imperatif ... Lain mengapa orang berbicara tentang " subset murni fungsional "?

Maka orang dapat mengklaim itu, Anda dapat melakukan ini atau itu dalam bahasa pemrograman fungsional saya, tetapi itu tidak benar-benar menjawab pertanyaan tentang pemrograman fungsional ketika itu bergantung pada apa yang dapat dianggap ekstra fungsional.

Jawabannya harus lebih tepat terkait dengan paradigma tertentu, tanpa tambahan.

Apa itu variabel?

Masalah lain adalah penggunaan terminologi. Dalam matematika, variabel adalah entitas yang mewakili nilai yang tidak ditentukan dalam beberapa domain. Ini digunakan untuk berbagai keperluan. Digunakan dalam suatu persamaan, itu bisa berdiri untuk nilai apa pun sehingga persamaan diverifikasi. Visi ini digunakan dalam pemrograman logika dengan nama " variabel logis ", mungkin karena variabel nama sudah memiliki arti lain ketika pemrograman logika dikembangkan.

Dalam pemrograman imperatif tradisional, variabel dipahami sebagai semacam wadah (atau lokasi memori) yang dapat menghafal representasi nilai, dan mungkin mendapatkan nilai saat ini digantikan oleh yang lain).

Dalam pemrograman fungsional, variabel memiliki tujuan yang sama dengan matematika dalam sebagai tempat-penampung untuk beberapa nilai, belum disediakan. Dalam pemrograman imperatif tradisional peran ini sebenarnya dimainkan oleh konstan (jangan dikacaukan dengan literal yang ditentukan nilai yang dinyatakan dengan notasi khusus untuk domain nilai-nilai itu, seperti 123, true, ["abdcz", 3.14]).

Variabel apa pun, serta konstan, dapat diwakili oleh pengidentifikasi.

Variabel imperatif dapat mengubah nilainya dan itu adalah dasar untuk mutabilitas. Variabel fungsional tidak bisa.

Bahasa pemrograman biasanya memungkinkan untuk entitas yang lebih besar dibangun dari yang lebih kecil dalam bahasa tersebut.

Bahasa imperatif memungkinkan konstruksi semacam itu untuk memasukkan variabel dan itulah yang memberi Anda data yang bisa berubah.

Cara membaca program

Suatu program pada dasarnya deskripsi abstrak dari algoritma Anda adalah beberapa bahasa, apakah desain pragmatis, atau bahasa yang secara paradigmatik murni.

Pada prinsipnya, Anda dapat mengambil setiap pernyataan untuk apa yang seharusnya dimaksudkan secara abstrak. Kemudian kompiler akan menerjemahkannya ke bentuk yang tepat untuk dieksekusi oleh komputer, tetapi itu bukan masalah Anda dalam perkiraan pertama.

Tentu saja, kenyataannya sedikit lebih keras, dan sering kali baik untuk mengetahui apa yang terjadi sehingga untuk menghindari struktur yang tidak akan dikompilasi oleh kompiler untuk eksekusi yang efisien. Tapi itu sudah optimasi ... compiler mana yang bisa sangat baik, seringkali lebih baik daripada programmer.

Pemrograman fungsional dan mutabilitas

Mutabilitas didasarkan pada keberadaan variabel imperatif yang dapat berisi nilai-nilai, untuk diubah oleh penugasan. Karena ini tidak ada dalam pemrograman fungsional, semuanya dapat dilihat sebagai tidak berubah.

Pemrograman fungsional berurusan secara eksklusif dengan nilai-nilai.

Empat pernyataan pertama Anda tentang kekekalan sebagian besar benar, tetapi jelaskan dengan pandangan imperatif sesuatu yang tidak penting. Ini agak seperti menggambarkan dengan warna di dunia di mana setiap orang buta. Anda menggunakan konsep yang asing dengan pemrograman fungsional.

Anda hanya memiliki nilai murni, dan array bilangan bulat adalah nilai murni. Untuk mendapatkan array lain yang berbeda hanya dalam satu elemen, Anda harus menggunakan nilai array yang berbeda. Mengubah elemen hanyalah sebuah konsep yang tidak ada dalam konteks ini. Anda mungkin memiliki fungsi yang memiliki array dan beberapa indeks sebagai argumen, dan mengembalikan hasil yang merupakan array yang hampir sama yang hanya berbeda di mana ditunjukkan oleh indeks. Tetapi ini masih merupakan nilai array independen. Bagaimana nilai-nilai ini diwakili bukanlah masalah Anda. Mungkin mereka "berbagi" banyak dalam terjemahan imperatif untuk komputer ... tapi itu adalah pekerjaan kompiler ... dan Anda bahkan tidak ingin tahu untuk jenis arsitektur mesin apa yang dikompilasi.

Anda tidak menyalin nilai (itu tidak masuk akal, itu adalah konsep yang asing). Anda cukup menggunakan nilai yang ada di domain yang telah Anda tetapkan dalam program Anda. Entah Anda menggambarkannya (sebagai literal) atau merupakan hasil penerapan fungsi ke beberapa nilai lainnya. Anda dapat memberi mereka nama (sehingga mendefinisikan konstanta) untuk memastikan nilai yang sama digunakan di tempat yang berbeda dalam program. Perhatikan bahwa aplikasi fungsi tidak harus dianggap sebagai perhitungan tetapi sebagai hasil aplikasi ke argumen yang diberikan. Menulis 5+2atau menulis sama 7jumlahnya. Yang konsisten dengan paragraf sebelumnya.

Tidak ada variabel imperatif. Tidak ada tugas yang memungkinkan. Anda dapat mengikat nama hanya ke nilai (untuk membentuk konstanta), tidak seperti bahasa imperatif di mana Anda dapat mengikat nama ke variabel yang dapat ditentukan.

Apakah itu memiliki biaya dalam kompleksitas sama sekali tidak jelas. Untuk satu hal, Anda merujuk pada kompleksitas menyangkut paradigma imperatif. Ini tidak didefinisikan seperti itu untuk pemrograman fungsional, kecuali jika Anda memilih untuk membaca program fungsional Anda sebagai keharusan, yang bukan maksud dari perancang. Memang pandangan fungsional dimaksudkan untuk membuat Anda tidak khawatir tentang masalah seperti itu dan berkonsentrasi pada apa yang sedang dihitung. Ini agak mirip spesifikasi versus implementasi.

Kompiler harus menjaga implementasi, dan harus cukup pintar untuk menyesuaikan apa yang harus dilakukan dengan perangkat keras yang akan melakukannya, apa pun itu.

Saya tidak mengatakan programmer tidak pernah khawatir tentang itu. Saya juga tidak mengatakan bahwa bahasa pemrograman dan teknologi kompiler sama matangnya seperti yang kita harapkan.

Menjawab pertanyaan

  1. Anda tidak mengubah nilai yang ada (konsep alien), tetapi menghitung nilai baru yang berbeda di mana diinginkan, mungkin dengan memiliki satu elemen tambahan itu adalah daftar.

  2. Program bisa mendapatkan data baru. Intinya adalah bagaimana Anda mengekspresikannya dalam bahasa. Misalnya, Anda dapat mempertimbangkan bahwa program ini bekerja dengan satu nilai spesifik, mungkin dalam ukuran tidak terbatas, yang disebut aliran input. Ini adalah nilai yang seharusnya duduk di sana (apakah sudah diketahui sepenuhnya atau tidak bukan masalah Anda). Kemudian Anda memiliki fungsi yang mengembalikan pasangan yang terdiri dari elemen pertama dari aliran, dan sisanya dari aliran.

    Anda dapat menggunakannya untuk membangun jaringan komponen komunikasi dengan cara yang murni aplikatif (coroutine)

  3. Pembelajaran mesin hanyalah masalah lain ketika Anda harus mengumpulkan data dan mengubah nilai. Dalam pemrograman fungsional Anda tidak melakukan itu: Anda hanya menghitung nilai-nilai baru yang berbeda sesuai dengan data pelatihan. Mesin yang dihasilkan akan bekerja juga. Yang Anda khawatirkan adalah efisiensi waktu dan ruang komputasi. Tetapi, sekali lagi, itu adalah masalah yang berbeda, yang idealnya harus ditangani oleh kompiler.

Komentar akhir

Sangat jelas, dari komentar atau jawaban lain, bahwa bahasa pemrograman fungsional praktis tidak murni fungsional. Itu adalah refleksi dari fakta bahwa teknologi kami masih harus ditingkatkan, terutama dalam hal kompilasi.

Apakah mungkin menulis dengan gaya aplikatif murni? Jawabannya sudah diketahui sekitar 40 tahun dan itu adalah "ya". Tujuan semantik denotasional seperti yang muncul pada tahun 1970-an adalah untuk menerjemahkan (mengkompilasi) bahasa ke dalam gaya yang murni fungsional, dianggap lebih dipahami secara matematis dan dengan demikian dianggap sebagai pendanaan yang lebih baik untuk mendefinisikan semantik program.

Aspek yang menarik adalah bahwa struktur pemrograman imperatif, termasuk variabel, dapat diterjemahkan ke dalam gaya fungsional dengan memperkenalkan domain nilai yang sesuai, seperti penyimpanan data. Dan meskipun gaya fungsional, secara mengejutkan tetap mirip dengan kode kompiler yang sebenarnya ditulis dalam gaya imperatif.


0

Ini adalah kesalahpahaman bahwa program fungsional tidak dapat menyimpan data, dan saya pikir jawaban Jakes tidak menjelaskan hal ini dengan sangat baik.

Program fungsional, seperti program apa pun, benar-benar berfungsi memetakan bilangan bulat ke bilangan bulat. Setiap program penting yang beroperasi pada struktur data yang dapat berubah memiliki mitra fungsional. Ini hanyalah cara lain untuk mencapai tujuan yang sama.

Cara fungsional menyimpan data eksperimen dari beberapa sumber akan memanggil fungsi penyimpanan dengan struktur data sebagai argumen dan menghasilkan gabungan dari struktur data yang ada dan data baru dan dengan demikian data disimpan tanpa gagasan tentang struktur data yang bisa berubah.

Dari pengalaman saya sendiri , saya pikir konsep struktur data yang tidak dapat diubah membuat para pengembang konvensional berpikir bahwa ada hal-hal tertentu yang tidak praktis atau bahkan tidak mungkin dilakukan dalam pengaturan fungsional. Ini bukan kasusnya.


"Program fungsional, seperti program apa pun, benar-benar berfungsi memetakan bilangan bulat ke bilangan bulat." Bagaimana, katakanlah, Minecraft, benar-benar sebuah fungsi pemetaan bilangan bulat ke bilangan bulat?
David Richerby

Dengan mudah. Setiap byte dapat diartikan sebagai biner integer. Keadaan di komputer adalah kumpulan byte. Sebuah program, bahkan Minecraft, memanipulasi status komputer, memetakannya dari satu kondisi ke kondisi lainnya.
Jeppe Hartmund

Input pengguna sepertinya tidak cocok dengan dunia ini.
David Richerby

Input pengguna adalah bagian dari kondisi komputer. Itu tidak hanya ada di layar Anda.
Jeppe Hartmund
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.