Apakah pemrograman reaktif (fungsional) itu?

1148

Saya telah membaca artikel Wikipedia tentang pemrograman reaktif . Saya juga membaca artikel kecil tentang pemrograman reaktif fungsional . Uraiannya cukup abstrak.

  1. Apa arti pemrograman reaktif fungsional (FRP) dalam praktek?
  2. Terdiri dari apakah pemrograman reaktif (berlawanan dengan pemrograman non-reaktif?)?

Latar belakang saya dalam bahasa imperatif / OO, jadi penjelasan yang berhubungan dengan paradigma ini akan dihargai.

JtR
sumber
159
inilah seorang pria dengan imajinasi aktif dan keterampilan mendongeng yang baik menangani semuanya. paulstovell.com/reactive-programming
melaos
39
Seseorang benar-benar perlu menulis "Pemrograman Reaktif Fungsional Untuk Dummies" untuk kita semua autodidak di sini. Setiap sumber yang saya temukan, bahkan Elm, tampaknya berasumsi Anda mendapatkan gelar Master di CS dalam lima tahun terakhir. Mereka yang memiliki pengetahuan tentang FRP tampaknya benar-benar kehilangan kemampuan untuk melihat masalah ini dari sudut pandang yang naif, sesuatu yang penting untuk pengajaran, pelatihan dan penginjilan.
TechZen
26
Pengantar FRP lain yang luar biasa: Pengantar Pemrograman Reaktif yang Anda lewatkan oleh rekan saya André
Jonik
5
Salah satu yang terbaik yang pernah saya lihat, Contoh berbasis: gist.github.com/staltz/868e7e9bc2a7b8c1f754
Razmig
2
Saya menemukan analogi spreadsheet sangat membantu sebagai kesan kasar pertama (lihat jawaban Bob: stackoverflow.com/a/1033066/1593924 ). Sel spreadsheet bereaksi terhadap perubahan pada sel lain (menarik) tetapi tidak menjangkau dan mengubah yang lain (tidak mendorong). Hasil akhirnya adalah bahwa Anda dapat mengubah satu sel dan jutaan lainnya 'secara mandiri' memperbarui tampilan mereka sendiri.
Jon Coombs

Jawaban:

931

Jika Anda ingin merasakan FRP, Anda bisa mulai dengan tutorial Fran lama dari tahun 1998, yang memiliki ilustrasi animasi. Untuk makalah, mulailah dengan Animasi Reaktif Fungsional dan kemudian tindak lanjuti tautan pada tautan publikasi di halaman rumah saya dan tautan FRP pada wiki Haskell .

Secara pribadi, saya suka berpikir tentang apa arti FRP sebelum membahas bagaimana itu mungkin diterapkan. (Kode tanpa spesifikasi adalah jawaban tanpa pertanyaan dan dengan demikian "bahkan tidak salah".) Jadi saya tidak menggambarkan FRP dalam istilah representasi / implementasi seperti yang dilakukan Thomas K dalam jawaban lain (grafik, simpul, tepi, tembak, eksekusi, dll). Ada banyak gaya implementasi mungkin, tapi tidak ada implementasi mengatakan apa FRP adalah .

Saya beresonansi dengan deskripsi sederhana Laurence G bahwa FRP adalah tentang "tipe data yang mewakili nilai 'dari waktu ke waktu'". Pemrograman imperatif konvensional menangkap nilai-nilai dinamis ini hanya secara tidak langsung, melalui keadaan dan mutasi. Sejarah lengkap (masa lalu, sekarang, masa depan) tidak memiliki representasi kelas satu. Selain itu, hanya nilai-nilai yang berkembang secara diskrit yang dapat (secara tidak langsung) ditangkap, karena paradigma imperatif bersifat diskrit sementara. Sebaliknya, FRP menangkap nilai-nilai yang berkembang ini secara langsung dan tidak mengalami kesulitan dengan nilai-nilai yang terus berkembang.

FRP juga tidak biasa karena berbarengan tanpa bertabrakan dengan sarang tikus teori & pragmatis yang menjangkiti konkurensi imperatif. Secara semantik, konkurensi FRP adalah berbutir halus , tegas , dan berkelanjutan . (Saya berbicara tentang makna, bukan implementasi. Suatu implementasi mungkin melibatkan atau tidak melibatkan konkurensi atau paralelisme.) Penentuan semantik sangat penting untuk penalaran, baik yang ketat maupun informal. Sementara concurrency menambah kompleksitas yang luar biasa pada pemrograman imperatif (karena interleaving nondeterministic), itu mudah di FRP.

