Keuntungan pemrograman stateless?

131

Saya baru-baru ini belajar tentang pemrograman fungsional (khususnya Haskell, tapi saya sudah membaca tutorial tentang Lisp dan Erlang juga). Sementara saya menemukan konsep yang sangat mencerahkan, saya masih tidak melihat sisi praktis dari konsep "tanpa efek samping". Apa manfaat praktisnya? Saya mencoba berpikir dalam pola pikir fungsional, tetapi ada beberapa situasi yang sepertinya terlalu rumit tanpa kemampuan untuk menyelamatkan negara dengan cara yang mudah (saya tidak menganggap monad Haskell 'mudah').

Apakah perlu terus mempelajari Haskell (atau bahasa fungsional murni lainnya) secara mendalam? Apakah pemrograman fungsional atau stateless sebenarnya lebih produktif daripada prosedural? Apakah mungkin saya akan terus menggunakan Haskell atau bahasa fungsional lainnya nanti, atau haruskah saya mempelajarinya hanya untuk pengertian?

Saya kurang peduli dengan kinerja daripada produktivitas. Jadi saya terutama bertanya apakah saya akan lebih produktif dalam bahasa fungsional daripada prosedural / berorientasi objek / apa pun.

Sasha Chedygov
sumber

Jawaban:

167

Baca Pemrograman Fungsional Singkatnya .

Ada banyak keuntungan untuk pemrograman stateless, tidak sedikit dari yang dramatis multithreaded dan kode bersamaan. Terus terang, keadaan bisa berubah adalah musuh kode multithreaded. Jika nilai tidak dapat diubah secara default, pemrogram tidak perlu khawatir tentang satu utas yang memutasi nilai status bersama di antara dua utas, sehingga ia menghilangkan seluruh kelas bug multithreading yang terkait dengan kondisi balapan. Karena tidak ada kondisi balapan, maka tidak ada alasan untuk menggunakan kunci juga, jadi ketidakmampuan menghilangkan seluruh kelas bug lain yang terkait dengan deadlock juga.

Itulah alasan utama mengapa pemrograman fungsional penting, dan mungkin yang terbaik untuk naik kereta pemrograman fungsional. Ada juga banyak manfaat lain, termasuk debugging yang disederhanakan (yaitu fungsi murni dan tidak bermutasi di bagian lain aplikasi), kode lebih ekspresif dan ekspresif, kode boilerplate lebih sedikit dibandingkan dengan bahasa yang sangat tergantung pada pola desain, dan kompiler dapat lebih agresif mengoptimalkan kode Anda.

Juliet
sumber
5
Saya yang kedua! Saya percaya pemrograman fungsional akan digunakan lebih luas di masa depan karena kesesuaiannya dengan pemrograman paralel.
Ray Hidayat
@ Ray: Saya juga akan menambahkan pemrograman terdistribusi!
Anton Tykhyy
Sebagian besar itu benar, kecuali untuk debugging. Umumnya lebih sulit di haskell, karena Anda tidak memiliki tumpukan panggilan nyata, hanya tumpukan pola-cocok. Dan jauh lebih sulit untuk memprediksi bagaimana kode Anda berakhir.
hasufell
3
Juga, pemrograman fungsional tidak benar-benar tentang "stateless". Rekursi sudah merupakan keadaan (lokal) implisit dan merupakan hal utama yang kami lakukan di haskell. Itu menjadi jelas setelah Anda menerapkan beberapa algoritma non-sepele dalam haskell idiomatik (misalnya hal-hal geometri komputasi) dan bersenang-senang men-debug itu.
hasufell
2
Tidak suka menyamakan kewarganegaraan dengan FP. Banyak program FP diisi dengan status, itu hanya ada dalam penutupan daripada objek.
mikemaccana
46

Semakin banyak bagian dari program Anda yang tidak memiliki kewarganegaraan, semakin banyak cara untuk menyatukan potongan-potongan tanpa mengalami kerusakan . Kekuatan paradigma kewarganegaraan tidak terletak pada kewarganegaraan (atau kemurnian) semata , tetapi kemampuan yang diberikannya kepada Anda untuk menulis fungsi-fungsi yang kuat dan dapat digunakan kembali dan menggabungkannya.

Anda dapat menemukan tutorial yang bagus dengan banyak contoh di makalah John Hughes, Why Functional Programming Matters (PDF).

