Pertanyaan Apakah (fungsional) pemrograman reaktif?


Saya sudah membaca artikel Wikipedia di pemrograman reaktif. Saya juga sudah membaca artikel kecil di pemrograman reaktif fungsional. Uraiannya cukup abstrak.

  1. Apa artinya fungsional reaktif programming (FRP) dalam prakteknya?
  2. Apa pemrograman reaktif (sebagai lawan dari pemrograman non-reaktif?) Terdiri dari?

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


1149
2018-06-22 16:41


asal


Jawaban:


Jika Anda ingin merasakan FRP, Anda bisa mulai dengan yang lama Tutorial Fran dari 1998, yang memiliki ilustrasi animasi. Untuk kertas, mulailah dengan Animasi Reaktif Fungsional dan kemudian menindaklanjuti tautan pada tautan publikasi di laman beranda saya dan FRP tautan di Haskell wiki.

Secara pribadi, saya suka berpikir tentang apa FRP cara sebelum membahas bagaimana penerapannya. (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 kemungkinan gaya implementasi, tetapi tidak ada implementasi yang mengatakan apa FRP aku s.

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

FRP juga tidak biasa dalam hal ini bersamaan tanpa bertabrakan dengan sarang tikus teoritis & pragmatis yang mengganggu konkordansi imperatif. Secara semantis, konkurensi FRP adalah berbutir halus, tentu, dan kontinu. (Saya berbicara tentang makna, bukan implementasi. Suatu implementasi mungkin atau mungkin tidak melibatkan konkurensi atau paralelisme.) Penentuan semantik sangat penting untuk penalaran, baik yang ketat dan informal. Sementara konkurensi menambah kerumitan luar biasa untuk pemrograman imperatif (karena interleaving non-deterministik), itu mudah dalam FRP.

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

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

  • Perilaku dibangun dari beberapa primitif, seperti konstan (statis) perilaku 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 dari waktu ke waktu.

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

  • Untuk menghasilkan kosakata komposisi dari mana semua perilaku dan peristiwa dapat dibangun, mainkan dengan beberapa contoh. Terus dekonstruksi menjadi bagian-bagian yang lebih umum / sederhana.

  • Agar Anda tahu bahwa Anda berada di tanah yang kokoh, berikan seluruh model dasar komposisi, menggunakan teknik semantik denotasi, yang berarti bahwa (a) setiap jenis memiliki jenis "makna" matematika sederhana & tepat yang sesuai, dan ( b) setiap primitif dan operator memiliki arti yang sederhana & tepat sebagai fungsi dari makna konstituen. Tidak akan pernah campurkan pertimbangan implementasi ke dalam proses eksplorasi Anda. Jika deskripsi ini omong kosong kepada Anda, konsultasikan (a) Denotational design dengan jenis morfisme kelas, (b) Dorong-tarik pemrograman reaktif fungsional (mengabaikan bit implementasi), dan (c) Semantik Denotasional Halaman wikibooks Haskell. Berhati-hatilah semantik denotasional memiliki dua bagian, dari dua pendiri Christopher Strachey dan Dana Scott: bagian Strachey yang lebih mudah dan berguna serta bagian yang lebih keras dan kurang bermanfaat (untuk desain perangkat lunak) Scott.

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

Di mana saya mendapatkan prinsip-prinsip ini? Dalam desain perangkat lunak, saya selalu menanyakan pertanyaan yang sama: "apa artinya?". Denotational semantik memberi saya kerangka kerja yang tepat untuk pertanyaan ini, dan yang sesuai dengan estetika saya (tidak seperti semantik operasional atau aksiomatik, keduanya tidak memuaskan saya). Jadi saya bertanya pada diri sendiri apa perilaku itu? Saya segera menyadari bahwa sifat penghitungan imperatif yang bersifat sementara sementara adalah akomodasi untuk gaya tertentu mesin, daripada deskripsi alami perilaku itu sendiri. Deskripsi perilaku paling sederhana yang tepat yang dapat saya pikirkan hanyalah "fungsi waktu (berkelanjutan)", jadi itulah model saya. Yang menyenangkan, model ini menangani konkurensi kontinu, deterministik dengan mudah dan anggun.

Sudah cukup sulit untuk menerapkan model ini dengan benar dan efisien, tetapi itu adalah cerita lain.


932
2018-06-23 04:31



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 yang berubah-waktu ini akan memiliki nilai-nilai yang berubah seiring waktu.

Misalnya, Anda dapat 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 penugasan ini satu kali, dan variabel x dan y akan tetap "up to date" secara otomatis. Inilah sebabnya mengapa pemrograman reaktif dan pemrograman fungsional bekerja dengan baik bersama-sama: pemrograman reaktif menghilangkan kebutuhan untuk memutasi variabel sementara masih membiarkan Anda melakukan banyak hal yang dapat Anda capai dengan mutasi variabel.

Jika kita kemudian melakukan 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, minX akan selalu 16 kurang dari koordinat x dari penunjuk tetikus. Dengan pustaka reaktif-sadar Anda kemudian dapat mengatakan sesuatu seperti:

rectangle(minX, minY, maxX, maxY)

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

Ini cukup bagus makalah tentang pemrograman reaktif fungsional.


740
2018-06-22 18:06



Cara mudah 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 apa pun yang merujuk ke sel itu juga berubah. Sama halnya 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 saja sangat keliru. Metafora rusak cukup cepat ketika Anda benar-benar menggunakan sistem FRP. Untuk satu, biasanya ada upaya untuk model kejadian diskrit juga (misalnya mouse yang diklik). Saya hanya menempatkan ini di sini untuk memberi Anda gambaran seperti apa rasanya.


144
2018-06-23 14:52



Bagi saya itu adalah sekitar 2 arti simbol yang berbeda =:

  1. Dalam matematika x = sin(t) maksudnya x aku s nama yang berbeda untuk sin(t). Jadi menulis x + y adalah hal yang sama sin(t) + y. Pemrograman reaktif fungsional seperti matematika dalam hal ini: jika Anda menulis x + y, dihitung dengan nilai berapa pun t pada saat itu digunakan.
  2. Dalam bahasa pemrograman seperti-C (bahasa imperatif), x = sin(t) adalah tugas: artinya itu x menyimpan Nilai dari  sin(t) diambil pada saat penugasan.

132
2018-05-25 14:52



Oke, dari pengetahuan latar belakang dan dari membaca halaman Wikipedia yang Anda tunjuk, tampak bahwa pemrograman reaktif adalah sesuatu seperti komputasi dataflow tetapi dengan "rangsangan" eksternal spesifik memicu set node untuk memecat dan melakukan perhitungan 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 output audio yang sebenarnya. Ketika Anda memodifikasi volume (slider, katakanlah) yang akan sesuai dengan memodifikasi nilai yang terkait dengan node dalam grafik langsung.

Berbagai node memiliki ujung dari "nilai volume" node akan secara otomatis dipicu dan setiap perhitungan dan pembaruan yang diperlukan akan secara alami 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 dataflow", cari dua kata di Wikipedia atau menggunakan mesin pencari favorit Anda. Gagasan umumnya adalah ini: program adalah grafik diarahkan node, masing-masing melakukan beberapa perhitungan sederhana. Simpul-simpul ini saling terhubung satu sama lain dengan tautan-tautan grafik yang menyediakan output dari beberapa simpul ke input-input orang lain.

Ketika sebuah titik api atau melakukan perhitungannya, node yang terhubung ke outputnya memiliki masukan yang sesuai "dipicu" atau "ditandai". Semua node yang memiliki semua input yang dipicu / ditandai / tersedia secara otomatis diaktifkan. Grafik mungkin implisit atau eksplisit tergantung pada persis bagaimana pemrograman reaktif diimplementasikan.

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

Pemrograman reaktif mengimpor ide "kaskade eksekusi" ini dan tampaknya memikirkan program dalam mode seperti aliran data tetapi dengan ketentuan bahwa beberapa node terhubung ke "dunia luar" dan riam eksekusi dipicu ketika sensorik ini -seperti simpul berubah. Eksekusi program kemudian akan terlihat seperti sesuatu yang analog dengan busur refleks yang kompleks. Program ini mungkin atau mungkin tidak pada dasarnya sessile antara rangsangan atau dapat menetap ke dalam 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 subjektif, karena orang mungkin akan tergoda untuk mengatakan apa pun yang menanggapi masukan eksternal "bereaksi" kepada mereka. Tapi melihat semangat dari hal itu, sebuah program yang polling suatu peristiwa antrian pada interval tetap dan mengirimkan setiap peristiwa yang ditemukan fungsi (atau benang) kurang reaktif (karena hanya hadir untuk input pengguna pada interval tetap). Sekali lagi, ini adalah semangat hal di sini: orang dapat membayangkan menempatkan pelaksanaan polling dengan interval polling cepat ke dalam sistem pada tingkat yang sangat rendah dan program dengan cara yang reaktif di atasnya.


71
2018-06-22 17:45



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

Saya mengutip di bawah Heinrich Apfelmus (penulis pisang reaktif).

Apa esensi dari pemrograman reaktif fungsional?

Jawaban yang umum adalah bahwa "FRP adalah semua tentang menggambarkan suatu sistem di   istilah fungsi yang berubah-waktu dan bukannya bisa berubah-ubah ”, dan itu   pasti tidak akan salah. Ini adalah sudut pandang semantik. Tetapi dalam   pendapat saya, jawaban yang lebih dalam dan lebih memuaskan diberikan oleh   mengikuti kriteria sintaksis murni:

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

Misalnya, ambil contoh penghitung: Anda memiliki dua tombol   berlabel "Up" dan "Down" yang dapat digunakan untuk kenaikan atau pengurangan   penghitung. Secara imperatif, Anda pertama-tama akan menentukan nilai awal   dan kemudian ubah kapan pun sebuah 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 ditentukan; perilaku dinamis dari counter   tersirat dalam sisa teks program. Sebaliknya, fungsional   pemrograman reaktif menentukan seluruh perilaku dinamis pada saat itu   deklarasi, seperti ini:

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

Setiap kali Anda ingin memahami dinamika counter, Anda hanya perlu   untuk melihat definisinya. Segala sesuatu yang bisa terjadi akan terjadi   muncul di sisi kanan. Ini sangat jauh berbeda 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: enter image description here

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

f tergantung pada t jadi ini menggabungkan kemungkinan untuk memodelkan rangsangan eksternal

semua keadaan program diringkas dalam variabel x_i

Perpustakaan FRP mengatur waktu progresif, dengan kata lain, mengambil j untuk j+1.

Saya menjelaskan persamaan ini dengan lebih detail ini video.

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) memecahkan masalah praktis yang penting: pembatalan cache.