Jadi, apa itu FRP? Anda bisa menciptakannya sendiri. Mulailah dengan ide-ide ini:

  • Nilai dinamis / berkembang (yaitu, nilai "dari waktu ke waktu") adalah nilai kelas pertama dalam dirinya sendiri. Anda dapat mendefinisikan dan menggabungkannya, meneruskannya ke & keluar dari fungsi. Saya menyebut hal-hal ini "perilaku".

  • Perilaku dibangun dari beberapa primitif, seperti perilaku konstan (statis) dan waktu (seperti jam), dan kemudian dengan kombinasi berurutan dan paralel. n perilaku digabungkan dengan menerapkan fungsi n-ary (pada nilai statis), "point-wise", yaitu, terus menerus seiring waktu.

  • Untuk menjelaskan fenomena diskrit, miliki tipe (keluarga) lain dari "peristiwa", yang masing-masing memiliki aliran (terbatas atau tak terbatas) dari kejadian. Setiap kejadian memiliki waktu dan nilai yang terkait.

  • Untuk menghasilkan kosakata komposisi yang darinya semua perilaku dan peristiwa dapat dibangun, mainkan dengan beberapa contoh. Terus mendekonstruksi menjadi potongan-potongan yang lebih umum / sederhana.

  • Agar Anda tahu bahwa Anda berada di tanah yang kokoh, berikan seluruh model fondasi komposisional, menggunakan teknik semantik denotasional, yang hanya berarti bahwa (a) setiap jenis memiliki tipe matematika "makna" sederhana & tepat yang sesuai, dan ( b) setiap primitif dan operator memiliki makna yang sederhana & tepat sebagai fungsi dari makna konstituen. Jangan sekali-kali mencampurkan pertimbangan implementasi ke dalam proses eksplorasi Anda. Jika uraian ini tidak benar bagi Anda, lihat (a) Desain denotasi dengan morfisme kelas tipe , (b) pemrograman reaktif fungsional tarikan-tarik (mengabaikan bit implementasi), dan (c) halaman wikibooks Denotational Semantics Haskell wikibooks. Berhati-hatilah bahwa semantik denotasional memiliki dua bagian, dari dua pendirinya Christopher Strachey dan Dana Scott: bagian Strachey yang lebih mudah & lebih bermanfaat dan bagian Scott yang lebih sulit dan kurang bermanfaat.

Jika Anda tetap berpegang pada prinsip-prinsip ini, saya berharap Anda akan mendapatkan sesuatu yang kurang lebih dalam semangat FRP.

Dari mana saya mendapatkan prinsip-prinsip ini? Dalam desain perangkat lunak, saya selalu mengajukan pertanyaan yang sama: "apa artinya?". Semantik denotasional memberi saya kerangka kerja yang tepat untuk pertanyaan ini, dan yang cocok dengan estetika saya (tidak seperti semantik operasional atau aksiomatik, keduanya membuat saya tidak puas). Jadi saya bertanya pada diri sendiri apa itu perilaku? Saya segera menyadari bahwa sifat sementara dari perhitungan imperatif adalah akomodasi untuk gaya mesin tertentu , daripada deskripsi alami perilaku itu sendiri. Deskripsi akurat paling sederhana dari perilaku yang dapat saya pikirkan hanyalah "fungsi waktu (kontinu)", jadi itulah model saya. Menyenangkan, model ini menangani konkurensi deterministik yang berkesinambungan dengan mudah dan anggun.

Sudah cukup sulit untuk mengimplementasikan model ini dengan benar dan efisien, tapi itu cerita lain.

Conal
sumber
78
Saya telah menyadari pemrograman reaktif fungsional. Tampaknya terkait dengan penelitian saya sendiri (dalam grafik statistik interaktif) dan saya yakin banyak ide akan membantu pekerjaan saya. Namun, saya merasa sangat sulit untuk melewati bahasa - haruskah saya benar-benar belajar tentang "semantik denotasi" dan "tipe morfisme kelas" untuk memahami apa yang terjadi? Pengantar khalayak umum tentang topik ini akan sangat berguna.
hadley
212
@Conal: Anda jelas tahu apa yang Anda bicarakan, tetapi bahasa Anda mengandaikan saya memiliki gelar doktor dalam matematika komputasional, yang saya tidak tahu. Saya memang memiliki latar belakang dalam rekayasa sistem dan 20+ tahun pengalaman dengan komputer dan bahasa pemrograman, masih saya merasa respons Anda membuat saya bingung. Saya menantang Anda untuk mengirim ulang balasan Anda dalam bahasa Inggris ;-)
mindplay.dk
50
@ minplay.dk: Pernyataan Anda tidak memberi saya banyak informasi tentang hal-hal tertentu yang tidak Anda pahami, dan saya enggan membuat tebakan liar tentang bagian bahasa Inggris apa yang Anda cari. Namun, saya mengundang Anda untuk mengatakan secara spesifik aspek apa dari penjelasan saya di atas yang sedang Anda hadapi, sehingga saya dan orang lain dapat membantu Anda. Misalnya, apakah ada kata-kata tertentu yang ingin Anda definisikan atau konsep-konsep yang ingin Anda tambahkan referensi? Saya benar-benar suka meningkatkan kejelasan dan aksesibilitas tulisan saya - tanpa membuatnya mati.
Conal
27
"Penentuan" / "determinasi" berarti ada satu nilai yang benar dan terdefinisi dengan baik. Sebaliknya, hampir semua bentuk concurrency imperatif dapat memberikan jawaban yang berbeda, tergantung pada penjadwal atau apakah Anda sedang melihat atau tidak, dan mereka bahkan dapat menemui jalan buntu. "Semantik" (dan lebih khusus lagi "denotasional") mengacu pada nilai ("denotasi") dari ekspresi atau representasi, berbeda dengan "operasional" (bagaimana jawaban dihitung atau berapa banyak ruang dan / atau waktu yang dikonsumsi oleh apa jenis mesin).
Conal
18
Saya setuju dengan @ mindplay.dk meskipun saya tidak bisa membual karena telah lama berada di lapangan. Meskipun sepertinya Anda tahu apa yang Anda bicarakan, itu tidak memberi saya pemahaman yang cepat, singkat dan sederhana tentang apa ini, karena saya cukup manja untuk mengharapkan pada SO. Jawaban ini terutama mengarahkan saya ke banyak pertanyaan baru tanpa benar-benar menjawab yang pertama. Saya berharap bahwa berbagi pengalaman masih relatif bodoh di lapangan dapat memberi Anda wawasan tentang betapa sederhana dan singkatnya Anda benar-benar perlu. Saya berasal dari latar belakang yang sama dengan OP, btw.
Aske B.
739

