Menangani masalah status dalam pemrograman fungsional

18

Saya telah belajar bagaimana memprogram terutama dari sudut pandang OOP (seperti kebanyakan dari kita, saya yakin), tetapi saya telah menghabiskan banyak waktu untuk belajar bagaimana menyelesaikan masalah dengan cara fungsional. Saya memiliki pemahaman yang baik tentang bagaimana menyelesaikan masalah perhitungan dengan FP, tetapi ketika datang ke masalah yang lebih rumit saya selalu menemukan diri saya kembali ke membutuhkan benda yang bisa berubah. Sebagai contoh, jika saya menulis simulator partikel, saya ingin partikel "objek" dengan posisi yang dapat diperbarui untuk diperbarui. Bagaimana secara inheren masalah "stateful" biasanya diselesaikan dengan menggunakan teknik pemrograman fungsional?

Andrew Martin
sumber
4
Langkah pertama kemungkinan menyadari bahwa masalah tidak secara inheren stateful.
Telastyn
4
Beberapa masalah secara inheren stateful, seperti menulis ke database atau menggambar gui. Mengambil contoh simulator partikel saya, apa yang akan menjadi cara alternatif untuk memikirkannya? Mengembalikan partikel baru setiap kali posisi mereka diperbarui untuk menghindari keadaan tampaknya tidak efisien bagi saya, dan bukan model yang baik dari dunia nyata.
Andrew Martin
4
Kecuali mungkin untuk contoh database, masalah-masalah itu tidak secara inheren stateful. Misalnya, untuk pemrograman GUI, Anda benar-benar menggunakan keadaan bisa berubah sebagai model waktu yang buruk dan implisit ; pemrograman reaktif fungsional memungkinkan Anda memodelkan waktu secara eksplisit tanpa bergantung pada keadaan dengan memberikan aliran peristiwa yang dapat Anda gabungkan.
Tikhon Jelvis
1
Ada solusi yang lebih sederhana: ketika Anda sampai pada masalah yang tidak mudah dimodelkan dengan teknik FP, jangan gunakan pemrograman fungsional untuk menyelesaikannya. Alat yang tepat untuk pekerjaan dan semua itu ...
Mason Wheeler
1
@AndrewMartin Bukan model dunia nyata yang bagus? Matematika yang digunakan dalam fisika untuk memodelkan dunia nyata adalah murni fungsional. Dengan pengumpul sampah yang baik mengalokasikan objek semurah menabrak pointer, dan waktu pengumpulan sebanding dengan jumlah objek hidup . Jika ada, saya berani bertaruh sumber utama ketidakefisienan dalam pemrograman fungsional menggunakan struktur data yang tidak efisien cache. Daftar yang ditautkan dan pohon biner tidak sepenuhnya menunjukkan efisiensi cache.
Doval

Jawaban:

20

Program fungsional menangani keadaan dengan sangat baik, tetapi membutuhkan cara pandang yang berbeda. Sebagai contoh posisi Anda, satu hal yang perlu dipertimbangkan adalah menjadikan posisi Anda sebagai fungsi waktu alih-alih nilai tetap . Ini berfungsi baik untuk partikel mengikuti jalur matematika tetap, tetapi Anda memerlukan strategi berbeda untuk menangani perubahan di jalur, seperti setelah tabrakan.

Strategi dasar di sini adalah Anda membuat fungsi yang mengambil dalam keadaan dan mengembalikan negara baru . Jadi simulator partikel akan menjadi fungsi yang mengambil Setpartikel sebagai input dan mengembalikan Setpartikel baru setelah langkah waktu. Kemudian Anda hanya berulang kali memanggil fungsi itu dengan inputnya disetel ke hasil sebelumnya.

Karl Bielefeldt
sumber
5
+1 Tidak apa-apa untuk memiliki status di FP, hanya saja keadaan tidak bisa berubah .
jhewlett
1
Terima kasih atas wawasan ini. Kekhawatiran saya tentang ketidakefisienan digagalkan oleh @logc; rincian teknis tentang bagaimana negara ditransformasikan adalah masalah implementasi tingkat rendah yang bahasa itu sendiri seharusnya dipecahkan. Saya menyaksikan Rich Hickey menjelaskan bagaimana dia melakukan ini dengan Clojure dalam sebuah video.
Andrew Martin
1
@ jhewlett: Untuk lebih tepatnya: FP memang memiliki status, bahkan keadaan bisa berubah, tetapi mereka tidak mewakilinya menggunakan variabel yang bisa diubah.
Giorgio
9