Persamaan untuk x_i-s menggambarkan grafik ketergantungan. Ketika beberapa x_i berubah pada waktunya j maka tidak semua yang lain x_i' nilai-nilai di j+1 perlu 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. Misalnya, mari kita pertimbangkan operasi peta f=g.map(_+1) di Scala, di mana f dan g adalah List dari Ints. Sini f sesuai dengan x_i(t_j) dan g aku s x_j(t_j). Sekarang jika saya menambahkan elemen ke g maka itu akan sia-sia untuk melaksanakan map operasi untuk semua elemen di g. Beberapa implementasi FRP (misalnya refleks-frp) bertujuan untuk memecahkan masalah ini. Masalah ini juga dikenal sebagai komputasi inkremental.

Dengan kata lain, perilaku (yang x_i-s) dalam FRP dapat dianggap sebagai perhitungan cache-ed. Ini adalah tugas mesin FRP untuk secara efisien membatalkan dan menghitung ulang cache-s ini (yang x_i-s) jika beberapa f_i-s lakukan perubahan.


65
2018-01-31 03:46



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

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

Keuntungan utama menggunakan yang dapat diamati adalah:
i) itu mengaburkan negara jauh dari kode Anda, misalnya, jika Anda ingin pengendali event untuk dipecat hanya untuk setiap acara 'n'th, atau berhenti menembaki setelah peristiwa' n 'pertama, atau mulai menembakan hanya setelah yang pertama' n 'Peristiwa, Anda hanya dapat menggunakan HoFs (filter, takeUntil, lewati masing-masing) bukannya pengaturan, memperbarui dan memeriksa counter.
ii) itu meningkatkan lokalitas kode - jika Anda memiliki 5 penangan kejadian yang berbeda yang mengubah status komponen, Anda dapat menggabungkan hasil yang dapat diamati dan mendefinisikan satu pengendali event pada gabungan yang dapat diamati sebagai gantinya, secara efektif menggabungkan 5 pengendali event ke 1. Hal ini membuatnya sangat mudah untuk berpikir tentang peristiwa apa saja di seluruh sistem Anda yang dapat memengaruhi komponen, karena semuanya ada dalam satu penangan.

  • Yang Dapat Diamati adalah dual dari Iterable.