Dalam pemrograman fungsional murni, tidak ada efek samping. Untuk banyak jenis perangkat lunak (misalnya, apa pun dengan interaksi pengguna) efek samping diperlukan pada tingkat tertentu.

Salah satu cara untuk mendapatkan efek samping seperti perilaku sambil tetap mempertahankan gaya fungsional adalah dengan menggunakan pemrograman reaktif fungsional. Ini adalah kombinasi dari pemrograman fungsional, dan pemrograman reaktif. (Artikel Wikipedia yang Anda tautkan adalah tentang yang terakhir.)

Ide dasar di balik pemrograman reaktif adalah bahwa ada tipe data tertentu yang mewakili nilai "dari waktu ke waktu". Komputasi yang melibatkan nilai-nilai perubahan-dari-waktu ini sendiri akan memiliki nilai-nilai yang berubah seiring waktu.

Misalnya, Anda bisa mewakili koordinat mouse sebagai pasangan nilai integer-over-time. Katakanlah kita memiliki sesuatu seperti (ini adalah pseudo-code):

x = <mouse-x>;
y = <mouse-y>;

Setiap saat, x dan y akan memiliki koordinat mouse. Tidak seperti pemrograman non-reaktif, kita hanya perlu membuat tugas ini sekali, dan variabel x dan y akan tetap "up to date" secara otomatis. Inilah sebabnya mengapa pemrograman reaktif dan pemrograman fungsional bekerja sangat baik bersama-sama: pemrograman reaktif menghilangkan kebutuhan untuk mengubah variabel sambil tetap membiarkan Anda melakukan banyak hal yang dapat Anda capai dengan mutasi variabel.

Jika kita melakukan beberapa perhitungan berdasarkan ini, nilai yang dihasilkan juga akan menjadi nilai yang berubah seiring waktu. Sebagai contoh:

minX = x - 16;
minY = y - 16;
maxX = x + 16;
maxY = y + 16;

Dalam contoh ini, minXakan selalu 16 kurang dari koordinat x dari pointer mouse. Dengan pustaka yang sadar reaktif, Anda dapat mengatakan sesuatu seperti:

rectangle(minX, minY, maxX, maxY)

Dan kotak 32x32 akan digambar di sekitar penunjuk tetikus dan akan melacaknya ke mana pun ia bergerak.

Berikut ini adalah makalah yang cukup bagus tentang pemrograman reaktif fungsional .

Laurence Gonsalves
sumber
25
Jadi pemrograman reaktif adalah bentuk pemrograman deklaratif?
troelskn
31
> Jadi pemrograman reaktif adalah bentuk pemrograman deklaratif? Pemrograman reaktif fungsional adalah suatu bentuk pemrograman fungsional, yang merupakan bentuk pemrograman deklaratif.
Conal
7
@ user712092 Tidak juga, tidak. Misalnya, jika saya memanggil sqrt(x)C dengan makro Anda, itu hanya menghitung sqrt(mouse_x())dan memberi saya dua kali lipat. Dalam sistem reaktif fungsional sejati, sqrt(x)akan mengembalikan "ganda dari waktu ke waktu" baru. Jika Anda mencoba untuk mensimulasikan sistem FR dengan #defineAnda cukup banyak harus bersumpah variabel yang mendukung makro. Sistem FR juga biasanya hanya menghitung ulang hal-hal ketika perlu dihitung ulang, sementara menggunakan makro berarti Anda akan terus mengevaluasi ulang semuanya, sampai ke subekspresi.
Laurence Gonsalves
4
"Untuk banyak jenis perangkat lunak (misalnya, apa pun dengan interaksi pengguna) efek samping diperlukan pada tingkat tertentu." Dan mungkin hanya pada level implementasi. Ada banyak efek samping dalam implementasi pemrograman fungsional murni, malas, dan salah satu keberhasilan paradigma adalah untuk menjaga banyak dari efek tersebut dari model pemrograman. Langkah saya sendiri ke antarmuka pengguna fungsional menunjukkan bahwa mereka juga dapat diprogram sepenuhnya tanpa efek samping.
Conal
4
@tieTYT x tidak pernah dipindahkan / dimutasi. Nilai x adalah urutan nilai dari waktu ke waktu. Cara lain untuk melihatnya adalah bahwa alih-alih x memiliki nilai "normal", seperti angka, nilai x adalah (secara konseptual) fungsi yang membutuhkan waktu sebagai parameter. (Ini sedikit penyederhanaan yang berlebihan. Anda tidak dapat membuat nilai waktu yang memungkinkan Anda untuk memprediksi masa depan hal-hal seperti posisi mouse.)
Laurence Gonsalves
144

Cara mudah untuk mencapai intuisi pertama tentang seperti apa rasanya membayangkan program Anda adalah spreadsheet dan semua variabel Anda adalah sel. Jika salah satu sel dalam spreadsheet berubah, sel mana pun yang merujuk pada sel itu juga berubah. Sama saja dengan FRP. Sekarang bayangkan bahwa beberapa sel berubah sendiri (atau lebih tepatnya, diambil dari dunia luar): dalam situasi GUI, posisi mouse akan menjadi contoh yang baik.