Anda akan sekumpulan lebih produktif, terutama jika Anda memilih bahasa fungsional yang juga memiliki tipe data aljabar dan pola yang cocok (Caml, SML, Haskell).

Norman Ramsey
sumber
Bukankah mixin juga menyediakan kode yang dapat digunakan kembali dengan cara yang mirip dengan OOP? Tidak menganjurkan OOP hanya mencoba memahami hal-hal sendiri.
mikemaccana
20

Banyak jawaban lain berfokus pada sisi kinerja (paralelisme) pemrograman fungsional, yang saya yakini sangat penting. Namun, Anda memang secara spesifik bertanya tentang produktivitas, seperti dalam, bisakah Anda memprogram hal yang sama lebih cepat dalam paradigma fungsional daripada dalam paradigma imperatif.

Saya benar-benar menemukan (dari pengalaman pribadi) bahwa pemrograman dalam F # cocok dengan cara saya berpikir yang lebih baik, sehingga lebih mudah. Saya pikir itu perbedaan terbesar. Saya telah memprogram dalam F # dan C #, dan ada jauh lebih sedikit "melawan bahasa" dalam F #, yang saya sukai. Anda tidak perlu memikirkan detail di F #. Inilah beberapa contoh dari apa yang saya temukan yang sangat saya nikmati.

Sebagai contoh, meskipun F # diketik secara statis (semua tipe diselesaikan pada waktu kompilasi), inferensi tipe mencari tahu tipe apa yang Anda miliki, jadi Anda tidak perlu mengatakannya. Dan jika tidak bisa mengetahuinya, secara otomatis membuat fungsi Anda / kelas / apa pun generik. Jadi, Anda tidak perlu menulis generik apa pun, semuanya otomatis. Saya menemukan itu berarti saya menghabiskan lebih banyak waktu untuk memikirkan masalah dan lebih sedikit bagaimana menerapkannya. Bahkan, setiap kali saya kembali ke C #, saya merasa saya benar-benar kehilangan inferensi tipe ini, Anda tidak pernah menyadari betapa mengganggu itu sampai Anda tidak perlu melakukannya lagi.

Juga di F #, alih-alih menulis loop, Anda memanggil fungsi. Ini adalah perubahan yang halus, tetapi signifikan, karena Anda tidak perlu memikirkan tentang loop build lagi. Sebagai contoh, berikut adalah sepotong kode yang akan melewati dan mencocokkan sesuatu (Saya tidak ingat apa, itu dari puzzle Euler proyek):

let matchingFactors =
    factors
    |> Seq.filter (fun x -> largestPalindrome % x = 0)
    |> Seq.map (fun x -> (x, largestPalindrome / x))

Saya menyadari bahwa melakukan filter maka peta (itu adalah konversi dari setiap elemen) di C # akan sangat sederhana, tetapi Anda harus berpikir di tingkat yang lebih rendah. Khususnya, Anda harus menulis loop itu sendiri, dan memiliki pernyataan if if Anda sendiri, dan hal-hal semacam itu. Sejak mempelajari F #, saya menyadari saya merasa lebih mudah untuk membuat kode dengan cara fungsional, di mana jika Anda ingin memfilter, Anda menulis "filter", dan jika Anda ingin memetakan, Anda menulis "peta", daripada mengimplementasikan masing-masing detail.

Saya juga suka operator |>, yang saya pikir memisahkan F # dari ocaml, dan mungkin bahasa fungsional lainnya. Ini operator pipa, ini memungkinkan Anda "menyalurkan" output dari satu ekspresi ke input ekspresi lain. Itu membuat kode mengikuti bagaimana saya berpikir lebih. Seperti dalam cuplikan kode di atas, artinya, "ambil urutan faktor, saring, lalu petakan." Ini adalah tingkat pemikiran yang sangat tinggi, yang tidak Anda dapatkan dalam bahasa pemrograman imperatif karena Anda begitu sibuk menulis perulangan dan pernyataan if. Itu satu hal yang paling saya rindukan setiap kali saya masuk ke bahasa lain.

Jadi secara umum, meskipun saya dapat memprogram di C # dan F #, saya merasa lebih mudah untuk menggunakan F # karena Anda dapat berpikir pada tingkat yang lebih tinggi. Saya berpendapat bahwa karena detail yang lebih kecil dihapus dari pemrograman fungsional (setidaknya di F #), saya lebih produktif.