Seperti dicatat oleh @KarlBielefeldt, pendekatan fungsional untuk masalah seperti itu adalah melihatnya mengembalikan negara baru dari keadaan sebelumnya. Fungsi itu sendiri tidak menyimpan informasi apa pun, sehingga mereka akan selalu memperbarui status m ke status n .

Saya pikir Anda menemukan ini tidak efisien karena Anda menganggap keadaan sebelumnya harus disimpan dalam memori saat menghitung keadaan baru. Perhatikan bahwa pilihan antara menulis keadaan yang sama sekali baru atau menulis ulang yang lama di tempat adalah detail implementasi dari sudut pandang bahasa fungsional.

Misalnya, saya memiliki daftar sejuta bilangan bulat, dan ingin menambah kesepuluh dengan satu unit. Menyalin seluruh daftar dengan nomor baru di posisi kesepuluh adalah pemborosan, Anda benar; tetapi hanya cara konseptual untuk menggambarkan operasi ke kompiler bahasa atau juru bahasa. Kompiler atau juru bahasa bebas untuk mengambil daftar pertama dan hanya menimpa posisi kesepuluh.

Keuntungan dari menggambarkan operasi dengan cara ini adalah bahwa kompiler dapat mempertimbangkan situasi ketika banyak utas ingin memperbarui daftar yang sama pada posisi yang berbeda. Jika operasi digambarkan sebagai "buka posisi ini dan timpa apa yang Anda temukan", maka itu adalah programmer, bukan kompiler, yang bertugas memastikan bahwa overwrites tidak bertabrakan.

Dengan semua yang dikatakan, bahkan di Haskell ada monad negara yang membantu memodelkan situasi di mana "menjaga negara" adalah solusi yang lebih intuitif untuk masalah. Tetapi tolong perhatikan juga beberapa masalah yang Anda temukan " secara inheren stateful, seperti menulis ke database " memiliki solusi yang tidak dapat diubah seperti Datomic . Ini bisa mengejutkan sampai Anda memahaminya adalah sebuah konsep, belum tentu realisasinya.

logc
sumber
4
Saya pikir cuplikan tentang memperbarui daftar besar menyesatkan; Saya tidak tahu ada kompiler yang benar-benar akan melakukan optimasi untuk Anda. Bahkan jika kompiler dapat melakukan itu, itu hanya mungkin dalam kasus di mana Anda tidak berpegang pada versi daftar sebelumnya. The nyata solusi adalah dengan menggunakan struktur daftar data yang tidak memerlukan menyalin seluruh hal untuk mengubah satu elemen.
Doval
@Doval: "Bahkan jika kompiler dapat melakukan itu, itu hanya mungkin dalam kasus di mana Anda tidak berpegang pada versi sebelumnya dari daftar.": Ini mengingatkan saya pada jenis unik di Bersihkan.
Giorgio
4

Berlangganan model mental yang tepat membantu orang berpikir lebih baik dan mengelola keadaan. Dalam pikiran saya, model mental terbaik adalah buku flip . Setelah klik ini, Anda akan memahami bahwa FP sangat bergantung pada struktur data persisten yang menangkap keadaan dunia dan bahwa fungsi digunakan untuk mentransisikan keadaan itu tanpa mutasi apa pun.

Rich Hickey menjelaskan ide-ide ini:

Ada pembicaraan lain tetapi ini akan mengirim Anda ke arah yang benar.

Mario T. Lanza
sumber
3

Saat menulis aplikasi besar dan cukup besar, saya sering menemukan manfaat untuk membedakan antara bagian aplikasi yang stateful dan yang stateless.

Kelas / struktur data di bagian stateful menyimpan data aplikasi dan fungsi di bagian ini bekerja dengan pengetahuan implisit dari data aplikasi.

Kelas / struktur data / fungsi di bagian stateless ada untuk mendukung aspek algoritmik murni aplikasi. Mereka tidak memiliki pengetahuan implisit tentang data aplikasi. Mereka bekerja secara murni fungsional. Bagian stateful dari aplikasi dapat mengalami perubahan status sebagai efek samping dari menjalankan fungsi di bagian stateless dari aplikasi.

Bagian tersulit adalah mencari tahu kelas / fungsi mana yang diletakkan di bagian stateless dan kelas / fungsi mana yang diletakkan di bagian stateful, dan memiliki disiplin untuk meletakkannya di file / pustaka yang terpisah.

R Sahu
sumber
Bagaimana ini menjawab pertanyaan? (tidak-downvoting)
kravemir
@kravemir, apakah Anda menulis aplikasi menggunakan OOP atau FP, Anda harus memahami di mana letak aplikasi tersebut.
R Sahu