Itu tentu merindukan banyak. Metafora rusak cukup cepat ketika Anda benar-benar menggunakan sistem FRP. Untuk satu, biasanya ada upaya untuk memodelkan kejadian diskrit juga (mis. Mouse diklik). Saya hanya menempatkan ini di sini untuk memberi Anda gambaran bagaimana rasanya.


sumber
3
Contoh yang sangat tepat. Sangat menyenangkan memiliki hal-hal teoretis, dan mungkin beberapa orang mendapatkan implikasi dari hal itu tanpa menggunakan contoh dasar, tetapi saya harus mulai dengan apa yang dilakukannya untuk saya, bukan apa yang abstrak. Apa yang baru-baru ini saya dapatkan (dari pembicaraan Rx oleh Netflix!) Adalah RP (atau Rx, bagaimanapun), membuat "nilai-nilai perubahan" ini menjadi kelas utama dan memungkinkan Anda mempertimbangkannya, atau menulis fungsi yang berhubungan dengan mereka. Tulis fungsi untuk membuat spreadsheet atau sel, jika mau. Dan itu menangani ketika nilai berakhir (hilang) dan memungkinkan Anda membersihkan secara otomatis.
Benjohn
Contoh ini menekankan perbedaan antara pemrograman yang digerakkan oleh peristiwa dan pendekatan reaktif, di mana Anda hanya mendeklarasikan dependensi untuk menggunakan perutean cerdas.
kinjelom
131

Bagi saya ini adalah tentang 2 arti simbol yang berbeda =:

  1. Dalam matematika x = sin(t)berarti, bahwa xadalah nama yang berbeda untuk sin(t). Jadi menulis x + ysama dengan sin(t) + y. Pemrograman reaktif fungsional seperti matematika dalam hal ini: jika Anda menulis x + y, itu dihitung dengan nilai apa pun tpada saat digunakan.
  2. Dalam bahasa pemrograman mirip-C (bahasa imperatif), x = sin(t)adalah sebuah tugas: itu berarti xmenyimpan nilai yang sin(t) diambil pada saat tugas tersebut.
pengguna712092
sumber
5
Penjelasan yang bagus. Saya pikir Anda juga bisa menambahkan bahwa "waktu" dalam arti FRP biasanya "setiap perubahan dari input eksternal". Kapan saja kekuatan eksternal mengubah input FRP, Anda telah "maju" waktu, dan menghitung ulang semuanya lagi yang dipengaruhi oleh perubahan.
Didier A.
4
Dalam matematika x = sin(t)berarti xnilai sin(t)untuk yang diberikan t. Ini bukan nama yang berbeda untuk sin(t)fungsi. Kalau tidak demikian x(t) = sin(t).
Dmitri Zaitsev
+ Tanda Dmitri Zaitsev Equals memiliki beberapa arti dalam matematika. Salah satunya adalah bahwa setiap kali Anda melihat sisi kiri Anda dapat bertukar dengan sisi kanan. Misalnya 2 + 3 = 5atau a**2 + b**2 = c**2.
user712092
71

OK, dari latar belakang pengetahuan dan dari membaca halaman Wikipedia yang Anda tunjuk, tampaknya pemrograman reaktif adalah sesuatu seperti komputasi aliran data tetapi dengan "rangsangan" eksternal tertentu yang memicu serangkaian node untuk diaktifkan dan melakukan komputasi mereka.

Ini sangat cocok untuk desain UI, misalnya, di mana menyentuh kontrol antarmuka pengguna (misalnya, kontrol volume pada aplikasi pemutaran musik) mungkin perlu memperbarui berbagai item tampilan dan volume aktual dari output audio. Saat Anda memodifikasi volume (slider, katakanlah) yang akan sesuai dengan memodifikasi nilai yang terkait dengan sebuah simpul dalam grafik yang diarahkan.

Berbagai node yang memiliki tepi dari simpul "nilai volume" itu akan secara otomatis dipicu dan setiap perhitungan dan pembaruan yang diperlukan secara alami akan beriak melalui aplikasi. Aplikasi "bereaksi" terhadap stimulus pengguna. Pemrograman reaktif fungsional hanya akan menjadi implementasi ide ini dalam bahasa fungsional, atau umumnya dalam paradigma pemrograman fungsional.

Untuk lebih lanjut tentang "komputasi aliran data", cari dua kata di Wikipedia atau menggunakan mesin pencari favorit Anda. Gagasan umumnya adalah ini: program adalah grafik node yang diarahkan, masing-masing melakukan beberapa perhitungan sederhana. Node-node ini terhubung satu sama lain melalui tautan grafik yang menyediakan output dari beberapa node ke input dari yang lain.

Ketika sebuah node menjalankan atau melakukan perhitungannya, node yang terhubung ke outputnya memiliki input yang sesuai "terpicu" atau "ditandai". Setiap simpul yang semua inputnya dipicu / ditandai / tersedia secara otomatis akan menyala. Grafik mungkin implisit atau eksplisit tergantung pada bagaimana pemrograman reaktif diterapkan.

Node dapat dilihat sebagai penembakan secara paralel, tetapi seringkali mereka dieksekusi secara serial atau dengan paralelisme terbatas (misalnya, mungkin ada beberapa thread yang mengeksekusinya). Contoh terkenal adalah Manchester Dataflow Machine , yang (IIRC) menggunakan arsitektur data yang ditandai untuk menjadwalkan eksekusi node dalam grafik melalui satu atau lebih unit eksekusi. Komputasi dataflow cukup cocok untuk situasi di mana memicu perhitungan secara asinkron menimbulkan kaskade komputasi bekerja lebih baik daripada mencoba untuk membuat eksekusi diatur oleh jam (atau jam).

