Pemrograman reaktif java. Apa itu Pemrograman Reaktif? Operasi pada Dasar Primitif

Pergi.

Pemrograman reaktif terdengar seperti nama paradigma yang baru lahir pada awalnya, tetapi sebenarnya mengacu pada metode pemrograman yang menggunakan pendekatan berbasis peristiwa untuk bekerja dengan aliran data asinkron. Berdasarkan data terkini, sistem reaktif bereaksi terhadapnya dengan mengeksekusi serangkaian peristiwa.
Pemrograman reaktif mengikuti pola desain Pengamat, yang dapat didefinisikan sebagai berikut: jika perubahan status terjadi pada satu objek, maka semua objek lain akan diberitahukan dan diperbarui sesuai dengan itu. Jadi, alih-alih polling acara untuk perubahan, acara didorong secara asinkron sehingga pengamat dapat memprosesnya. Dalam contoh ini, pengamat adalah fungsi yang dijalankan ketika suatu peristiwa dikirim. Dan aliran data yang disebutkan adalah yang sebenarnya dapat diamati.

Hampir semua bahasa dan kerangka kerja menggunakan pendekatan ini di ekosistemnya, dan versi terbaru Java tidak terkecuali. Pada artikel ini, saya akan menjelaskan bagaimana pemrograman reaktif dapat diterapkan menggunakan versi terbaru JAX-RS di fungsionalitas Java EE 8 dan Java 8.

Manifesto Reaktif

Manifesto Reaktif mencantumkan empat aspek mendasar yang dibutuhkan aplikasi agar lebih fleksibel, digabungkan secara longgar, dan mudah diskalakan, dan oleh karena itu dapat menjadi reaktif. Dikatakan bahwa aplikasi harus responsif, fleksibel (dan karenanya dapat diskalakan), tangguh, dan didorong oleh pesan.

Tujuan dasarnya adalah aplikasi yang benar-benar responsif. Katakanlah Anda memiliki aplikasi yang memiliki satu utas besar yang memproses permintaan pengguna, dan ketika pekerjaan selesai, utas itu mengirimkan tanggapan kembali ke pemohon asli. Saat aplikasi menerima lebih banyak permintaan daripada yang dapat ditanganinya, aliran ini menjadi hambatan dan aplikasi kehilangan respons sebelumnya. Agar tetap responsif, aplikasi harus dapat diskalakan dan tangguh. Aplikasi tangguh adalah aplikasi yang memiliki fungsi pemulihan otomatis. Dalam pengalaman sebagian besar pengembang, hanya arsitektur berbasis pesan yang memungkinkan aplikasi dapat diskalakan, kuat, dan responsif.

Pemrograman reaktif diperkenalkan di Java 8 dan Java EE 8. Bahasa Java memperkenalkan konsep seperti CompletionStage dan implementasi CompletableFuture-nya, dan Java mulai menggunakan fitur ini dalam spesifikasi seperti Reactive Client API di JAX-RS.

JAX-RS 2.1 API Klien Reaktif

Mari kita lihat bagaimana pemrograman reaktif dapat digunakan dalam aplikasi Java EE 8. Untuk memahami prosesnya, Anda memerlukan pengetahuan dasar tentang Java EE API.

JAX-RS 2.1 memperkenalkan cara baru untuk membuat klien REST reaktif. Implementasi default dari Invoker yang ditawarkan oleh JAX-RS adalah sinkron, yang berarti bahwa klien yang dibuat akan mengirim panggilan pemblokiran ke titik akhir server. Contoh implementasi disediakan di Listing 1.

Respons respons = ClientBuilder.newClient() .target("http://localhost:8080/service-url") .request() .get();
Dimulai dengan versi 2.0, JAX-RS menyediakan dukungan untuk membuat pemanggil asinkron pada API klien dengan panggilan sederhana ke metode async(), seperti yang ditunjukkan pada Daftar 2.

Masa depan respon = ClientBuilder.newClient() .target("http://localhost:8080/service-url") .request() .async() .get();
Menggunakan Invoker asinkron pada klien akan mengembalikan instance Future dari tipe javax.ws.rs.core.Response . Hal ini dapat mengakibatkan polling untuk respons, memanggil future.get() , atau mendaftarkan panggilan balik yang akan dipanggil saat ada respons HTTP yang tersedia. Kedua implementasi cocok untuk pemrograman asinkron, tetapi hal-hal cenderung menjadi lebih rumit jika Anda ingin mengelompokkan panggilan balik atau menambahkan kasus bersyarat ke minimum eksekusi asinkron tersebut.

JAX-RS 2.1 menyediakan cara reaktif untuk mengatasi masalah ini dengan API Klien Reaktif JAX-RS baru untuk perakitan klien. Sesederhana memanggil metode rx() selama pembuatan klien. Di Listing 3, metode rx() mengembalikan pemanggil reaktif yang ada selama eksekusi klien, dan klien mengembalikan respons dengan tipe CompletionStage.rx() yang memungkinkan transisi dari pemanggil sinkron ke pemanggil asinkron dengan sederhana panggilan.

Tahap Penyelesaian respon = ClientBuilder.newClient() .target("http://localhost:8080/service-url") .request() .rx() .get();
Tahap Penyelesaian<Т>adalah antarmuka baru yang diperkenalkan di Java 8. Ini mewakili perhitungan, yang bisa menjadi langkah dalam perhitungan yang lebih besar, seperti namanya. Ini adalah satu-satunya reaktivitas Java 8 yang berhasil masuk ke JAX-RS.
Setelah menerima instance respons, saya dapat memanggil AcceptAsync() , di mana saya dapat memberikan sepotong kode yang akan dieksekusi secara asinkron ketika respons tersedia, seperti yang ditunjukkan pada Listing 4.