Sunting : Saya melihat di salah satu komentar yang Anda minta contoh "negara" dalam bahasa pemrograman fungsional. F # dapat ditulis secara imperatif, jadi inilah contoh langsung bagaimana Anda dapat memiliki status yang dapat berubah dalam F #:

let mutable x = 5
for i in 1..10 do
    x <- x + i
Ray Hidayat
sumber
1
Saya setuju dengan posting Anda secara umum, tetapi |> tidak ada hubungannya dengan pemrograman fungsional per se. Sebenarnya, a |> b (p1, p2)itu hanya gula sintaksis b (a, p1, p2). Pasangkan ini dengan asosiasi kanan dan Anda sudah mendapatkannya.
Anton Tykhyy
2
Benar, saya harus mengakui bahwa mungkin banyak pengalaman positif saya dengan F # lebih banyak berkaitan dengan F # daripada hubungannya dengan pemrograman fungsional. Tapi tetap saja, ada korelasi kuat antara keduanya, dan meskipun hal-hal seperti inferensi jenis dan |> bukan pemrograman fungsional semata, tentu saya akan mengklaim mereka "pergi dengan wilayah itu." Setidaknya secara umum.
Ray Hidayat
|> hanyalah fungsi infiks tingkat tinggi lainnya, dalam hal ini operator fungsi-aplikasi. Mendefinisikan orde tinggi Anda sendiri, operator infiks jelas merupakan bagian dari pemrograman fungsional (kecuali jika Anda seorang Schemer). Haskell memiliki $ nya yang sama kecuali informasi dalam pipa mengalir kanan ke kiri.
Norman Ramsey
15

Pertimbangkan semua bug sulit yang telah Anda habiskan untuk debugging lama.

Sekarang, berapa banyak bug yang disebabkan oleh "interaksi yang tidak disengaja" antara dua komponen program yang terpisah? (Hampir semua bug threading memiliki formulir ini: perlombaan yang melibatkan penulisan data bersama, kebuntuan, ... Selain itu, umum untuk menemukan perpustakaan yang memiliki beberapa efek tak terduga pada keadaan global, atau membaca / menulis registri / lingkungan, dll.) I akan berpendapat bahwa setidaknya 1 dari 3 'hard bug' termasuk dalam kategori ini.

Sekarang jika Anda beralih ke pemrograman stateless / immutable / pure, semua bug itu hilang. Anda akan disajikan dengan beberapa tantangan baru, bukan (misalnya ketika Anda melakukan ingin modul yang berbeda untuk berinteraksi dengan lingkungan), tapi dalam bahasa seperti Haskell, mereka interaksi mendapatkan eksplisit tereifikasi ke dalam sistem jenis, yang berarti Anda hanya dapat melihat jenis fungsi dan alasan tentang jenis interaksi yang dapat dimilikinya dengan program lainnya.

Itulah kemenangan besar dari IMO 'kekekalan'. Di dunia yang ideal, kita semua akan merancang API yang hebat dan bahkan ketika semuanya bisa berubah, efeknya akan lokal dan didokumentasikan dengan baik dan interaksi 'tak terduga' akan dijaga seminimal mungkin. Di dunia nyata, ada banyak API yang berinteraksi dengan keadaan global dalam berbagai cara, dan ini adalah sumber bug yang paling merusak. Bercita-cita untuk kewarganegaraan adalah bercita-cita untuk menyingkirkan interaksi yang tidak disengaja / implisit / di belakang layar di antara komponen.

Brian
sumber
6
Seseorang pernah berkata bahwa menimpa nilai yang bisa berubah berarti Anda secara eksplisit mengumpulkan / membebaskan nilai sebelumnya. Dalam beberapa kasus bagian lain dari program tidak selesai menggunakan nilai itu. Ketika nilai tidak dapat dimutasi, kelas bug ini juga hilang.
shapr
8

Salah satu keuntungan dari fungsi stateless adalah mereka mengizinkan precalculation atau caching dari nilai-nilai pengembalian fungsi. Bahkan beberapa kompiler C memungkinkan Anda untuk secara eksplisit menandai fungsi sebagai stateless untuk meningkatkan optimisabilitasnya. Seperti yang banyak dicatat, fungsi stateless jauh lebih mudah diparalelkan.