Pemrograman reaktif mengimpor ide "kaskade eksekusi" ini dan tampaknya memikirkan program dengan cara seperti aliran data, tetapi dengan ketentuan bahwa beberapa node terhubung ke "dunia luar" dan arus eksekusi dipicu ketika sensor ini -seperti perubahan node. Eksekusi program kemudian akan terlihat seperti sesuatu yang analog dengan busur refleks yang kompleks. Program ini mungkin pada dasarnya sessile antara rangsangan atau mungkin pada dasarnya keadaan sessile antara rangsangan.

Pemrograman "non-reaktif" adalah pemrograman dengan pandangan yang sangat berbeda dari aliran eksekusi dan hubungan dengan input eksternal. Ini mungkin agak subyektif, karena orang cenderung tergoda untuk mengatakan apa pun yang merespons masukan eksternal "bereaksi" terhadap mereka. Tetapi melihat semangat dari hal itu, sebuah program yang memungut jajak pendapat suatu peristiwa pada interval tetap dan mengirimkan semua peristiwa yang ditemukan ke fungsi (atau utas) kurang reaktif (karena hanya memperhatikan input pengguna pada interval tetap). Sekali lagi, inilah semangatnya di sini: orang dapat membayangkan menempatkan implementasi pemungutan suara dengan interval pemungutan suara cepat ke dalam sistem pada tingkat yang sangat rendah dan memprogram dengan cara yang reaktif di atasnya.

Thomas Kammeyer
sumber
1
OK, ada beberapa jawaban bagus di atas sekarang. Haruskah saya menghapus posting saya? Jika saya melihat dua atau tiga orang mengatakan itu tidak menambahkan apa-apa, saya akan menghapusnya kecuali hitungannya yang membantu naik. Tidak ada gunanya meninggalkannya di sini kecuali jika itu menambah sesuatu yang bernilai.
Thomas Kammeyer
3
Anda telah menyebutkan aliran data, sehingga menambah beberapa nilai IMHO.
Rainer Joswig
Tampaknya itulah yang dimaksud dengan QML;)
mlvljr
3
Bagi saya, jawaban ini paling mudah dipahami, terutama karena penggunaan analog alami seperti "riak melalui aplikasi" dan "node seperti sensorik". Bagus!
Akseli Palén
1
Sayangnya, tautan Mesin Dataflow Manchester sudah mati.
Pac0
65

Setelah membaca banyak halaman tentang FRP saya akhirnya menemukan ini tulisan tentang FRP yang mencerahkan , akhirnya membuat saya mengerti apa sebenarnya FRP itu.

Saya kutip di bawah ini Heinrich Apfelmus (penulis pisang reaktif).

Apa inti dari pemrograman reaktif fungsional?

Jawaban umum adalah bahwa "FRP adalah semua tentang menggambarkan suatu sistem dalam hal fungsi yang bervariasi waktu daripada keadaan yang bisa berubah", dan itu tentu saja tidak salah. Ini adalah sudut pandang semantik. Tetapi menurut saya, jawaban yang lebih dalam, lebih memuaskan diberikan oleh kriteria sintaksis murni berikut:

Inti dari pemrograman reaktif fungsional adalah untuk menentukan perilaku dinamis dari suatu nilai sepenuhnya pada saat deklarasi.

Misalnya, ambil contoh penghitung: Anda memiliki dua tombol berlabel "Atas" dan "Turun" yang dapat digunakan untuk menambah atau mengurangi penghitung. Secara imperatif, Anda pertama-tama akan menentukan nilai awal dan kemudian mengubahnya setiap kali tombol ditekan; sesuatu seperti ini:

counter := 0                               -- initial value
on buttonUp   = (counter := counter + 1)   -- change it later
on buttonDown = (counter := counter - 1)

Intinya adalah bahwa pada saat deklarasi, hanya nilai awal untuk penghitung yang ditentukan; perilaku dinamis penghitung tersirat dalam sisa teks program. Sebaliknya, pemrograman reaktif fungsional menentukan seluruh perilaku dinamis pada saat deklarasi, seperti ini:

counter :: Behavior Int
counter = accumulate ($) 0
            (fmap (+1) eventUp
             `union` fmap (subtract 1) eventDown)

Setiap kali Anda ingin memahami dinamika penghitung, Anda hanya perlu melihat definisinya. Segala sesuatu yang dapat terjadi padanya akan muncul di sisi kanan. Ini sangat kontras dengan pendekatan imperatif di mana deklarasi selanjutnya dapat mengubah perilaku dinamis dari nilai yang dinyatakan sebelumnya.

Jadi, dalam pemahaman saya, program FRP adalah seperangkat persamaan: masukkan deskripsi gambar di sini

j diskrit: 1,2,3,4 ...

ftergantung pada tsehingga ini menggabungkan kemungkinan untuk memodelkan rangsangan eksternal

semua status program diringkas dalam variabel x_i

Perpustakaan FRP menangani kemajuan waktu, dengan kata lain, mengambil jke j+1.

Saya menjelaskan persamaan ini secara lebih rinci dalam video ini .

EDIT:

Sekitar 2 tahun setelah jawaban asli, baru-baru ini saya sampai pada kesimpulan bahwa implementasi FRP memiliki aspek penting lainnya. Mereka perlu (dan biasanya melakukan) memecahkan masalah praktis yang penting: pembatalan cache .