Response.thenAcceptAsync(res -> ( Suhu t = res.readEntity(Temperature.class); //lakukan hal-hal dengan t ));
Menambahkan reaktivitas ke titik akhir REST

Pendekatan reaktif tidak terbatas pada sisi klien di JAX-RS; itu dapat digunakan di sisi server juga. Sebagai contoh, pertama-tama saya akan membuat skrip sederhana di mana saya dapat meminta daftar lokasi untuk satu tujuan. Untuk setiap posisi, saya akan membuat panggilan terpisah dengan data lokasi ke titik lain untuk mendapatkan nilai suhu. Interaksi tujuan akan seperti yang ditunjukkan pada Gambar 1.

Gambar 1. Interaksi antar destinasi

Pertama saya hanya mendefinisikan model domain dan kemudian layanan untuk setiap model. Daftar 5 menunjukkan bagaimana kelas Prakiraan didefinisikan, yang membungkus kelas Lokasi dan Suhu.

Suhu kelas publik (suhu ganda pribadi; skala String pribadi; // getter & setter) kelas publik Lokasi (Nama string; Lokasi publik() () Lokasi publik(Nama string) ( this.name = name; ) // getter & setter ) kelas publik Prakiraan ( lokasi Lokasi pribadi; suhu Suhu pribadi; Prakiraan publik (Lokasi lokasi) ( this.location = lokasi; ) set Prakiraan publikSuhu (suhu Suhu akhir) ( this.temperature = suhu; kembalikan ini; ) // getter )
Untuk membungkus daftar prediksi, kelas ServiceResponse diimplementasikan di Listing 6.

ServiceResponse kelas publik (Waktu pemrosesan lama pribadi; Daftar pribadi perkiraan = ArrayList baru<>(); public void setProcessingTime(waktu pemrosesan lama) ( this.processingTime = processingTime; ) perkiraan ServiceResponse publik(Daftar prakiraan) ( this.forecasts = prakiraan; kembalikan ini; ) // getter )
LocationResource , ditampilkan di Listing 7, mendefinisikan tiga pola lokasi yang dikembalikan dengan path /location .

@Path("/location") public class LocationResource ( @GET @Produces(MediaType.APPLICATION_JSON) public Response getLocations() ( Daftar lokasi = ArrayList baru<>(); lokasi.add(Lokasi baru("London")); lokasi.add(Lokasi baru("Istanbul")); lokasi.add(Lokasi baru("Praha")); kembalikan Response.ok (Entitas Generik baru >(lokasi)()).build(); ) )
TemperatureResource , ditunjukkan pada Listing 8, mengembalikan nilai suhu yang dihasilkan secara acak antara 30 dan 50 untuk lokasi tertentu. Penundaan 500 ms telah ditambahkan ke implementasi untuk mensimulasikan pembacaan sensor.

@Path("/temperature") public class TemperatureResource ( @GET @Path("/(city)") @Produces(MediaType.APPLICATION_JSON) public Response getAverageTemperature(@PathParam("city") String cityName) ( Suhu suhu = baru Temperature(); temperature.setTemperature((double) (new Random().nextInt(20) + 30)); temperature.setScale("Celsius"); try ( Thread.sleep(500); ) catch (InterruptedException diabaikan) ( mengabaikan.printStackTrace(); ) mengembalikan Response.ok(temperature).build(); ) )
Pertama, saya akan menunjukkan implementasi ForecastResource sinkron (lihat Daftar 9) yang mengembalikan semua lokasi. Kemudian, untuk setiap posisi, ia memanggil layanan suhu untuk mendapatkan nilai dalam derajat Celcius.

@Path("/forecast") public class ForecastResource ( @Uri("location") private WebTarget locationTarget; @Uri("temperature/(city)") private WebTarget temperatureTarget; @GET @Produces(MediaType.APPLICATION_JSON) public Response getLocationsWithTemperature () ( long startTime = System.currentTimeMillis(); ServiceResponse response = new ServiceResponse(); Daftar lokasi = locationTarget .request() .get(new GenericType >()()); forEach(location -> ( Temperature temperature = temperatureTarget .resolveTemplate("city", location.getName()) .request() .get(Temperature.class); response.getForecasts().add(new Forecast(location) .setTemperature (suhu)); )); long endTime = System.currentTimeMillis(); response.setProcessingTime(Waktu akhir - waktu mulai); kembalikan Respon.ok(tanggapan).build(); ) )
Saat tujuan prakiraan diminta sebagai /forecast , Anda akan mendapatkan output yang mirip dengan Listing 10. Perhatikan bahwa permintaan membutuhkan waktu 1,533 md untuk diproses, yang logis, karena permintaan nilai suhu secara sinkron dari tiga lokasi berbeda akan bertambah hingga 1,5 md.

( "perkiraan": [ ( "lokasi": ( "nama": "London" ), "suhu": ( "skala": "Celcius", "suhu": 33 ) ), ( "lokasi": ( "nama ": "Istanbul" ), "suhu": ( "skala": "Celcius", "suhu": 38) ), ( "lokasi": ( "nama": "Praha" ), "suhu": ( "skala ": "Celcius", "suhu": 46 ) ) ], "waktu pemrosesan": 1533 )
Sejauh ini semuanya berjalan sesuai rencana. Saatnya untuk memperkenalkan pemrograman reaktif di sisi server, di mana panggilan ke setiap lokasi dapat dilakukan secara paralel setelah semua lokasi diterima. Ini jelas dapat meningkatkan aliran sinkron yang ditunjukkan sebelumnya. Ini dilakukan di Daftar 11, yang menunjukkan definisi versi reaktif dari layanan prakiraan.

@Path("/reactiveForecast") public class ForecastReactiveResource ( @Uri("location") private WebTarget locationTarget; @Uri("temperature/(city)") private WebTarget temperatureTarget; @GET @Produces(MediaType.APPLICATION_JSON) public void getLocationsWithTemperature (@Suspended final AsyncResponse async) ( long startTime = System.currentTimeMillis(); // Buat panggung untuk mengambil lokasi CompletionStage > locationCS = locationTarget.request() .rx() .get(New GenericType >() ()); // Dengan membuat tahap terpisah di tahap lokasi, // dijelaskan di atas, kumpulkan daftar prediksi // seperti dalam satu CompletionStage final CompletionStage besar > forecastCS = locationCS.thenCompose(locations -> ( // Buat tahapan untuk mendapatkan prakiraan // sebagai Daftar CompletionStage > forecastList = // Streaming lokasi dan proses masing-masing // secara terpisah location.stream().map(location -> ( // Buat langkah untuk mendapatkan // suhu hanya satu kota // dengan namanya final CompletionStage tempCS = temperatureTarget .resolveTemplate("city", location.getName()) .request() .rx() .get(Temperature.class); // Kemudian buat CompletableFuture yang // berisi contoh prakiraan dengan // lokasi dan nilai suhu mengembalikan CompletableFuture.completedFuture(new Forecast(location)) .thenCombine(tempCS, Forecast::setTemperature); )).collect(Collectors.toList()); // Kembalikan instance CompletableFuture terakhir di mana // semua objek masa depan yang dapat diselesaikan yang disajikan adalah // selesai return CompletableFuture.allOf(forecastList.toArray(new CompletableFuture)) .thenApply(v -> forecastList.stream() .map(CompletionStage::toCompletableFuture ) .map(CompletableFuture::join) .collect(Collectors.toList())); )); // Buat instance ServiceResponse yang // berisi daftar lengkap prediksi // bersama dengan waktu pemrosesan. // Buat masa depannya dan gabungkan dengan // forecastCS untuk mendapatkan prakiraan // dan masukkan ke dalam respons layanan CompletableFuture.completedFuture(new ServiceResponse()) .thenCombine(forecastCS, ServiceResponse::forecasts) .whenCompleteAsync((response, throwable) - > ( response.setProcessingTime(System.currentTimeMillis() - startTime); async.resume(respon); )); ) )
Implementasi reaktif mungkin tampak rumit pada pandangan pertama, tetapi setelah melihat lebih dekat, Anda akan melihat bahwa itu cukup sederhana. Dalam implementasi ForecastReactiveResource, pertama-tama saya membuat panggilan klien ke layanan lokasi menggunakan JAX-RS Reactive Client API. Seperti yang saya sebutkan di atas, ini adalah tambahan untuk Java EE 8 dan membantu membuat panggilan reaktif hanya dengan metode rx().

Sekarang saya membuat tahap baru berdasarkan lokasi untuk mengumpulkan daftar prediksi. Mereka akan disimpan sebagai daftar prakiraan dalam satu tahap penyelesaian besar yang disebut forecastCS. Pada akhirnya, saya akan membuat respons panggilan layanan hanya menggunakan forecastCS .

Sekarang mari kita kumpulkan perkiraan sebagai daftar tahapan penyelesaian yang ditentukan dalam variabel forecastList . Untuk membuat tahap penyelesaian untuk setiap perkiraan, saya meneruskan data lokasi dan kemudian membuat variabel tempCS, sekali lagi menggunakan JAX-RS Reactive Client API, yang memanggil layanan suhu dengan nama kota. Di sini, saya menggunakan metode resolveTemplate() untuk membangun klien, dan ini memungkinkan saya untuk meneruskan nama kota ke pembangun sebagai parameter.

Sebagai langkah terakhir streaming, saya membuat panggilan ke CompletableFuture.completedFuture() , meneruskan instance Forecast baru sebagai parameter. Saya menggabungkan masa depan ini dengan tahap tempCS sehingga saya memiliki nilai suhu untuk lokasi yang diulang.

Metode CompletableFuture.allOf() di Listing 11 mengubah daftar tahapan penyelesaian menjadi forecastCS . Menjalankan langkah ini mengembalikan masa depan yang dapat diselesaikan yang besar ketika semua masa depan yang dapat diselesaikan yang disediakan telah selesai.

Respons layanan adalah turunan dari kelas ServiceResponse, jadi saya membuat masa depan yang lengkap dan kemudian menggabungkan tahap penyelesaian forecastCS dengan daftar prakiraan dan menghitung waktu respons layanan.

Tentu saja, pemrograman reaktif hanya memaksa sisi server untuk mengeksekusi secara asinkron; sisi klien akan memblokir sampai server mengirimkan respons kembali ke pemohon. Untuk mengatasi masalah ini, Server Sent Events (SSEs) dapat digunakan untuk mengirim sebagian respons segera setelah tersedia, sehingga nilai suhu untuk setiap lokasi dikirim ke klien satu per satu. Output dari ForecastReactiveResource akan mirip dengan Listing 12. Seperti yang ditunjukkan pada output, waktu pemrosesan adalah 515ms, yang merupakan waktu eksekusi yang ideal untuk mendapatkan nilai suhu dari satu lokasi.

( "perkiraan": [ ( "lokasi": ( "nama": "London" ), "suhu": ( "skala": "Celcius", "suhu": 49 ) ), ( "lokasi": ( "nama ": "Istanbul" ), "suhu": ( "skala": "Celcius", "suhu": 32 ) ), ( "lokasi": ( "nama": "Praha" ), "suhu": ( "skala ": "Celcius", "suhu": 45 ) )], "Waktu pemrosesan": 515 )
Kesimpulan

Dalam contoh di artikel ini, saya pertama kali menunjukkan cara sinkron untuk mendapatkan prakiraan menggunakan layanan lokasi dan suhu. Kemudian, saya beralih ke pendekatan reaktif agar pemrosesan asinkron dilakukan di antara panggilan layanan. Saat Anda menggunakan JAX-RS Reactive Client API di Java EE 8 bersama dengan kelas CompletionStage dan CompletableFuture yang tersedia di Java 8, kekuatan pemrosesan asinkron dilepaskan melalui pemrograman reaktif.

Pemrograman reaktif lebih dari sekadar menerapkan model asinkron dari yang sinkron; itu juga menyederhanakan konsep seperti tahap bersarang. Semakin banyak digunakan, semakin mudah untuk mengelola skenario kompleks dalam pemrograman paralel.

Terima kasih atas perhatian Anda. Seperti biasa, kami menyambut komentar dan pertanyaan Anda.

Anda dapat membantu dan mentransfer sejumlah dana untuk pengembangan situs

Sampai saat ini, ada sejumlah metodologi untuk pemrograman sistem real-time yang kompleks. Salah satu metodologi ini disebut FRP (FRP). Itu telah menyerap pola desain yang disebut Pengamat (Pengamat) di satu sisi, dan di sisi lain, seperti yang Anda duga, prinsip-prinsip pemrograman fungsional. Pada artikel ini, kami akan mempertimbangkan pemrograman reaktif fungsional menggunakan contoh implementasinya di perpustakaan Sodium untuk bahasa Haskell.

Bagian teoretis

Secara tradisional, program dibagi menjadi tiga kelas:

  • Kelompok
  • Interaktif
  • jet

Perbedaan antara ketiga kelas program ini terletak pada jenis interaksinya dengan dunia luar.

Eksekusi program batch sama sekali tidak disinkronkan dengan dunia luar. Mereka dapat diluncurkan pada beberapa titik waktu yang sewenang-wenang dan diselesaikan ketika data awal diproses dan hasilnya diperoleh. Tidak seperti program batch, program interaktif dan reaktif berkomunikasi dengan dunia luar secara terus menerus selama pekerjaan mereka dari saat mereka memulai hingga saat mereka berhenti.

Program reaktif, tidak seperti program interaktif, sangat disinkronkan dengan lingkungan eksternal: diperlukan untuk menanggapi peristiwa di dunia luar dengan penundaan yang dapat diterima dalam tingkat terjadinya peristiwa ini. Pada saat yang sama, diperbolehkan untuk program interaktif untuk membuat lingkungan eksternal menunggu. Misalnya, editor teks adalah program interaktif: pengguna yang memulai proses koreksi kesalahan dalam teks harus menunggu sampai selesai untuk melanjutkan pengeditan. Namun autopilot merupakan program yang reaktif, karena ketika terjadi halangan maka harus segera memperbaiki lintasan agar dapat melakukan manuver flyby, karena dunia nyata tidak dapat di-pause seperti pengguna text editor.

Kedua kelas program ini muncul kemudian, ketika komputer mulai digunakan untuk mengontrol mesin dan antarmuka pengguna mulai berkembang. Sejak itu, banyak metode implementasi telah berubah, yang masing-masing dirancang untuk memperbaiki kekurangan yang sebelumnya. Pada awalnya, ini adalah program yang digerakkan oleh peristiwa sederhana: serangkaian peristiwa tertentu dialokasikan dalam sistem yang perlu ditanggapi, dan penangan untuk peristiwa ini dibuat. Penangan, pada gilirannya, juga dapat menghasilkan acara yang pergi ke dunia luar. Model ini sederhana dan memecahkan berbagai masalah interaktif sederhana.

Namun seiring waktu, program interaktif dan reaktif menjadi lebih kompleks dan pemrograman yang digerakkan oleh peristiwa berubah menjadi neraka. Ada kebutuhan untuk alat yang lebih canggih untuk sintesis sistem yang digerakkan oleh peristiwa. Ada dua kelemahan utama dalam metodologi ini:

  • keadaan implisit
  • Nondeterminisme

Untuk memperbaikinya, template dibuat terlebih dahulu pengamat (pengamat), yang mengubah peristiwa menjadi nilai yang berubah-ubah waktu. Himpunan nilai-nilai ini mewakili keadaan eksplisit program itu. Jadi pemrosesan acara digantikan oleh pekerjaan biasa dengan data untuk program batch. Pengembang dapat mengubah nilai yang dapat diamati dan penangan berlangganan untuk mengubah nilai ini. Menjadi mungkin untuk menerapkan nilai-nilai dependen yang berubah sesuai dengan algoritma yang diberikan, ketika mengubah nilai-nilai yang mereka andalkan.

Meskipun peristiwa dalam pendekatan ini telah berlalu, kebutuhan akan mereka masih tetap ada. Suatu peristiwa tidak selalu berarti perubahan nilai. Misalnya, peristiwa waktu nyata menyiratkan peningkatan penghitung waktu satu detik, tetapi peristiwa alarm setiap hari pada waktu tertentu tidak menyiratkan nilai sama sekali. Tentu saja, Anda juga dapat mengaitkan acara ini dengan makna tertentu, tetapi ini akan menjadi perangkat buatan. Misalnya, kita dapat memasukkan nilai: alarm_time == sisa_pembagian (waktu_saat ini, 24*60*60). Tapi ini tidak akan persis seperti yang kita minati, karena variabel ini terikat pada satu detik, dan sebenarnya nilainya berubah dua kali. Untuk mengetahui bahwa alarm berbunyi, pelanggan harus menentukan bahwa nilainya menjadi benar, dan bukan sebaliknya. Nilainya akan benar untuk tepat satu detik, dan jika kita mengubah periode centang dari satu detik menjadi, katakanlah, 100 milidetik, maka nilai sebenarnya tidak akan lagi menjadi satu detik, tetapi 100 milidetik ini.

Munculnya metodologi pemrograman reaktif fungsional adalah semacam respons fungsionalis terhadap template pengamat. Di FRP, pendekatan yang dikembangkan dipikirkan kembali: peristiwa (Peristiwa) tidak hilang, namun, nilai yang diamati juga muncul dan dinamai karakteristik (Perilaku). Suatu peristiwa dalam metodologi ini adalah nilai yang dihasilkan secara diskrit, dan suatu karakteristik adalah nilai yang dihasilkan secara terus-menerus. Keduanya dapat dihubungkan: karakteristik dapat menghasilkan peristiwa, dan peristiwa dapat bertindak sebagai sumber karakteristik.

Masalah ketidaktentuan negara jauh lebih rumit. Ini berasal dari fakta bahwa sistem yang digerakkan oleh peristiwa secara de facto tidak sinkron. Ini memerlukan munculnya keadaan perantara dari sistem, yang mungkin tidak dapat diterima karena alasan tertentu. Untuk mengatasi masalah ini, apa yang disebut pemrograman sinkron muncul.

Bagian praktis

Perpustakaan Sodium muncul sebagai proyek implementasi FRP dengan antarmuka umum dalam bahasa pemrograman yang berbeda. Ini berisi semua elemen metodologi: primitif (Peristiwa, Perilaku) dan pola penggunaannya.

Primitif dan interaksi dengan dunia luar

Dua primitif utama yang harus kita tangani adalah:

  • peristiwa sebuah- acara dengan nilai tipe sebuah
  • Perilaku sebuah- karakteristik (atau nilai yang berubah) dari suatu tipe sebuah

Kami dapat membuat acara dan nilai baru dengan fungsi acara baru dan perilaku baru:

newEvent::Reactive(Event a, a -> Reactive()) newBehavior::a -> Reactive(Perilaku a, a -> Reactive())

Seperti yang Anda lihat, kedua fungsi ini hanya dapat dipanggil di monad. Reaktif, akibatnya, primitif itu sendiri dikembalikan, serta fungsi yang harus dipanggil untuk mengaktifkan acara atau mengubah nilainya. Fungsi pembuatan fitur mengambil nilai awal sebagai argumen pertama.

Untuk menghubungkan dunia nyata dengan program reaktif, ada fungsi sinkronisasi, dan untuk menghubungkan program dengan dunia luar, ada fungsi mendengarkan:

Sinkronisasi:: Reaktif a -> IO mendengarkan:: Acara a -> (a -> IO ()) -> Reaktif (IO ())

Fungsi pertama, seperti namanya, mengeksekusi beberapa kode reaktif secara sinkron, memungkinkan Anda untuk masuk ke konteks Reaktif diluar konteks saya, dan yang kedua digunakan untuk menambahkan event handler yang terjadi dalam konteks Reaktif, mengeksekusi dalam konteks saya. Fungsi mendengarkan mengembalikan fungsi tidak mendengarkan Yang akan dipanggil untuk melepaskan pawang.

Dengan demikian, semacam mekanisme transaksi diterapkan. Ketika kita melakukan sesuatu di dalam monad Reaktif, kode dieksekusi dalam transaksi yang sama pada saat fungsi dipanggil. sinkronisasi. Keadaan hanya ditentukan di luar konteks suatu transaksi.

Ini adalah dasar dari pemrograman fungsional reaktif, yang cukup untuk menulis program. Ini bisa sedikit membingungkan bahwa Anda hanya dapat mendengarkan acara. Ini persis bagaimana seharusnya, seperti yang akan kita lihat nanti, ada hubungan erat antara peristiwa dan karakteristik.

Operasi pada Dasar Primitif

Untuk kenyamanan, fungsi tambahan telah ditambahkan ke metodologi yang mengubah peristiwa dan karakteristik. Mari kita pertimbangkan beberapa di antaranya:

Peristiwa yang tidak akan pernah terjadi -- (dapat digunakan sebagai rintisan) never:: Event a -- Menggabungkan dua event dengan tipe yang sama menjadi satu -- (berguna untuk mendefinisikan satu handler untuk kelas event) merge:: Event a -> Peristiwa a -> Peristiwa a -- Tarik nilai dari peristiwa Mungkin -- (pisahkan gandum dari sekam) filterJust:: Peristiwa (Mungkin a) -> Peristiwa a -- Ubah peristiwa menjadi karakteristik dengan inisial nilai -- (mengubah nilai saat peristiwa terjadi) hold :: a -> Peristiwa a -> Reaktif (Perilaku a) -- Mengubah karakteristik menjadi peristiwa -- (menghasilkan peristiwa saat nilai berubah) update:: Perilaku a -> Peristiwa a -- Mengubah karakteristik menjadi peristiwa -- (juga melempar peristiwa untuk nilai awal) nilai:: Perilaku a -> Peristiwa a -- Ketika suatu peristiwa terjadi, mengambil nilai karakteristik, -- menerapkan fungsi dan menghasilkan snapshot acara:: (a -> b -> c) -> Acara a -> Perilaku b -> Acara c -- Mendapatkan nilai saat ini dari sampel:: Perilaku a -> Reaktif a -- Mengurangi kejadian berulang menjadi satu kesatuan :: (a -> a -> a) -> Acara a -> Acara a -- Menekan semua acara kecuali yang pertama sekali:: Acara a -> Acara a -- Membagi acara dari pemisahan daftar multi-acara:: Acara [a] -> Acara a

Contoh bola dalam ruang hampa

Mari kita coba menulis sesuatu:

Impor FRP.Sodium main = lakukan sinkronisasi $ lakukan -- buat acara (e1, triggerE1)<- newEvent -- создаём характеристику с начальным значением 0 (v1, changeV1) <- newBehavior 0 -- определяем обработчик для события listen e1 $ \_ ->putStrLn $ "e1 trigger" -- tentukan handler untuk mengubah nilai karakteristik listening (value v1) $ \v -> putStrLn $ "v1 value: " ++ show v -- Menghasilkan event tanpa nilai triggerE1() -- Ubah nilai perubahan karakteristikV1 13

Instal paketnya Sodium dengan menggunakan Komplotan rahasia dan jalankan contoh di penerjemah:

# jika kita ingin bekerja di sandbox terpisah # buat > cabal sandbox init # install > cabal install sodium > cabal repl GHCI, versi 7.6.3: http://www.haskell.org/ghc/ :? untuk bantuan Memuat paket ghc-prim ... penautan ... selesai. Memuat paket integer-gmp ... penautan ... selesai. Memuat basis paket ... penautan ... selesai. # load example Prelude> :l Example.hs Compiling Main (Example.hs, diinterpretasikan) Ok, modul yang dimuat: Main. # jalankan contoh *Utama>

Sekarang mari kita bereksperimen. Mari kita beri komentar pada baris di mana kita mengubah nilai kita (changeV1 13) dan memulai kembali contoh:

*Main> :l Example.hs Compiling Main (Example.hs, diinterpretasikan) Ok, modul yang dimuat: Main. *Utama> nilai v1 dipicu e1 utama: 0

Seperti yang Anda lihat, nilai awal sekarang ditampilkan, ini karena fungsinya nilai menghasilkan peristiwa pertama dengan nilai awal karakteristik. Mari kita ganti fungsinya nilai pada pembaruan dan lihat apa yang terjadi:

*Main> :l Example.hs Compiling Main (Example.hs, diinterpretasikan) Ok, modul yang dimuat: Main. *Utama> e1 utama dipicu

Sekarang nilai awal tidak ditampilkan, tetapi jika Anda menghapus komentar pada baris yang kami ubah nilainya, nilai yang diubah akan tetap ditampilkan. Mari kembalikan semuanya seperti semula dan buat acara e1 dua kali:

*Main> :l Example.hs Compiling Main (Example.hs, diinterpretasikan) Ok, modul yang dimuat: Main. *Utama> e1 utama dipicu e1 dipicu nilai v1: 13

Seperti yang Anda lihat, acara tersebut juga menembak dua kali. Mari kita coba untuk menghindari ini, mengapa dalam fungsi mendengarkan ganti argumen e1 pada (sekali e1), sehingga membuat acara baru yang diaktifkan sekali:

*Main> :l Example.hs Compiling Main (Example.hs, diinterpretasikan) Ok, modul yang dimuat: Main. *Utama> nilai v1 dipicu e1 utama: 13

Ketika suatu peristiwa tidak memiliki argumen, fakta ada atau tidak adanya peristiwa itu penting bagi kita, jadi fungsinya satu kali untuk menggabungkan acara adalah pilihan yang tepat. Namun, ketika ada argumen, ini tidak selalu tepat. Mari kita tulis ulang contohnya sebagai berikut:

<- newEvent (v1, changeV1) <- newBehavior 0 listen e1 $ \v ->putStrLn $ "e1 dipicu dengan: " ++ show v listen (nilai v1) $ \v -> putStrLn $ "nilai v1: " ++ show v triggerE1 "a" triggerE1 "b" triggerE1 "c" changeV1 13

Kami mendapatkan, seperti yang diharapkan, semua peristiwa dengan nilai dalam urutan yang sama di mana mereka dihasilkan:

*Main> :l Example.hs Compiling Main (Example.hs, diinterpretasikan) Ok, modul yang dimuat: Main. *Utama> e1 utama dipicu dengan: "a" e1 dipicu dengan: "b" e1 dipicu dengan: "c" nilai v1: 13

Jika kita menggunakan fungsi satu kali Dengan e1, maka kita hanya mendapatkan acara pertama, jadi mari kita coba menggunakan fungsinya bersatu, yang kita ganti argumennya e1 di mendengarkan argumen (menyatu (\_ a -> a) e1):

*Main> :l Example.hs Compiling Main (Example.hs, diinterpretasikan) Ok, modul yang dimuat: Main. *Utama> e1 utama dipicu dengan: "c" nilai v1: 13

Dan memang, kami hanya mendapat acara terakhir.

Contoh lainnya

Mari kita lihat contoh yang lebih rumit:

Impor FRP.Sodium main = lakukan sinkronisasi $do(e1,triggerE1)<- newEvent -- создаём характеристику, изменяемую событием e1 v1 <- hold 0 e1 listen e1 $ \v ->putStrLn $ "e1 dipicu dengan: " ++ show v mendengarkan (nilai v1) $ \v -> putStrLn $ "Nilai v1 adalah: " ++ show v -- memancarkan peristiwa triggerE1 1 triggerE1 2 triggerE1 3

Berikut adalah outputnya:

*Main> :l Example.hs Compiling Main (Example.hs, diinterpretasikan) Ok, modul yang dimuat: Main. *Utama> e1 utama dipicu dengan: 1 e1 dipicu dengan: 2 e1 dipicu dengan: 3 nilai v1 adalah: 3

Nilai karakteristik ditampilkan hanya sekali, meskipun beberapa peristiwa dihasilkan. Ini adalah kekhasan pemrograman sinkron: karakteristik disinkronkan dengan panggilan sinkronisasi. Untuk mendemonstrasikan ini, mari sedikit memodifikasi contoh kita:

<- sync $ do (e1, triggerE1) <- newEvent v1 <- hold 0 e1 listen e1 $ \v ->putStrLn $ "e1 dipicu dengan: " ++ show v listen (nilai v1) $ \v -> putStrLn $ "Nilai v1 adalah: " ++ show v return triggerE1 sync $ triggerE1 1 sync $ triggerE1 2 sync $ triggerE1 3

Kami baru saja memindahkan pemicu acara ke dunia luar dan menyebutnya dalam fase sinkronisasi yang berbeda:

*Main> :l Example.hs Compiling Main (Example.hs, diinterpretasikan) Ok, modul yang dimuat: Main. *Utama> nilai v1 utama adalah: 0 e1 dipicu dengan: 1 nilai v1 adalah: 1 e1 dipicu dengan: 2 nilai v1 adalah: 2 e1 dipicu dengan: 3 nilai v1 adalah: 3

Sekarang setiap peristiwa menunjukkan nilai baru.

Operasi lain pada primitif

Pertimbangkan kelompok fungsi yang berguna berikut:

Menggabungkan acara menggunakan mergeWith:: (a -> a -> a) -> Acara a -> Acara a -> Acara a -- Memfilter acara hanya dengan menyisakannya -- yang fungsinya mengembalikan filter sebenarnyaE:: (a -> Bool ) -> Acara a -> Acara a -- Mengizinkan acara "mematikan" -- ketika karakteristiknya False gate:: Acara a -> Behavior Bool -> Acara a -- Mengatur pengubah acara -- dengan keadaan internal kumpulkanE: : (a -> s -> (b, s)) -> s -> Acara a -> Reaktif (Acara b) -- Mengatur konverter fitur -- dengan keadaan internal mengumpulkan:: (a -> s -> ( b , s)) -> s -> Behavior a -> Reactive (Behavior b) -- Membuat karakteristik sebagai hasil akumulasi dari event accum:: a -> Event (a -> a) -> Reactive (Behavior a )

Tentu saja, ini tidak semua fungsi yang disediakan oleh perpustakaan. Masih banyak lagi hal-hal eksotis yang berada di luar cakupan artikel ini.

Contoh

Mari kita coba fungsi-fungsi ini dalam aksi. Mari kita mulai dengan yang terakhir, mengatur sesuatu seperti kalkulator. Mari kita memiliki beberapa nilai di mana kita dapat menerapkan fungsi aritmatika dan mendapatkan hasilnya:

Impor FRP.Sodium utama = lakukan triggerE1<- sync $ do (e1, triggerE1) <- newEvent -- пусть начальное значение будет равно 1 v1 <- accum (1:: Int) e1 listen (value v1) $ \v ->putStrLn $ "nilai v1 adalah: " ++ show v return triggerE1 -- ​​tambahkan 1 sync $ triggerE1 (+ 1) -- kalikan dengan 2 sync $ triggerE1 (* 2) -- kurangi 3 sync $ triggerE1 (+ (- 3) ) -- tambahkan 5 sync $ triggerE1 (+ 5) -- naikkan pangkat 3 sync $ triggerE1 (^ 3)

Ayo lari:

*Main> :l Example.hs Compiling Main (Example.hs, diinterpretasikan) Ok, modul yang dimuat: Main. *Utama> nilai v1 utama adalah: 1 nilai v1 adalah: 2 nilai v1 adalah: 4 nilai v1 adalah: 1 nilai v1 adalah: 6 nilai v1 adalah: 216

Tampaknya kumpulan fitur agak langka, tetapi sebenarnya tidak. Lagi pula, kita berurusan dengan Haskell, fungsi aplikatif dan monad belum hilang. Kami dapat melakukan operasi apa pun pada karakteristik dan peristiwa yang biasa kami lakukan pada nilai murni. Akibatnya, karakteristik dan peristiwa baru diperoleh. Untuk karakteristik, kelas functor dan functor aplikatif diimplementasikan, untuk acara, untuk alasan yang jelas, hanya functor.

Sebagai contoh:

<$>), (<*>)) impor FRP.Sodium main = do(setA, setB)<- sync $ do (a, setA) <- newBehavior 0 (b, setB) <- newBehavior 0 -- Новая характеристика a + b let a_add_b = (+) <$>sebuah<*>b -- Fitur baru a * b misalkan a_mul_b = (*)<$>sebuah<*>b mendengarkan (nilai a) $ \v -> putStrLn $ "a = " ++ show v listen (nilai b) $ \v -> putStrLn $ "b = " ++ show v listen (nilai a_add_b) $ \v - > putStrLn $ "a + b = " ++ show v listen (nilai a_mul_b) $ \v -> putStrLn $ "a * b = " ++ show v return (setA, setB) sync $ do setA 2 setB 3 sync $ sinkronisasi setA 3 $setB 7

Inilah yang akan menjadi output dalam interpreter:

> utama a = 0 b = 0 a + b = 0 a * b = 0 a = 2 b = 3 a + b = 5 a * b = 6 a = 3 a + b = 6 a * b = 9 b = 7 a + b = 10 a * b = 21

Sekarang mari kita lihat bagaimana sesuatu seperti ini bekerja dengan acara:

ImporKontrol.Aplikatif((<$>)) impor FRP.Sodium main = lakukan sigA<- sync $ do (a, sigA) <- newEvent let a_mul_2 = (* 2) <$>a biarkan a_pow_2 = (^2)<$>a listen a $ \v -> putStrLn $ "a = " ++ show v listen a_mul_2 $ \v -> putStrLn $ "a * 2 = " ++ show v listen a_pow_2 $ \v -> putStrLn $ "a ^ 2 = "++ tampilkan v kembali sigA sinkronkan $lakukan sigA 2 sinkronkan $sigA 3 sinkronkan $sigA 7

Inilah yang akan menjadi output:

> utama a = 2 a * 2 = 4 a ^ 2 = 4 a = 3 a * 2 = 6 a ^ 2 = 9 a = 7 a * 2 = 14 a ^ 2 = 49

Dokumentasi berisi daftar instance kelas yang diimplementasikan untuk Perilaku dan peristiwa, tetapi tidak ada yang mencegah Anda mengimplementasikan instance dari kelas yang hilang.

Kelemahan dari reaktivitas

Pemrograman reaktif fungsional tentu saja menyederhanakan pengembangan sistem real-time yang kompleks, tetapi ada banyak aspek yang perlu dipertimbangkan saat menggunakan pendekatan ini. Karena itu, kami akan mempertimbangkan di sini masalah yang paling sering terjadi.

Nonsimultanitas

Pemrograman sinkron menyiratkan mekanisme transaksi tertentu yang memastikan konsistensi status sistem yang berurutan, dan, akibatnya, tidak adanya status perantara yang tidak terduga. PADA Sodium panggilan bertanggung jawab untuk transaksi sinkronisasi. Meskipun keadaan di dalam transaksi tidak ditentukan, bagaimanapun, tidak dapat dianggap bahwa segala sesuatu di dalamnya terjadi pada waktu yang sama. Nilai berubah dalam urutan tertentu, yang memengaruhi hasil. Misalnya, berbagi peristiwa dan karakteristik dapat memiliki efek yang tidak terduga. Pertimbangkan sebuah contoh:

ImporKontrol.Aplikatif((<$>)) impor FRP.Sodium main = lakukan setVal<- sync $ do (val, setVal) <- newBehavior 0 -- создаём булеву характеристику val >2 biarkan gt2 = (> 2)<$>val -- buat event dengan nilai yang > 2 let evt = gate (nilai val) gt2 listen (nilai val) $ \v -> putStrLn $ "val = " ++ show v listen (nilai gt2) $ \ v -> putStrLn $ "val > 2 ? " ++ show v listen evt $ \v -> putStrLn $ "val > 2: " ++ show v return setVal sync $ setVal 1 sync $ setVal 2 sync $ setVal 3 sync $ sinkronisasi setVal 4 $setVal 0

Anda dapat mengharapkan output seperti ini:

Nilai = 0 nilai > 2 ? Nilai salah = 1 nilai > 2 ? Nilai salah = 2 nilai > 2 ? Nilai salah = 3 nilai > 2 ? Benar val > 2: 3 val = 4 val > 2 ? Benar val > 2: 4 val = 0 val > 2 ? PALSU

Namun, sebenarnya garis val > 2: 3 akan absen, dan pada akhirnya akan ada garis nilai > 2: 0. Ini karena peristiwa perubahan nilai (nilai nilai) dihasilkan sebelum karakteristik dependen dihitung gt2, dan acaranya evt tidak terjadi untuk nilai yang ditetapkan 3. Pada akhirnya, ketika kita menetapkan 0 lagi, perhitungan karakteristik gt2 terlambat.

Secara umum, efeknya sama seperti pada elektronik analog dan digital: balapan sinyal, untuk pertempuran yang mereka gunakan teknik yang berbeda. Secara khusus, sinkronisasi. Inilah yang akan kita lakukan untuk membuat kode ini berfungsi dengan baik:

ImporKontrol.Aplikatif((<$>)) import FRP.Sodium main = do(sigClk, setVal)<- sync $ do -- Мы ввели новое событие clk -- сигнал синхронизации -- прям как в цифровой электронике (clk, sigClk) <- newEvent (val, setVal) <- newBehavior 0 -- Также вы создали альтернативную функцию -- получения значения по сигналу синхронизации -- и заменили все вызовы value на value" let value" = snapshot (\_ v ->v) clk biarkan gt2 = (> 2)<$>val let evt = gerbang (nilai" val) gt2 mendengarkan (nilai" val) $ \v -> putStrLn $ "val = " ++ show v mendengarkan (nilai" gt2) $ \v -> putStrLn $ "val > 2 ? " ++ show v listen evt $ \v -> putStrLn $ "val > 2: " ++ show v return (sigClk, setVal) -- Memperkenalkan fungsi sinkronisasi baru -- yang memanggil sinyal sinkronisasi -- di akhir setiap transaksi - - Dan diganti dengan itu semua panggilan sync let sync" a = sync $ a >> sigClk () sync" $ setVal 1 sync" $ setVal 2 sync" $ setVal 3 sync" $ setVal 4 sync" $ setVal 0

Sekarang output kami seperti yang diharapkan:

> nilai utama = 0 nilai > 2 ? Nilai salah = 1 nilai > 2 ? Nilai salah = 2 nilai > 2 ? Nilai salah = 3 nilai > 2 ? Benar val > 2: 3 val = 4 val > 2 ? Benar val > 2: 4 val = 0 val > 2 ? PALSU

kemalasan

Masalah dari jenis yang berbeda terkait dengan sifat malas perhitungan di Haskell. Ini mengarah pada fakta bahwa ketika menguji kode dalam penerjemah, beberapa keluaran di akhir mungkin hilang begitu saja. Apa yang dapat disarankan dalam hal ini adalah melakukan langkah sinkronisasi yang tidak berguna di akhir, seperti sinkronisasi$kembali().

Kesimpulan

Itu sudah cukup untuk saat ini, saya pikir. Saat ini salah satu penulis perpustakaan Sodium menulis buku tentang FRP. Mudah-mudahan, ini entah bagaimana akan mengisi celah di bidang pemrograman ini dan berfungsi untuk mempopulerkan pendekatan progresif dalam pikiran kita yang kaku.

Dunia pengembangan OOP pada umumnya dan bahasa Java pada khususnya menjalani kehidupan yang sangat aktif. Ini memiliki tren mode sendiri, dan hari ini kami akan menganalisis salah satu tren utama musim ini - kerangka kerja ReactiveX. Jika Anda masih jauh dari gelombang ini - saya berjanji Anda akan menyukainya! Ini pasti lebih baik daripada jeans berpinggang tinggi :).

Pemrograman reaktif

Segera setelah bahasa OOP matang untuk digunakan secara massal, pengembang menyadari betapa kemampuan bahasa mirip-C terkadang kurang. Karena menulis kode dalam gaya pemrograman fungsional secara serius menghancurkan kualitas kode OOP, dan karenanya pemeliharaan proyek, hibrida diciptakan - pemrograman reaktif.

Paradigma pengembangan reaktif didasarkan pada gagasan untuk terus melacak perubahan keadaan suatu objek. Jika perubahan seperti itu telah terjadi, maka semua objek yang tertarik harus menerima data yang sudah diperbarui dan hanya bekerja dengannya, melupakan yang lama.

Contoh yang baik dari ide pemrograman reaktif adalah spreadsheet Excel. Jika Anda menautkan beberapa sel dengan satu rumus, hasil penghitungan akan berubah setiap kali data dalam sel ini berubah. Untuk akuntansi, perubahan data yang dinamis seperti itu adalah hal yang biasa, tetapi bagi programmer itu adalah pengecualian.

A=3; b=4; c=a+b; F1(c); a=1; F2(c);

Dalam contoh ini, fungsi F1 dan F2 akan bekerja dengan nilai variabel C yang berbeda. Seringkali kedua fungsi tersebut hanya memiliki data terbaru - pemrograman reaktif akan memungkinkan Anda untuk segera memanggil F1 dengan yang baru parameter tanpa mengubah logika fungsi itu sendiri. Struktur kode ini memberi aplikasi kemampuan untuk secara instan merespons setiap perubahan, yang akan membuatnya cepat, fleksibel, dan responsif.

ReaktifX

Menerapkan ide-ide pemrograman reaktif dari awal bisa sangat merepotkan - ada jebakan, dan itu akan memakan banyak waktu. Oleh karena itu, bagi banyak pengembang, paradigma ini tetap hanya materi teoretis sampai ReactiveX muncul.

Kerangka kerja ReactiveX adalah alat pemrograman reaktif yang bekerja dengan semua bahasa OOP populer. Pembuatnya sendiri menyebutnya sebagai API multi-platform untuk pengembangan asinkron, berdasarkan pola Pengamat.

Jika istilah "pemrograman reaktif" adalah semacam model teoretis, maka pola Pengamat adalah mekanisme siap pakai untuk melacak perubahan dalam suatu program. Dan Anda harus sering melacaknya: memuat dan memperbarui data, notifikasi acara, dan sebagainya.

Pola Pengamat telah ada selama OOP itu sendiri. Objek yang statusnya dapat berubah disebut penerbit (terjemahan populer dari istilah Observable). Semua peserta lain yang tertarik dengan perubahan ini adalah pelanggan (Pengamat, Pelanggan). Untuk menerima notifikasi, pelanggan mendaftar ke penerbit dengan menyebutkan ID mereka secara eksplisit. Penerbit dari waktu ke waktu menghasilkan pemberitahuan, yang dikirim oleh penerbit ke daftar pelanggan terdaftar.

Sebenarnya, pencipta ReactiveX tidak menemukan sesuatu yang revolusioner, mereka hanya dengan mudah menerapkan polanya. Dan meskipun banyak bahasa OOP, dan Java khususnya, memiliki implementasi pola yang sudah jadi, kerangka kerja ini memiliki "penyetelan" tambahan yang mengubah "Pengamat" menjadi alat yang sangat kuat.

RxAndroid

Port library ReactiveX untuk dunia Android disebut rxAndroid dan terhubung, seperti biasa, melalui Gradle.

Kompilasi "io.reactivex:rxandroid:1.1.0"

Penerbit yang menghasilkan notifikasi ditentukan di sini menggunakan kelas Observable. Penerbit dapat memiliki beberapa pelanggan; untuk menerapkannya, kami akan menggunakan kelas Pelanggan. Perilaku default untuk Observable adalah mengeluarkan satu atau beberapa pesan ke pelanggan dan kemudian keluar atau mengeluarkan pesan kesalahan. Pesan dapat berupa variabel dan objek integer.

Rx.Observable myObserv = rx.Observable.create(rx.Observable.OnSubscribe baru () ( @Override panggilan batal publik (Pelangganpelanggan) ( pelanggan.onBerikutnya("Halo"); pelanggan.onBerikutnya("dunia"); pelanggan.onSelesai(); ) ));

Dalam hal ini, penerbit myObserv pertama-tama akan mengirim string halo dan pesan, dan kemudian pesan sukses. Penerbit dapat memanggil metode onNext() , onCompleted() , dan onEror() , sehingga pelanggan harus menetapkannya.

Subscriber mySub = Pelanggan baru () (... @Override public void onNext(Nilai string) (Log.e("mendapat data", " " + nilai);) );

Semuanya siap untuk bekerja. Tetap menghubungkan objek satu sama lain - dan "Halo, dunia!" dalam pemrograman reaktif sudah siap!

MyObserv.berlangganan(mySub);

Saya harus mengatakan bahwa ini adalah contoh yang sangat sederhana. ReactiveX memiliki banyak opsi untuk perilaku semua peserta dalam pola: pemfilteran, pengelompokan, penanganan kesalahan. Manfaat dari pemrograman reaktif hanya dapat dirasakan dengan mencobanya dalam tindakan. Mari kita turun ke tugas lebih serius.

Lanjutan tersedia hanya untuk anggota

Opsi 1. Bergabunglah dengan komunitas "situs" untuk membaca semua materi di situs

Keanggotaan dalam komunitas selama periode yang ditentukan akan memberi Anda akses ke SEMUA materi Peretas, meningkatkan diskon kumulatif pribadi Anda dan memungkinkan Anda untuk mengumpulkan peringkat Skor Xakep profesional!

Saya ingin memberi tahu Anda tentang disiplin pemrograman modern yang memenuhi tuntutan yang berkembang untuk skalabilitas, toleransi kesalahan, dan respons cepat, dan sangat diperlukan di lingkungan multi-inti dan komputasi awan, serta menyajikan kursus online terbuka tentangnya, yang akan mulai hanya dalam beberapa hari.

Jika Anda belum pernah mendengar apa pun tentang pemrograman reaktif, Anda siap melakukannya. Ini adalah disiplin yang berkembang pesat yang menggabungkan konkurensi dengan event-driven dan asynchronous. Reaktivitas melekat dalam layanan web dan sistem terdistribusi apa pun, dan merupakan inti dari banyak sistem berkinerja tinggi dengan tingkat paralelisme yang tinggi. Singkatnya, penulis kursus mengusulkan untuk mempertimbangkan pemrograman reaktif sebagai perpanjangan alami dari pemrograman fungsional (dengan fungsi tingkat tinggi) ke sistem paralel dengan status terdistribusi, dikoordinasikan dan diatur oleh aliran data asinkron yang dipertukarkan oleh subjek aktif, atau aktor.

Dengan kata-kata yang lebih mudah dipahami, ini dijelaskan dalam Manifesto Reaktif, di bawah ini saya akan menceritakan kembali ketentuan utama darinya, dan terjemahan lengkapnya diterbitkan di Habré. Seperti yang dikatakan Wikipedia, istilah pemrograman reaktif telah ada sejak lama dan memiliki aplikasi praktis dari berbagai tingkat eksotisme, tetapi menerima dorongan baru untuk pengembangan dan distribusi baru-baru ini, berkat upaya penulis Manifesto Reaktif, sebuah kelompok inisiatif dari Typesafe Inc. Typesafe dikenal di komunitas pemrograman fungsional sebagai perusahaan yang didirikan oleh penulis bahasa Scala yang luar biasa dan platform paralel revolusioner Akka. Sekarang mereka memposisikan perusahaan mereka sebagai pencipta platform jet pertama di dunia yang dirancang untuk mengembangkan generasi baru. Platform mereka memungkinkan pengembangan cepat antarmuka pengguna yang kompleks dan menyediakan tingkat abstraksi baru melalui komputasi paralel dan multithreading, mengurangi risiko bawaan mereka dengan penskalaan yang dapat diprediksi dan dijamin. Ini mempraktekkan ide-ide dari Manifesto Reaktif dan memungkinkan pengembang untuk memahami dan membuat aplikasi yang memenuhi kebutuhan saat ini.

Anda dapat mengenal platform ini dan pemrograman reaktif dengan mengikuti Kursus Online Terbuka Besar-besaran Prinsip Reaktif. Kursus ini merupakan kelanjutan dari kursus "Prinsip Pemrograman Fungsional di Scala" Martin Odersky, yang telah diikuti lebih dari 100.000 peserta dan menunjukkan salah satu tingkat keberhasilan tertinggi dalam kursus online terbuka besar-besaran di dunia. Bersama dengan pencipta bahasa Scala, kursus baru ini diajarkan oleh Eric Meyer, yang mengembangkan kerangka Rx untuk pemrograman reaktif di bawah .NET, dan Roland Kuhn, yang saat ini memimpin tim pengembangan Akka di Typesafe. Kursus ini mencakup elemen kunci dari pemrograman reaktif dan menunjukkan bagaimana mereka diterapkan untuk merancang sistem yang digerakkan oleh peristiwa yang dapat diskalakan dan toleran terhadap kesalahan. Materi pelatihan diilustrasikan dengan program pendek dan disertai dengan serangkaian tugas, yang masing-masing merupakan proyek perangkat lunak. Dalam hal berhasil menyelesaikan semua tugas, peserta menerima sertifikat (tentu saja, partisipasi dan sertifikat gratis). Kursus berlangsung 7 minggu dan dimulai Senin ini, 4 November. Garis besar terperinci serta video pengantar tersedia di halaman kursus: https://www.coursera.org/course/reactive.

Bagi mereka yang tertarik atau ragu, saya menawarkan ringkasan singkat tentang konsep dasar Manifesto Reaktif. Penulisnya mencatat perubahan signifikan dalam persyaratan untuk aplikasi dalam beberapa tahun terakhir. Saat ini, aplikasi diterapkan di lingkungan apa pun mulai dari perangkat seluler hingga kluster cloud dengan ribuan prosesor multi-inti. Lingkungan ini menempatkan tuntutan baru pada perangkat lunak dan teknologi. Arsitektur generasi sebelumnya berfokus pada server dan wadah terkelola, penskalaan melalui perangkat keras tambahan yang mahal, solusi eksklusif, dan komputasi paralel melalui multithreading. Saat ini sedang dikembangkan arsitektur baru yang memiliki empat fitur utama yang semakin lazim di lingkungan industri konsumen dan perusahaan. Sistem dengan arsitektur ini: event-driven (Event-driven), scalable (Scalable), fault-tolerant (Resilient) dan memiliki respon yang cepat, mis. tanggap (Responsif). Ini memberikan pengalaman pengguna yang mulus dengan nuansa real-time dan didukung oleh tumpukan aplikasi yang dapat diskalakan dan pulih sendiri yang siap digunakan di lingkungan multi-inti dan cloud. Masing-masing dari empat karakteristik arsitektur reaktif berlaku untuk seluruh tumpukan teknologi, yang membedakannya dari tautan dalam arsitektur berlapis. Mari kita pertimbangkan mereka sedikit lebih detail.


Didorong oleh Acara aplikasi mengasumsikan komunikasi komponen asinkron dan menerapkan desain yang digabungkan secara longgar: pengirim dan penerima pesan tidak perlu mengetahui baik tentang satu sama lain atau tentang metode transmisi pesan, yang memungkinkan mereka untuk berkonsentrasi pada konten komunikasi. Selain fakta bahwa komponen yang digabungkan secara longgar secara signifikan meningkatkan pemeliharaan, ekstensibilitas, dan evolusi sistem, sifat asinkron dan non-pemblokiran dari interaksi mereka juga dapat membebaskan sebagian besar sumber daya, mengurangi waktu respons, dan menyediakan tentang throughput yang lebih tinggi daripada aplikasi tradisional. Berkat sifat yang digerakkan oleh peristiwa, fitur arsitektur reaktif lainnya dimungkinkan.

Skalabilitas dalam konteks pemrograman reaktif, ini adalah reaksi sistem terhadap perubahan beban, mis. elastisitas, dicapai dengan kemampuan untuk menambah atau melepaskan node komputasi sesuai kebutuhan. Dengan kopling rendah, pesan asinkron, dan transparansi lokasi, metode penerapan dan topologi aplikasi menjadi keputusan waktu penerapan dan tunduk pada konfigurasi responsif beban dan algoritme adaptif. Dengan demikian, jaringan komputer menjadi bagian dari aplikasi yang pada awalnya bersifat terdistribusi secara eksplisit.

toleransi kesalahan arsitektur reaktif juga menjadi bagian dari desain, dan ini membuatnya sangat berbeda dari pendekatan tradisional untuk memastikan ketersediaan sistem yang berkelanjutan melalui redundansi server dan failover. Kekokohan sistem tersebut dicapai dengan kemampuannya untuk merespon dengan benar kegagalan komponen individu, mengisolasi kegagalan ini dengan menyimpan konteksnya dalam bentuk pesan yang menyebabkannya, dan meneruskan pesan ini ke komponen lain yang dapat membuat keputusan tentang bagaimana menangani kesalahan. Pendekatan ini memungkinkan Anda untuk menjaga logika bisnis aplikasi tetap bersih, memisahkan logika penanganan kegagalan darinya, yang dirumuskan dalam bentuk deklaratif eksplisit untuk pendaftaran, isolasi, dan penanganan kegagalan melalui sistem itu sendiri. Untuk membangun sistem penyembuhan diri seperti itu, komponen-komponen diurutkan secara hierarkis, dan masalahnya ditingkatkan ke tingkat yang dapat menyelesaikannya.

Dan akhirnya daya tanggap- adalah kemampuan sistem untuk merespon masukan pengguna terlepas dari beban dan kegagalan, aplikasi tersebut melibatkan pengguna dalam interaksi, menciptakan perasaan hubungan yang erat dengan sistem dan peralatan yang memadai untuk melakukan tugas saat ini. Ketanggapan relevan tidak hanya dalam sistem waktu nyata, tetapi juga diperlukan untuk berbagai aplikasi. Selain itu, sistem yang tidak dapat merespons dengan cepat bahkan pada saat kegagalan tidak dapat dianggap toleran terhadap kesalahan. Ketanggapan dicapai dengan menggunakan model yang dapat diamati, aliran peristiwa, dan klien stateful. Model yang dapat diamati memancarkan peristiwa ketika keadaannya berubah dan menyediakan interaksi waktu nyata antara pengguna dan sistem, sementara aliran peristiwa menyediakan abstraksi di mana interaksi ini dibangun melalui transformasi dan komunikasi asinkron yang tidak memblokir.

Dengan demikian, aplikasi reaktif mewakili pendekatan yang seimbang untuk memecahkan berbagai masalah pengembangan perangkat lunak modern. Dibangun di atas fondasi yang digerakkan oleh peristiwa, mereka menyediakan alat yang diperlukan untuk menjamin skalabilitas dan ketahanan, dan mendukung pengalaman pengguna responsif berfitur lengkap. Penulis mengharapkan semakin banyak sistem untuk mematuhi prinsip-prinsip manifesto reaktif.

Selain itu, saya memberikan rencana kursus tanpa terjemahan. Kalau-kalau Anda sudah membaca sejauh ini dan masih penasaran.

Minggu 1: Tinjauan Prinsip Pemrograman Fungsional: model substitusi, ekspresi-for dan bagaimana kaitannya dengan monad. Memperkenalkan implementasi baru untuk ekspresi: generator nilai acak. Menunjukkan bagaimana ini dapat digunakan dalam pengujian acak dan memberikan gambaran umum tentang ScalaCheck, alat yang mengimplementasikan ide ini.

Minggu 2: Pemrograman fungsional dan status yang bisa berubah. Apa yang membuat suatu objek bisa berubah? Bagaimana ini berdampak pada model substitusi. Contoh yang diperluas: Simulasi sirkuit digital

Minggu 3: berjangka. Memperkenalkan futures sebagai monad lain, dengan ekspresi for sebagai sintaksis konkret. Menunjukkan bagaimana masa depan dapat disusun untuk menghindari pemblokiran utas. Membahas penanganan kesalahan lintas-utas.

Minggu 4: Pemrosesan aliran reaktif. Generalisasi masa depan ke perhitungan reaktif melalui aliran. operator aliran.

Minggu 5: Aktor. Memperkenalkan Model Aktor, aktor sebagai unit konsistensi yang dienkapsulasi, penyampaian pesan asinkron, membahas semantik pengiriman pesan yang berbeda (paling banyak sekali, setidaknya sekali, tepat sekali) dan konsistensi akhirnya.

Minggu 6: pengawasan. Memperkenalkan reifikasi kegagalan, penanganan kegagalan hierarkis, pola Kernel Kesalahan, pemantauan siklus hidup, membahas status sementara dan persisten.

Minggu 7: Pola Percakapan. Membahas pengelolaan status percakapan antara aktor dan pola untuk kontrol aliran, perutean pesan ke kumpulan aktor untuk ketahanan atau penyeimbangan beban, pengakuan penerimaan untuk mencapai pengiriman yang andal.

Seiring waktu, bahasa pemrograman terus berubah dan berkembang karena munculnya teknologi baru, persyaratan modern, atau keinginan sederhana untuk menyegarkan gaya penulisan kode. Pemrograman reaktif dapat diimplementasikan menggunakan berbagai kerangka kerja seperti Kakao Reaktif. Ini mengubah ruang lingkup gaya imperatif bahasa Objective-C, dan pendekatan pemrograman ini memiliki banyak hal untuk ditawarkan pada paradigma standar. Hal ini tentu saja menarik perhatian para developer iOS.

ReactiveCocoa membawa gaya deklaratif ke Objective-C. Apa yang kita maksud dengan ini? Gaya imperatif tradisional yang digunakan oleh bahasa seperti C, C++, Objective-C, dan Java, dll. dapat dijelaskan sebagai berikut: Anda menulis arahan untuk program komputer yang harus dijalankan dengan cara tertentu. Dengan kata lain, Anda mengatakan "bagaimana melakukan" sesuatu. Sementara pemrograman deklaratif memungkinkan Anda untuk menggambarkan aliran kontrol sebagai urutan tindakan, "apa yang harus dilakukan", tanpa mendefinisikan "bagaimana melakukannya".

Pemrograman Imperatif vs Fungsional

Pendekatan imperatif untuk pemrograman adalah deskripsi terperinci dari setiap langkah yang harus dilakukan komputer untuk menyelesaikan tugas. Faktanya, gaya imperatif digunakan dalam bahasa pemrograman asli (atau digunakan saat menulis kode mesin). Omong-omong, ini adalah fitur karakteristik dari sebagian besar bahasa pemrograman.

Sebaliknya, pendekatan fungsional memecahkan masalah dengan serangkaian fungsi yang perlu dilakukan. Anda menentukan parameter input untuk setiap fungsi, dan apa yang dikembalikan oleh setiap fungsi. Kedua pendekatan pemrograman ini sangat berbeda.

Berikut adalah perbedaan bahasa utama:

1. Perubahan status

Untuk pemrograman fungsional murni, tidak ada perubahan status karena tidak ada efek samping. Efek samping menyiratkan perubahan status selain nilai kembalian karena beberapa interaksi eksternal. SR (transparansi referensial) dari subekspresi sering didefinisikan sebagai "tidak adanya efek samping" dan terutama mengacu pada fungsi murni. SP tidak mengizinkan eksekusi fungsi memiliki akses eksternal ke status volatilitas fungsi, karena setiap subekspresi adalah panggilan fungsi menurut definisi.

Untuk memperjelas, fungsi murni memiliki atribut berikut:

  • satu-satunya keluaran yang penting adalah nilai baliknya
  • satu-satunya ketergantungan parameter input adalah argumen
  • argumen sepenuhnya memenuhi syarat sebelum menghasilkan output apa pun

Terlepas dari kenyataan bahwa pendekatan fungsional meminimalkan efek samping, mereka tidak dapat sepenuhnya dihindari, karena merupakan bagian intrinsik dari perkembangan apa pun.

Di sisi lain, fungsi dalam pemrograman imperatif tidak memiliki transparansi referensial, dan ini mungkin satu-satunya perbedaan antara pendekatan deklaratif dan imperatif. Efek samping banyak digunakan untuk mengimplementasikan status dan I/O. Perintah dalam bahasa sumber dapat mengubah keadaan, menghasilkan nilai yang berbeda untuk ekspresi bahasa yang sama.

Bagaimana dengan ReactiveCocoa? Ini adalah kerangka kerja fungsional untuk Objective-C, yang merupakan bahasa imperatif secara konseptual, tidak termasuk fungsi murni yang eksplisit. Saat mencoba menghindari perubahan status, efek sampingnya tidak terbatas.

2. Objek kelas pertama

Dalam pemrograman fungsional, ada objek dan fungsi yang merupakan objek kelas satu. Apa artinya? Ini berarti bahwa fungsi dapat diteruskan sebagai parameter, ditetapkan ke variabel, atau dikembalikan dari suatu fungsi. Mengapa nyaman? Ini memudahkan untuk mengelola blok eksekusi, membuat dan menggabungkan fungsi dalam berbagai cara tanpa kerumitan pointer fungsi (char *(*(**foo)()); - bersenang-senang!).

Bahasa yang menggunakan pendekatan imperatif memiliki kekhasan mereka sendiri sehubungan dengan ekspresi kelas satu. Bagaimana dengan Objective-C? Ini memiliki blok sebagai implementasi penutupan. Fungsi orde tinggi (HFO) dapat dimodelkan dengan mengambil blok sebagai parameter. Dalam hal ini, blok adalah penutupan, dan fungsi tingkat tinggi dapat dibuat dari kumpulan blok tertentu.

Namun, proses memanipulasi FVP dalam bahasa fungsional lebih cepat dan membutuhkan lebih sedikit baris kode.

3. Kontrol aliran utama

Loop gaya imperatif direpresentasikan sebagai panggilan ke fungsi rekursi dalam pemrograman fungsional. Iterasi dalam bahasa fungsional biasanya dilakukan melalui rekursi. Mengapa? Mungkin demi kerumitan. Untuk pengembang Objective-C, loop tampak jauh lebih ramah programmer. Rekursi dapat menyebabkan kesulitan, seperti konsumsi RAM yang berlebihan.

Tetapi! Kita dapat menulis sebuah fungsi tanpa menggunakan loop atau rekursi. Untuk setiap tindakan khusus yang sangat mungkin yang dapat diterapkan ke setiap elemen koleksi, pemrograman fungsional menggunakan fungsi iteratif yang dapat digunakan kembali seperti “ peta”, “melipat”, “". Fungsi-fungsi ini berguna untuk memfaktorkan ulang kode sumber. Mereka mengurangi duplikasi dan tidak memerlukan penulisan fungsi yang terpisah. (baca terus, kami memiliki informasi lebih lanjut tentang itu!)

4. Urutan eksekusi

Ekspresi deklaratif hanya menunjukkan hubungan logis dari argumen fungsi subekspresi dan hubungan status persisten. Jadi tanpa adanya efek samping, transisi keadaan dari setiap panggilan fungsi terjadi secara independen dari yang lain.

Urutan fungsional eksekusi ekspresi imperatif tergantung pada keadaan volatil. Oleh karena itu, urutan eksekusi penting dan secara implisit ditentukan oleh organisasi kode sumber. Dalam pertanyaan ini, kita dapat menunjukkan perbedaan antara strategi evaluasi dari kedua pendekatan.

Evaluasi malas atau sesuai kebutuhan adalah strategi dalam bahasa pemrograman fungsional. Dalam kasus seperti itu, evaluasi ekspresi ditangguhkan sampai nilainya diperlukan, di mana kita menghindari evaluasi berulang. Dengan kata lain, ekspresi hanya dievaluasi ketika ekspresi dependen dievaluasi. Urutan operasi menjadi tidak terdefinisi.

Sebaliknya, evaluasi yang kuat dalam bahasa imperatif berarti bahwa ekspresi akan dievaluasi segera setelah terikat pada variabel. Ini melibatkan mendikte urutan eksekusi, sehingga lebih mudah untuk menentukan kapan subekspresi (termasuk fungsi) akan dievaluasi, karena subekspresi dapat memiliki efek samping yang mempengaruhi evaluasi ekspresi lainnya.

5. Jumlah kode

Ini penting, pendekatan fungsional membutuhkan penulisan kode lebih sedikit daripada yang imperatif. Ini berarti lebih sedikit kerusakan, lebih sedikit kode untuk diuji, dan siklus pengembangan yang lebih produktif. Karena sistem terus berkembang dan berkembang, ini penting.

Komponen utama ReactiveCocoa

Pemrograman fungsional berkaitan dengan konsep yang dikenal sebagai masa depan (representasi read-only dari variabel) dan janji (representasi read-only dari variabel masa depan). Apa yang baik tentang mereka? Dalam pemrograman imperatif, Anda harus bekerja dengan nilai-nilai yang sudah ada, yang mengarah pada kebutuhan untuk menyinkronkan kode asinkron dan kesulitan lainnya. Tetapi konsep masa depan dan janji memungkinkan Anda untuk bekerja dengan nilai yang belum dibuat (kode asinkron ditulis secara sinkron).


Sinyal

Masa depan dan janji direpresentasikan sebagai sinyal dalam pemrograman reaktif. - komponen utama ReactiveCocoa. Ini memberikan kesempatan untuk menyajikan alur peristiwa yang akan disajikan di masa depan. Anda berlangganan sinyal dan mendapatkan akses ke acara yang akan terjadi seiring waktu. Sinyal adalah utas yang digerakkan oleh push dan dapat berupa penekanan tombol, operasi jaringan asinkron, pengatur waktu, peristiwa UI lainnya, atau apa pun yang berubah seiring waktu. Mereka dapat menghubungkan hasil operasi asinkron dan secara efisien menggabungkan beberapa sumber peristiwa.

selanjutnya

Jenis lain dari aliran adalah urutan. Tidak seperti sinyal, urutan adalah aliran yang digerakkan oleh tarikan. Ini adalah jenis koleksi yang memiliki tujuan yang sama dengan NSArray. RACSequence memungkinkan operasi tertentu dilakukan saat Anda membutuhkannya, bukan secara berurutan, seperti pada koleksi NSArray. Nilai dalam urutan hanya dievaluasi jika ditentukan secara default. Menggunakan hanya sebagian dari urutan yang berpotensi meningkatkan kinerja. RACSequence memungkinkan koleksi Kakao ditangani dengan cara yang umum dan deklaratif. RAC menambahkan metode -rac_sequence ke sebagian besar kelas koleksi Kakao sehingga dapat digunakan sebagai RACSequences.

Tim

Menanggapi tindakan tertentu, perintah RACC dan berlangganan sinyal. Ini berlaku terutama untuk interaksi UI. Kategori UIKit disediakan oleh ReactiveCocoa untuk sebagian besar kontrol UIKit, beri kami cara yang benar untuk menangani peristiwa UI. Mari kita bayangkan bahwa kita harus mendaftarkan pengguna sebagai respons terhadap klik tombol. Dalam hal ini, perintah dapat mewakili permintaan jaringan. Saat proses dimulai, tombol berubah statusnya menjadi "tidak aktif" dan sebaliknya. Apa lagi? Kita dapat melewatkan sinyal aktif dalam sebuah perintah (Reachability adalah contoh yang baik). Oleh karena itu, jika server tidak tersedia (yang merupakan "sinyal aktif" kami), maka perintah tidak akan tersedia, dan setiap perintah dari kontrol terkait akan mencerminkan status ini.

Contoh Operasi Dasar

Berikut adalah beberapa diagram tentang cara kerja operasi dasar dengan RACsignals:

Menggabungkan

+ (RACSignal *)gabung:(id ) sinyal;


Aliran hasil memiliki kedua aliran peristiwa yang digabungkan bersama. Jadi "+gabungkan" berguna ketika Anda tidak peduli dengan sumber peristiwa tertentu, tetapi ingin menanganinya di satu tempat. Dalam contoh kita, stateLabel.text menggunakan 3 sinyal berbeda: penyelesaian, penyelesaian, kesalahan.

RACCommand *loginCommand = [ initWithSignalBlock:^RACSignal *(id input) ( // mari login!)]; RACsignal *executionSignal = ; RACSignal *completionSignal = filter:^BOOL(RACEvent *event) ( kembali event.eventType == RACEventTypeCompleted; )] peta:^id(nilai id) ( kembali @"Selesai"; )]; )]; RACsignal *errorSignal = ; RAC(self.stateLabel, teks) = ];

+ (RACSignal *)combineTerbaru :( )sinyal mengurangi:(id (^)())reduceBlock;

Akibatnya, aliran berisi nilai terbaru dari aliran yang ditransmisikan. Jika salah satu aliran tidak masalah, maka hasilnya akan kosong.


Kapan kita bisa menggunakannya? Mari kita ambil contoh kita sebelumnya dan tambahkan lebih banyak logika padanya. Berguna untuk hanya mengaktifkan tombol login ketika pengguna telah memasukkan email dan kata sandi yang benar, bukan? Kita dapat mendeklarasikan aturan ini seperti ini:

ACSignal *enabledSignal = reduce:^id (NSString *email, NSString *password) ( return @( && password.length > 3); )];

*Sekarang mari kita ubah sedikit perintah login kita dan hubungkan ke tombol login yang sebenarnya

RACCommand *loginCommand = [ initWithEnabled:enabledSignal signalBlock:^RACSignal *(id masukan) ( // mari masuk! )]; ;

- (RACSignal *)flattenMap:(RACStream * (^)(nilai id))blok;

Anda membuat aliran baru untuk setiap nilai dalam aliran asli menggunakan fungsi yang diberikan (f). Aliran hasil mengembalikan sinyal baru berdasarkan nilai yang dihasilkan dalam aliran asli. Jadi bisa asinkron.


Mari kita bayangkan bahwa permintaan otorisasi Anda ke sistem terdiri dari dua bagian terpisah: dapatkan data dari Facebook (ID, dll.) dan berikan ke Backend. Salah satu syaratnya adalah bisa membatalkan login. Oleh karena itu, kode klien harus menangani status proses login agar dapat membatalkannya. Ini memberikan banyak kode boilerplate, terutama jika Anda dapat masuk dari beberapa lokasi.

Bagaimana ReactiveCocoa membantu Anda? Ini bisa menjadi implementasi login:

- (RACSignal *)authorizeUsingFacebook ( return [[ flattenMap:^RACStream *(FBSession *session) ( return ; )] flattenMap:^RACStream *(NSDictionary *profile) ( return ; )]; )

legenda:

+ - sinyal yang mengarah ke pembukaan FBSesi. Jika perlu, ini dapat menyebabkan masuk ke Facebook.

- - sinyal yang mengambil data profil melalui sesi, yang ditransmisikan sebagai diri sendiri.

Keuntungan dari pendekatan ini adalah bagi pengguna, seluruh aliran tidak jelas, diwakili oleh satu sinyal yang dapat dibatalkan pada "tahap" apa pun, baik itu login Facebook atau panggilan Backend.

Saring

- (RACSignal *)filter:(BOOL (^)(nilai id))blok;

Akibatnya, aliran berisi nilai aliran "a", difilter sesuai dengan fungsi yang diberikan.


RACSequence *sequence = @[@"Some", @"example", @"of", @"sequence"].rac_sequence; RACSequence *filteredSequence = ; )];

Peta

- (RACSignal *)peta:(id (^)(nilai id))blok;

Tidak seperti FlattenMap, Peta berjalan secara sinkron. Nilai properti "a" melewati fungsi yang diberikan f (x + 1) dan mengembalikan nilai asli yang dipetakan.


Katakanlah Anda ingin memasukkan judul model di layar, menerapkan beberapa atribut padanya. Peta ikut bermain ketika "Menerapkan beberapa atribut" digambarkan sebagai fungsi mandiri:

RAC(self.titleLabel, teks) = initWithString:modelTitle atribut:atribut]; )];

Cara kerjanya: bersatu self.titleLabel.text dengan perubahan model.title dengan menerapkan atribut khusus padanya.

Zip

+ (Sinyal RAC *)zip:(id )streams mengurangi:(id (^)())reduceBlock;

Peristiwa aliran hasil dihasilkan ketika masing-masing aliran telah menghasilkan jumlah peristiwa yang sama. Ini berisi nilai, satu dari masing-masing dari 3 aliran gabungan.


Untuk beberapa contoh praktis, zip dapat digambarkan sebagai: kirim_grup_beritahu Misalnya, Anda memiliki 3 sinyal terpisah dan Anda ingin menggabungkan responsnya pada satu titik:

NSArray *sinyal = @; kembali;

- (RACSignal *)throttle:(NSTimeInterval)interval;

Dengan pengatur waktu yang disetel untuk jangka waktu tertentu, nilai pertama dari aliran "a" hanya diteruskan ke aliran hasil ketika pengatur waktu berakhir. Jika nilai baru dihasilkan dalam interval waktu tertentu, nilai pertama tetap dipertahankan, mencegahnya diteruskan ke aliran hasil. Sebagai gantinya, nilai kedua muncul di aliran hasil.


Kasus yang mengejutkan: kita perlu mencari kueri ketika pengguna mengubah searchField. Masalah standar, kan? Namun, ini tidak terlalu efisien untuk membangun dan mengirim permintaan jaringan setiap kali teks berubah, karena textField dapat menghasilkan banyak peristiwa seperti itu per detik, dan Anda berakhir dengan penggunaan jaringan yang tidak efisien.
Solusinya di sini adalah menambahkan penundaan setelah itu kami benar-benar membuat permintaan jaringan. Ini biasanya dicapai dengan menambahkan NSTimer. Dengan ReactiveCocoa jauh lebih mudah!

[[ throttle:0.3] subscribeNext:^(NSString *text) ( // melakukan permintaan jaringan )];

*Catatan penting di sini adalah bahwa semua textFields "sebelumnya" dimodifikasi sebelum yang "terakhir" dihapus.

penundaan

- (RACSignal *)delay:(NSTimeInterval)interval;

Nilai yang diterima dalam aliran "a" ditunda dan ditransfer ke aliran hasil setelah interval waktu tertentu.


Seperti -, penundaan hanya akan menunda pengiriman acara "berikutnya" dan "selesai".

[ berlanggananBerikutnya:^(NSString *teks) ( )];

Apa yang kami sukai dari Kakao Reaktif

  • Memperkenalkan Cocoa Bindings ke iOS
  • Kemampuan untuk membuat operasi pada data masa depan. Berikut adalah beberapa teori tentang futures & janji dari Scala.
  • Kemampuan untuk merepresentasikan operasi asinkron dengan cara yang sinkron. Kakao Reaktif menyederhanakan perangkat lunak asinkron seperti kode jaringan.
  • Dekomposisi yang nyaman. Kode yang berhubungan dengan peristiwa pengguna dan perubahan status aplikasi bisa menjadi sangat kompleks dan membingungkan. Kakao Reaktif membuat model operasi dependen menjadi sangat sederhana. Ketika kami merepresentasikan operasi sebagai utas gabungan (misalnya, pemrosesan permintaan jaringan, peristiwa pengguna, dll.), kami dapat mencapai modularitas tinggi dan kopling longgar, menghasilkan lebih banyak penggunaan kembali kode.
  • Perilaku dan hubungan antara properti didefinisikan sebagai deklaratif.
  • Memecahkan masalah sinkronisasi - jika Anda menggabungkan beberapa sinyal maka ada satu tempat untuk menangani semua hasil (apakah nilai berikutnya, penyelesaian atau sinyal kesalahan)

Dengan kerangka kerja RAC, Anda dapat membuat dan mengubah urutan nilai dengan cara yang lebih baik dan lebih tinggi. RAC memudahkan untuk mengelola segala sesuatu yang menunggu untuk menyelesaikan operasi asinkron: respons jaringan, perubahan nilai dependen, dan reaksi selanjutnya. Sulit untuk ditangani pada pandangan pertama, tetapi ReactiveCocoa menular!



Memuat...
Atas