Tetapi efisiensi bukan satu-satunya perhatian. Fungsi murni lebih mudah untuk menguji dan men-debug karena apa pun yang mempengaruhinya dinyatakan secara eksplisit. Dan ketika memprogram dalam bahasa fungsional, orang terbiasa membuat sesedikit mungkin fungsi "kotor" (dengan I / O, dll.). Memisahkan hal-hal stateful dengan cara ini adalah cara yang baik untuk merancang program, bahkan dalam bahasa yang tidak begitu fungsional.

Bahasa fungsional dapat membutuhkan waktu untuk "mendapatkan", dan sulit untuk menjelaskan kepada seseorang yang belum melalui proses itu. Tetapi kebanyakan orang yang bertahan cukup lama akhirnya menyadari bahwa kerepotan itu sepadan, bahkan jika mereka tidak banyak menggunakan bahasa fungsional.

Artelius
sumber
Bagian pertama itu adalah hal yang sangat menarik, saya tidak pernah memikirkan hal itu sebelumnya. Terima kasih!
Sasha Chedygov
Misalkan Anda memiliki sin(PI/3)dalam kode Anda, di mana PI adalah konstanta, kompilator dapat mengevaluasi fungsi ini pada waktu kompilasi dan menanamkan hasilnya dalam kode yang dihasilkan.
Artelius
6

Tanpa status, sangat mudah untuk memparalelkan kode Anda (karena CPU dibuat dengan semakin banyak inti, ini sangat penting).

Zifre
sumber
Ya, saya sudah pasti melihatnya. Model konkurensi Erlang sangat menarik. Namun, pada titik ini saya tidak terlalu peduli tentang konkurensi dan produktivitas. Apakah ada bonus produktivitas dari pemrograman tanpa status?
Sasha Chedygov
2
@musicfreak, tidak ada bonus produktivitas. Tetapi sebagai catatan, bahasa FP modern masih memungkinkan Anda menggunakan status jika Anda benar-benar membutuhkannya.
Tidak diketahui
Betulkah? Bisakah Anda memberikan contoh negara dalam bahasa fungsional, supaya saya bisa melihat bagaimana hal itu dilakukan?
Sasha Chedygov
Lihat State Monad di Haskell - book.realworldhaskell.org/read/monads.html#x_NZ
rampion
4
@ Tidak Diketahui: Saya tidak setuju. Pemrograman tanpa status mengurangi terjadinya bug yang disebabkan oleh interaksi yang tidak terduga / tidak diinginkan dari berbagai komponen. Ini juga mendorong desain yang lebih baik (lebih dapat digunakan kembali, pemisahan mekanisme dan kebijakan, dan hal-hal semacam itu). Ini tidak selalu sesuai untuk tugas yang dihadapi tetapi dalam beberapa kasus benar-benar bersinar.
Artelius
6

Aplikasi web stateless sangat penting ketika Anda mulai memiliki lalu lintas yang lebih tinggi.

Mungkin ada banyak data pengguna yang tidak ingin Anda simpan di sisi klien karena alasan keamanan misalnya. Dalam hal ini Anda perlu menyimpannya di sisi server. Anda dapat menggunakan sesi default aplikasi web tetapi jika Anda memiliki lebih dari satu instance aplikasi Anda perlu memastikan bahwa setiap pengguna selalu diarahkan ke instance yang sama.

Load balancers sering memiliki kemampuan untuk memiliki 'sesi lengket' di mana load balancer tahu bagaimana server untuk mengirim permintaan pengguna. Namun ini tidak ideal, misalnya artinya setiap kali Anda me-restart aplikasi web Anda, semua pengguna yang terhubung akan kehilangan sesi mereka.

Pendekatan yang lebih baik adalah dengan menyimpan sesi di belakang server web dalam semacam penyimpanan data, hari ini ada banyak produk nosql besar yang tersedia untuk ini (redis, mongo, elasticsearch, memcached). Dengan cara ini server web tidak memiliki kewarganegaraan tetapi Anda masih memiliki sisi server negara dan ketersediaan status ini dapat dikelola dengan memilih pengaturan datastore yang tepat. Penyimpanan data ini biasanya memiliki redundansi yang besar sehingga hampir selalu memungkinkan untuk membuat perubahan pada aplikasi web Anda dan bahkan penyimpanan data tanpa berdampak pada pengguna.

shmish111
sumber
5

Saya menulis posting hanya pada subjek ini beberapa waktu lalu: Pada Pentingnya Kemurnian .

naasking
sumber