Persamaan untuk x_i-s menggambarkan grafik ketergantungan. Ketika beberapa x_iperubahan pada saat itu jmaka tidak semua nilai lain x_i'di j+1perlu diperbarui, jadi tidak semua dependensi perlu dihitung ulang karena beberapa x_i'mungkin independen dari x_i.

Selanjutnya, x_i-s yang melakukan perubahan dapat diperbarui secara bertahap. Sebagai contoh mari mempertimbangkan operasi peta f=g.map(_+1)di Scala, di mana fdan gadalah Listdari Ints. Berikut fsesuai dengan x_i(t_j)dan gadalah x_j(t_j). Sekarang jika saya menambahkan sebuah elemen ke gkemudian akan sia-sia untuk melakukan mapoperasi untuk semua elemen di g. Beberapa implementasi FRP (misalnya reflex-frp ) bertujuan untuk menyelesaikan masalah ini. Masalah ini juga dikenal sebagai komputasi inkremental.

Dengan kata lain, perilaku (the x_i-s) di FRP dapat dianggap sebagai perhitungan cache-ed. Adalah tugas mesin FRP untuk secara efisien membatalkan dan mengkomputasi ulang cache-s ini (the x_i-s) jika beberapa f_i-s memang berubah.

jhegedus
sumber
4
Saya ada di sana bersama Anda sampai Anda pergi dengan persamaan diskrit . Gagasan pendirian FRP adalah waktu terus menerus , di mana tidak ada " j+1". Sebaliknya, pikirkan fungsi waktu terus menerus. Seperti yang ditunjukkan oleh Newton, Leibniz, dan lainnya, seringkali sangat berguna (dan "alami" dalam arti literal) untuk menggambarkan fungsi-fungsi ini secara berbeda, tetapi terus menerus demikian, menggunakan integral dan sistem ODE. Jika tidak, Anda menggambarkan algoritme aproksimasi (dan yang buruk) alih-alih hal itu sendiri.
Conal
Layx bahasa templating dan kendala bahasa layx tampaknya mengekspresikan elemen FRP.
@Conal ini membuat saya bertanya-tanya bagaimana FRP berbeda dari ODE. Bagaimana mereka berbeda?
jhegedus
@ jhegedus Dalam integrasi itu (mungkin rekursif, yaitu, ODE) menyediakan salah satu blok bangunan FRP, bukan keseluruhan. Setiap elemen dari kosakata FRP (termasuk tetapi tidak terbatas pada integrasi) dijelaskan dengan tepat dalam hal waktu terus menerus. Apakah penjelasan itu membantu?
Conal
29

Penafian: jawaban saya adalah dalam konteks rx.js - pustaka 'pemrograman reaktif' untuk Javascript.

Dalam pemrograman fungsional, alih-alih mengulangi setiap item koleksi, Anda menerapkan fungsi urutan lebih tinggi (HoF) ke koleksi itu sendiri. Jadi ide di balik FRP adalah bahwa alih-alih memproses setiap peristiwa individu, buat aliran acara (diimplementasikan dengan * yang dapat diamati) dan menerapkan HoF sebagai gantinya. Dengan cara ini Anda dapat memvisualisasikan sistem sebagai jalur pipa data yang menghubungkan penerbit ke pelanggan.

Keuntungan utama menggunakan diamati adalah:
i) itu abstrak jauh dari kode Anda, misalnya, jika Anda ingin event handler dipecat hanya untuk setiap acara 'n', atau berhenti menembak setelah peristiwa 'n' pertama, atau mulai menembak hanya setelah peristiwa 'n' pertama, Anda bisa menggunakan HoF (filter, takeUntil, skip masing-masing) alih-alih mengatur, memperbarui, dan memeriksa penghitung.
ii) itu meningkatkan lokalitas kode - jika Anda memiliki 5 penangan acara yang berbeda mengubah keadaan komponen, Anda dapat menggabungkan yang dapat diobservasi mereka dan menentukan penangan peristiwa tunggal pada gabungan yang dapat diamati, sebagai gantinya secara efektif menggabungkan 5 penangan acara menjadi 1. Ini membuatnya sangat mudah dipikirkan tentang peristiwa apa di seluruh sistem Anda yang dapat memengaruhi komponen, karena semuanya hadir dalam satu penangan tunggal.

  • Sebuah Observable adalah dual dari Iterable.

Iterable adalah urutan yang dikonsumsi secara malas - setiap item ditarik oleh iterator setiap kali ingin menggunakannya, dan karenanya enumerasi didorong oleh konsumen.

Yang dapat diamati adalah urutan yang diproduksi secara malas - setiap item didorong ke pengamat setiap kali ditambahkan ke urutan, dan karenanya enumerasi didorong oleh produsen.

tldr
sumber
1
Terima kasih banyak atas definisi langsung dari yang dapat diamati dan perbedaannya dari iterables. Saya pikir seringkali sangat membantu untuk membandingkan konsep yang kompleks dengan konsep rangkapnya yang terkenal untuk mendapatkan pemahaman yang benar.
2
"Jadi ide di balik FRP adalah bahwa alih-alih memproses setiap peristiwa individu, buat aliran acara (diimplementasikan dengan * yang dapat diamati) dan sebaliknya menerapkan HoF untuk itu." Saya bisa saja salah tetapi saya percaya ini sebenarnya bukan FRP melainkan abstraksi yang bagus atas pola desain Pengamat yang memungkinkan untuk operasi fungsional melalui HoF (yang hebat!) Sementara masih dimaksudkan untuk digunakan dengan kode imperatif. Diskusi tentang topik - lambda-the-ultimate.org/node/4982
nqe
18