Iterable adalah urutan malas dikonsumsi - setiap item ditarik oleh iterator kapan pun ia ingin menggunakannya, dan karenanya pencacahan didorong oleh konsumen.

Yang dapat diamati adalah urutan yang diproduksi secara malas - setiap item didorong ke pengamat kapan saja itu ditambahkan ke urutan, dan karenanya pencacahan didorong oleh produser.


30
2018-05-26 17:10



Kertas Reaktivitas fungsional yang cukup efisien oleh Conal Elliott (PDF langsung, 233 KB) adalah pengenalan yang cukup baik. Perpustakaan yang sesuai juga berfungsi.

Kertas itu sekarang diganti dengan kertas lain, Dorong-tarik pemrograman reaktif fungsional (PDF langsung, 286 KB).


29
2018-06-22 17:48



Bung, ini ide brilian yang luar biasa! Mengapa saya tidak mengetahui tentang ini di tahun 1998? Anyway, inilah penafsiran saya tentang Fran tutorial. Saran sangat disambut, saya berpikir tentang memulai mesin gim 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?


18
2018-03-13 09:44



Buku Paul Hudak, The Haskell School of Expression, bukan hanya pengenalan yang bagus untuk Haskell, tetapi juga menghabiskan cukup banyak waktu untuk FRP. Jika Anda seorang pemula dengan FRP, saya sangat merekomendasikannya untuk memberi Anda rasa bagaimana FRP bekerja.

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


14
2018-06-24 18:41