Kawan, ini ide brilian yang aneh! Mengapa saya tidak mencari tahu tentang ini pada tahun 1998? Bagaimanapun, inilah interpretasi saya tentang tutorial Fran . Saran dipersilahkan, saya berpikir untuk memulai mesin permainan berdasarkan ini.

import pygame
from pygame.surface import Surface
from pygame.sprite import Sprite, Group
from pygame.locals import *
from time import time as epoch_delta
from math import sin, pi
from copy import copy

pygame.init()
screen = pygame.display.set_mode((600,400))
pygame.display.set_caption('Functional Reactive System Demo')

class Time:
    def __float__(self):
        return epoch_delta()
time = Time()

class Function:
    def __init__(self, var, func, phase = 0., scale = 1., offset = 0.):
        self.var = var
        self.func = func
        self.phase = phase
        self.scale = scale
        self.offset = offset
    def copy(self):
        return copy(self)
    def __float__(self):
        return self.func(float(self.var) + float(self.phase)) * float(self.scale) + float(self.offset)
    def __int__(self):
        return int(float(self))
    def __add__(self, n):
        result = self.copy()
        result.offset += n
        return result
    def __mul__(self, n):
        result = self.copy()
        result.scale += n
        return result
    def __inv__(self):
        result = self.copy()
        result.scale *= -1.
        return result
    def __abs__(self):
        return Function(self, abs)

def FuncTime(func, phase = 0., scale = 1., offset = 0.):
    global time
    return Function(time, func, phase, scale, offset)

def SinTime(phase = 0., scale = 1., offset = 0.):
    return FuncTime(sin, phase, scale, offset)
sin_time = SinTime()

def CosTime(phase = 0., scale = 1., offset = 0.):
    phase += pi / 2.
    return SinTime(phase, scale, offset)
cos_time = CosTime()

class Circle:
    def __init__(self, x, y, radius):
        self.x = x
        self.y = y
        self.radius = radius
    @property
    def size(self):
        return [self.radius * 2] * 2
circle = Circle(
        x = cos_time * 200 + 250,
        y = abs(sin_time) * 200 + 50,
        radius = 50)

class CircleView(Sprite):
    def __init__(self, model, color = (255, 0, 0)):
        Sprite.__init__(self)
        self.color = color
        self.model = model
        self.image = Surface([model.radius * 2] * 2).convert_alpha()
        self.rect = self.image.get_rect()
        pygame.draw.ellipse(self.image, self.color, self.rect)
    def update(self):
        self.rect[:] = int(self.model.x), int(self.model.y), self.model.radius * 2, self.model.radius * 2
circle_view = CircleView(circle)

sprites = Group(circle_view)
running = True
while running:
    for event in pygame.event.get():
        if event.type == QUIT:
            running = False
        if event.type == KEYDOWN and event.key == K_ESCAPE:
            running = False
    screen.fill((0, 0, 0))
    sprites.update()
    sprites.draw(screen)
    pygame.display.flip()
pygame.quit()

Singkatnya: Jika setiap komponen dapat diperlakukan seperti angka, seluruh sistem dapat diperlakukan seperti persamaan matematika, bukan?

Dan Ross
sumber
1
Ini agak terlambat, tapi bagaimanapun ... Frag adalah permainan menggunakan FRP .
arx
14

Buku Paul Hudak, The Haskell School of Expression , tidak hanya pengantar yang bagus untuk Haskell, tetapi juga menghabiskan banyak waktu di FRP. Jika Anda seorang pemula dengan FRP, saya sangat merekomendasikannya untuk memberi Anda gambaran tentang bagaimana FRP bekerja.

Ada juga apa yang tampak seperti penulisan ulang baru buku ini (dirilis 2011, diperbarui 2014), The Haskell School of Music .

cjs
sumber
10

Menurut jawaban sebelumnya, tampaknya secara matematis, kita hanya berpikir dalam urutan yang lebih tinggi. Alih-alih memikirkan nilai x yang memiliki tipe X , kami memikirkan fungsi x : TX , di mana T adalah tipe waktu, baik itu bilangan asli, bilangan bulat atau kontinum. Sekarang ketika kita menulis y : = x + 1 dalam bahasa pemrograman, kita sebenarnya berarti persamaan y ( t ) = x ( t ) + 1.

Yuning
sumber
9

Bertindak seperti spreadsheet seperti yang disebutkan. Biasanya didasarkan pada kerangka acara yang digerakkan.

Seperti halnya semua "paradigma", kebaruan itu bisa diperdebatkan.

Dari pengalaman saya tentang jaringan aktor yang terdistribusi, dapat dengan mudah menjadi mangsa masalah umum konsistensi negara di seluruh jaringan node yaitu Anda berakhir dengan banyak osilasi dan terperangkap dalam loop aneh.

Ini sulit untuk dihindari karena beberapa semantik menyiratkan loop atau penyiaran referensial, dan bisa sangat kacau ketika jaringan aktor bertemu (atau tidak) pada beberapa negara yang tidak dapat diprediksi.

Demikian pula, beberapa negara mungkin tidak dapat dijangkau, walaupun memiliki batas yang jelas, karena negara global menjauhi solusi. 2 + 2 mungkin atau mungkin tidak menjadi 4 tergantung pada kapan 2 menjadi 2, dan apakah mereka tetap seperti itu. Spreadsheet memiliki jam sinkron dan deteksi loop. Aktor terdistribusi umumnya tidak.

Semuanya menyenangkan :).

emperorz
sumber
7

Artikel karya Andre Staltz ini adalah penjelasan terbaik dan paling jelas yang pernah saya lihat sejauh ini.

Beberapa kutipan dari artikel:

Pemrograman reaktif adalah pemrograman dengan aliran data asinkron.

Selain itu, Anda diberi kotak alat fungsi yang luar biasa untuk menggabungkan, membuat, dan memfilter aliran mana pun.

Berikut adalah contoh diagram fantastis yang merupakan bagian dari artikel:

Klik diagram aliran acara

GreenGiant
sumber
5

Ini adalah tentang transformasi data matematika dari waktu ke waktu (atau mengabaikan waktu).

Dalam kode ini berarti kemurnian fungsional dan pemrograman deklaratif.

Bug negara adalah masalah besar dalam paradigma imperatif standar. Berbagai bit kode dapat mengubah beberapa status bersama pada "waktu" yang berbeda dalam eksekusi program. Ini sulit dihadapi.

Dalam FRP Anda menggambarkan (seperti dalam pemrograman deklaratif) bagaimana data berubah dari satu negara ke negara lain dan apa yang memicu itu. Ini memungkinkan Anda untuk mengabaikan waktu karena fungsi Anda hanya bereaksi terhadap inputnya dan menggunakan nilainya saat ini untuk membuat yang baru. Ini berarti bahwa keadaan terkandung dalam grafik (atau pohon) node transformasi dan fungsional murni.

Ini secara besar-besaran mengurangi kompleksitas dan waktu debugging.

Pikirkan perbedaan antara A = B + C dalam matematika dan A = B + C dalam suatu program. Dalam matematika Anda menggambarkan hubungan yang tidak akan pernah berubah. Dalam sebuah program, dikatakan bahwa "Saat ini" A adalah B + C. Tetapi perintah selanjutnya mungkin B ++ dalam hal ini A tidak sama dengan B + C. Dalam matematika atau pemrograman deklaratif A akan selalu sama dengan B + C tidak peduli berapa kali Anda bertanya.

Jadi dengan menghapus kompleksitas status bersama dan mengubah nilai seiring waktu. Program Anda jauh lebih mudah untuk dipikirkan.

Sebuah EventStream adalah EventStream + beberapa fungsi transformasi.

Perilaku adalah EventStream + Nilai tertentu dalam memori.

Ketika acara diaktifkan, nilai diperbarui dengan menjalankan fungsi transformasi. Nilai yang dihasilkan ini disimpan dalam memori perilaku.

Perilaku dapat dikomposisikan untuk menghasilkan perilaku baru yang merupakan transformasi pada perilaku lain. Nilai yang dikomposisikan ini akan dihitung ulang sebagai peristiwa input (perilaku) kebakaran.

"Karena pengamat tidak memiliki kewarganegaraan, kita sering membutuhkan beberapa dari mereka untuk mensimulasikan mesin keadaan seperti pada contoh drag. Kita harus menyimpan keadaan di mana ia dapat diakses oleh semua pengamat yang terlibat seperti dalam jalur variabel di atas."

Kutipan dari - Mengabaikan Pola Observer http://infoscience.epfl.ch/record/148043/files/DeprecatingObserversTR2010.pdf

Jay Shepherd
sumber
Ini persis seperti yang saya rasakan tentang pemrograman deklaratif, dan Anda hanya menggambarkan idenya lebih baik daripada saya.
neevek
2

Penjelasan singkat dan jelas tentang Pemrograman Reaktif muncul di Cyclejs - Pemrograman Reaktif , ia menggunakan sampel sederhana dan visual.

A [modul / Komponen / objek] reaktif berarti bertanggung jawab penuh untuk mengelola keadaannya sendiri dengan bereaksi terhadap peristiwa eksternal.

Apa manfaat dari pendekatan ini? Ini adalah Inversion of Control , terutama karena [modul / Komponen / objek] bertanggung jawab untuk dirinya sendiri, meningkatkan enkapsulasi menggunakan metode pribadi terhadap yang publik.

Ini adalah titik awal yang baik, bukan sumber pengetahuan lengkap. Dari sana Anda bisa melompat ke kertas yang lebih kompleks dan dalam.

pdorgambide
sumber
0

Lihat Rx, Ekstensi Reaktif untuk .NET. Mereka menunjukkan bahwa dengan IEnumerable pada dasarnya Anda 'menarik' dari aliran. Pertanyaan Linq atas IQueryable / IEnumerable adalah operasi yang mengatur 'menyedot' hasil dari satu set. Tetapi dengan operator yang sama lebih dari IObservable Anda dapat menulis permintaan Linq yang 'bereaksi'.

Misalnya, Anda bisa menulis kueri Linq seperti (dari m di MyObservableSetOfMouseMovements di mana mX <100 dan mY <100 pilih Point baru (mX, mY)).

dan dengan ekstensi Rx, itu saja: Anda memiliki kode UI yang bereaksi terhadap aliran masuk gerakan mouse dan menarik setiap kali Anda berada di kotak 100.100 ...

Penjaga
sumber
0

FRP adalah kombinasi dari pemrograman Fungsional (paradigma pemrograman yang dibangun di atas gagasan segalanya adalah fungsi) dan paradigma pemrograman reaktif (dibangun di atas gagasan bahwa semuanya adalah aliran (pengamat dan filosofi yang dapat diamati)). Itu seharusnya menjadi yang terbaik di dunia.

Lihat posting Andre Staltz pada pemrograman reaktif untuk memulai.

Krishna Ganeriwal
sumber