Čo je reaktívne programovanie. Pochopenie rámca ReactiveX a písanie v reaktívnom štýle pre Android

Princípy reaktívneho programovania nie sú nové a možno ich vysledovať až k kľúčovej práci Jima Graya a Pata Hellanda o tandemovom systéme v 70. a 80. rokoch.

Títo ľudia výrazne predbehli dobu. Až za posledných 5 – 10 rokov bol technologický priemysel nútený prehodnotiť existujúce „ osvedčené postupy» na rozvoj podnikového systému. Naučila sa aplikovať poznatky o reaktívnych princípoch dnešného sveta multi-core a cloud computingu.

Základom pre reaktívny systém je odovzdávanie správ, ktoré vytvára časovú hranicu medzi komponentmi, umožňuje ich časové oddelenie pomocou paralelizmu a priestoru, ktorý rozdeľuje záťaž a poskytuje mobilitu. Toto oddelenie je požiadavkou na úplnú izoláciu medzi komponentmi a tvorí základ pre stabilitu a odolnosť systémov.

Základy reaktívneho programovania

Toto programovanie sa zameriava na tok informácií a šírenie zmien údajov. Pri používaní programovacích jazykov je ľahké rozlíšiť medzi statickými a dynamickými vláknami základný model automaticky prenesie zmeny do všetkých dátových tokov. Jednoducho povedané, v programovaní Rx, vysielané jedným komponentom a základná štruktúra poskytovaná knižnicami Rx, prenesie tieto zmeny do iného komponentu zaregistrovaného na prijímanie týchto zmien. Reaktívne programovanie Rx pozostáva z troch kľúčových bodov.

Hlavné funkcie komponentov:

  1. Pozorovateľné nie sú nič iné ako prúdy údajov. Pozorovateľné zhromažďuje údaje, ktoré možno prenášať z jedného prúdu do druhého. V zásade vydávajú údaje periodicky alebo len raz za svoj životný cyklus na základe konfigurácií. Existujú rôzne operátory, ktoré môžu pomôcť pozorovateľovi odoslať niektoré špecifické údaje na základe určitých udalostí.
  2. Pozorovatelia spotrebúvajú dátový tok emitovaný pozorovateľným. Pozorovatelia sa prihlasujú pomocou metódy reaktívneho programovania subscribeOn() na prijímanie údajov odovzdávaných pozorovateľným objektom. Kedykoľvek pozorovateľ odošle údaje, všetci registrovaní pozorovatelia dostanú údaje v spätnom volaní onNext(). Tu môžu vykonávať rôzne operácie, ako je analýza odpovede JSON alebo aktualizácia používateľského rozhrania. Ak je chyba spôsobená pozorovateľným, pozorovateľ ju dostane v onError().
  3. Plánovače (schedule) je komponent v Rx, ktorý hovorí pozorovateľom a pozorovateľom, na ktorom vlákne by mali bežať. Pomocou metódy observOn() môžete povedať pozorovateľom, ktoré vlákno by mali sledovať. Alternatívne možno použiť schedOn() na to, aby ste povedali pozorovateľnému, na ktorom vlákne by mali bežať.

Pri reaktívnom programovaní pomocou RxJava predvolené jadrové vlákna ako Schedulers.newThread() vytvoria nové pozadie. Schedulers.io() spustí kód na I/O vlákne.

Hlavnými výhodami Rx sú zvýšené využitie výpočtových zdrojov na viacjadrovom a viacprocesorovom hardvéri, zlepšený výkon znížením počtu bodov a zlepšený výkon znížením počtu serializačných bodov podľa Amdahlovho zákona a Güntherovho zákona o univerzálnej škálovateľnosti.

Druhou výhodou je vysoká produktivita pre vývojárov, pretože tradičné programovacie paradigmy majú problém poskytnúť jednoduchý a udržiavateľný prístup k asynchrónnym a neblokujúcim výpočtom a IO. Funkčné reaktívne programovanie zvláda tieto úlohy, pretože zvyčajne eliminuje potrebu explicitnej koordinácie medzi aktívnymi komponentmi.

Tam, kde sa vyskytuje Rx, vzniká proces vytvárania komponentov a skladba pracovných tokov. Ak chcete naplno využiť výhody asynchrónneho vykonávania, je dôležité povoliť spätný tlak, aby ste sa vyhli nadmernému využívaniu alebo skôr neobmedzenej spotrebe zdrojov. Aby sa zabezpečil stabilný stav, pokiaľ ide o tok údajov, spätný tlak založený na záťaži vysiela dopyt prúdiaci proti prúdu a prijíma správy.

Hlavné výhody systému sú teda:

  1. Zvýšený výkon – vďaka schopnosti rýchlo a konzistentne spracovávať obrovské množstvá dát.
  2. Vylepšené UX – vďaka tomu, že aplikácia lepšie reaguje na používateľa.
  3. Zjednodušené úpravy a aktualizácie – vďaka čitateľnejšiemu a ľahšie predvídateľnému kódu.

Ale aj keď je Reaktívne programovanie veľmi užitočná vec pri budovaní moderného softvéru, na premýšľanie o systéme na vyššej úrovni musíte použiť iný nástroj - Reactive Architecture pre proces navrhovania reaktívnych systémov. Tiež je dôležité si uvedomiť, že existuje veľa programovacích paradigiem a Rx je len jednou z nich, ako každý nástroj, nie je určený pre všetky prípady použitia.

Stabilita reaktívneho systému

Odolnosť je schopnosť reagovať na zlyhanie a je prirodzenou funkčnou vlastnosťou systému. Potrebuje vývoj, nielen spätné doplnenie systému. Odolnosť reaktívneho javascriptového programovania presahuje toleranciu chýb a nie je to spôsobené degradáciou, ale v prípade zlyhania sa môže úplne opraviť.

To si vyžaduje izoláciu komponentov a kontrolu porúch, aby sa predišlo šíreniu porúch na susedné komponenty, čo môže viesť ku kaskádovým scenárom katastrofických porúch. Kľúčom k budovaniu odolných systémov je teda to, že ich možno charakterizovať ako správy odosielané iným komponentom, ktoré pôsobia ako supervízory a sú riadené z bezpečného kontextu mimo zlyhaného komponentu.

Tu, keďže sú riadené správami, tieto zariadenia sa vzďaľujú od tesne prepojených, krehkých, hlboko vnorených synchrónnych volacích reťazcov, ktoré sú väčšinou ignorované. Cieľom je oddeliť riešenie porúch od reťazca hovorov, napríklad oslobodením klienta od zodpovednosti za spracovanie zlyhania servera.

Keďže väčšina systémov je vo svojej podstate zložitá, jedným z najdôležitejších aspektov je zabezpečiť, aby architektúra systému zaisťovala minimálnu degradáciu výkonu pri vývoji aj údržbe komponentov a zároveň znížila náhodnú zložitosť na minimum. Je to dôležité, pretože počas životný cyklus Systémy, ak nie sú správne navrhnuté, budú čoraz náročnejšie na údržbu a bude si vyžadovať stále viac času a úsilia na pochopenie, aby bolo možné izolovať a opraviť problémy.

Reaktívne systémy predstavujú najproduktívnejšiu systémovú architektúru v kontexte viacjadrových, cloudových a mobilných architektúr:

  1. Izolácia porúch ponúka prepážky medzi komponentmi, čím predchádza kaskádovým poruchám a obmedzuje rozsah a závažnosť porúch.
  2. Hierarchie supervízorov ponúkajú viacero úrovní ochrany v kombinácii so schopnosťami samoopravy, čo eliminuje mnoho dočasných zlyhaní akýchkoľvek prevádzkových nákladov na vyšetrenie.
  3. Preskakovanie správ a transparentnosť umiestnenia vám umožňujú deaktivovať a nahradiť komponenty bez ovplyvnenia skúseností koncového používateľa. To znižuje náklady na poruchy, ich relatívnu naliehavosť a zdroje potrebné na ich diagnostiku a opravu.
  4. Replikácia znižuje riziko straty údajov a znižuje dopad zlyhania na dostupnosť získavania a ukladania informácií.
  5. Elasticita umožňuje šetriť zdroje pri kolísaní využívania, čím sa minimalizujú prevádzkové náklady pri nízkej záťaži a riziko zlyhania alebo naliehavé investície do škálovateľnosti pri zvyšovaní záťaže.

Webové aplikácie môžu výrazne ťažiť z vývojového štýlu Rx, ktorý vám umožňuje zostaviť pracovné toky žiadosť-odpoveď, ktoré zahŕňajú vetvenie do servisných volaní, asynchrónne získavanie prostriedkov a kompozíciu odpovedí a následné triedenie pre klienta. V poslednej dobe sa udalosti push-to-server a webové sokety v praxi stávajú čoraz bežnejšími a robiť to vo veľkom rozsahu si vyžaduje efektívny spôsob ukladania mnohých otvorených pripojení a tam, kde IO nie je blokované.

Existujú na to nástroje ako Streams a Futures, ktoré uľahčujú neblokovacie a asynchrónne konverzie a posúvajú ich klientom. Reaktívne programovanie s vrstvou prístupu k údajom – aktualizuje ich a dopytuje efektívny zdroj, najlepšie s použitím báz SQL dáta alebo NoSQL s asynchrónnymi ovládačmi.

Webové aplikácie tiež profitujú z vývoja reaktívneho systému pre veci, ako je distribuované ukladanie do vyrovnávacej pamäte, konzistencia údajov a viacuzlové upozornenia. Tradičné webové aplikácie zvyčajne používajú stojace uzly. Akonáhle však programátori začnú používať Server-Sent-Events (SSE) a WebSockets, tieto uzly sa stanú funkčnými, pretože minimálne udržiavajú stav pripojenia klienta a podľa toho sa im odosielajú upozornenia push. Vyžaduje si to vývoj reaktívneho systému, pretože toto je oblasť, kde je dôležité oslovovanie príjemcov prostredníctvom správ.

Podstata reaktívneho programovania Java

Nie je povinné používať Rx v reaktívnych systémoch. Pretože programovanie Rx a reaktívne systémy nie sú to isté. Hoci sa často používajú zameniteľne, nie sú presnými synonymami a odrážajú rôzne veci. Systémy predstavujú ďalšiu úroveň „reaktivity“. Táto úroveň zahŕňa špecifické dizajnové a architektonické rozhodnutia, ktoré vám umožňujú vytvárať robustné a flexibilné aplikácie.

Veľmi dobrý nápad – kombinácia metód – však prináša aplikáciám ešte viac výhod, pretože ich robí ešte viac prepojenými, umožňuje efektívnejšie využívanie zdrojov a poskytuje nižšiu latenciu. Pokiaľ ide o obrovské množstvo údajov alebo multitasking, často je potrebné asynchrónne spracovanie, aby systémy boli rýchle a pohotové.

V Jave, dedičstve starého objektovo orientovaného programovania, môže byť asynchrónia skutočne komplikovaná a sťažuje pochopenie a údržbu kódu. Rx je teda obzvlášť užitočný pre toto „čisto“ objektovo orientované prostredie, pretože uľahčuje prácu s asynchrónnymi vláknami.

Vo svojich najnovších vydaniach, počnúc Java 8, samotná Java urobila niekoľko pokusov o implementáciu natívnej reaktivity, ale tieto pokusy dnes nie sú medzi vývojármi veľmi obľúbené. Existuje však niekoľko živých a pravidelne aktualizovaných implementácií tretích strán pre reaktívne programovanie Java, ktoré môžu zachrániť situáciu, a preto ich ocenia najmä vývojári Java.

V typickej aplikácii je bežné opakovane vykonávať niektoré reaktívne programovacie operácie pomocou RxJava, takže musíte porovnať rýchlosť, využitie CPU a pamäte s rovnakými operáciami, ktoré boli implementované s korutínmi Kotlin a RxJava. Toto je počiatočný test výkonu.

Vždy, keď sa použije nový nástroj, ktorý bude široko používaný v celom kóde, je dôležité pochopiť, či to ovplyvní celkový výkon aplikácie, skôr než sa rozhodneme, či sa ho oplatí používať. Prax používania dáva krátku odpoveď: vo väčšine prípadov by používatelia mali zvážiť nahradenie RxJava s coroutines Kotlin, najmä v systéme Android.

Reaktívne programovanie pomocou RxJava sa dá stále použiť v obmedzenom počte prípadov a v týchto prípadoch je možné kombinovať RxJava aj korutíny.

Jednoduché dôvody:

  1. Poskytujú oveľa väčšiu flexibilitu ako bežný Rx.
  2. Poskytuje bohatú sadu operátorov na kolekciách, ktoré budú vyzerať rovnako ako operátori RxJava.
  3. Reaktívne programovanie Kotlin môže v prípade potreby interagovať pomocou rxjava.
  4. Sú veľmi ľahké a efektívne vzhľadom na vyššie využitie CPU na zber odpadu zo všetkých objektov vytvorených RxJava.

Reaktívne rozšírenia

Reactive Extensions (ReactiveX alebo RX) je knižnica, ktorá dodržiava princípy Rx, t. j. skladanie asynchrónnych programov a programov založených na udalostiach pomocou pozorovateľnej sekvencie. Tieto knižnice poskytujú mnoho rozhraní a metód, ktoré pomáhajú vývojárom písať čistý a jednoduchý kód.

Reaktívne rozšírenia sú dostupné v niekoľkých jazykoch. Programátori sa obzvlášť zaujímajú o RxJava a RxAndroid, pretože android je najviac zameraná oblasť.

Reaktívne programovanie pomocou RxJava je implementáciou rozšírenia Java Reactive od Netflixu. V podstate ide o knižnicu, ktorá skladá asynchrónne udalosti podľa vzoru pozorovateľa.

Je možné vytvárať asynchrónne prenosy, transformovať ich a konzumovať ich pozorovateľom v rôznych dátových tokoch. Knižnica ponúka širokú škálu úžasných operátorov, ako je mapa, spojenie a filter, ktoré možno použiť na dátový tok. Keď programátor začne používať skutočné príklady kódu, dozvie sa viac o operátoroch a konverziách.

Multithreading v aplikáciách pre Android

Android" class="if uuid-2938324" src="/misc/i/gallery/73564/2938324.jpg" />

Android reaktívne programovanie (RxAndroid) je špecifické pre platformy Android s niekoľkými pridanými triedami nad RxJava. Presnejšie, plánovače sú predstavené v RxAndroid (AndroidSchedulers.mainThread()), ktorý hrá dôležitú úlohu pri podpore konceptu multithreadingu v aplikáciách pre Android.

Odborníci okrem iného radia používať iba knižnicu RxJava. Aj vďaka veľkému množstvu plánovačov používaných pri programovaní androidov.

Nižšie je uvedený zoznam plánovačov a ich súhrn:

  1. Schedulers.io() – Používa sa na vykonávanie neintenzívnych operácií, ako sú sieťové volania, čítanie diskov/súborov, operácie s databázou a ktoré udržiava skupinu vlákien.
  2. AndroidSchedulers.mainThread() – poskytuje prístup k hlavnej téme vlákna/používateľského rozhrania. Zvyčajne v tomto vlákne prebiehajú operácie, ako je aktualizácia používateľského rozhrania, interakcia s používateľom. Odborníci odporúčajú používateľom, aby s týmto streamom nevykonávali žiadne intenzívne operácie, pretože to môže spôsobiť zlyhanie aplikácie alebo dialógové okno ANR.
  3. Schedulers.newThread() - Pomocou tohto sa vytvorí nové vlákno vždy, keď je naplánovaná úloha. Vo všeobecnosti sa odporúča nepoužívať rozvrh pre veľmi dlhé úlohy. Vlákna vytvorené pomocou newThread() sa znova nepoužijú.
  4. Schedulers.computation () - tento plán možno použiť na vykonávanie operácií náročných na CPU, na spracovanie obrovských údajov reaktívneho programovacieho centra, spracovanie bitmapy. Počet vlákien vytvorených pomocou tohto plánovača úplne závisí od počtu dostupných jadier CPU.
  5. Schedulers.single() – Tento plánovač vykoná všetky úlohy v nasledujúcom poradí, ktoré možno použiť, keď je potrebné sekvenčné vykonávanie.
  6. Schedulers.immediate() – Tento plánovač vykoná úlohu okamžite synchrónnym blokovaním hlavného vlákna.
  7. Schedulers.trampoline() - Spúšťa úlohy v režime Prvý dnu-prvý von. Všetky naplánované úlohy sa spustia jedna po druhej, čím sa obmedzí počet vlákien na pozadí na jedno.
  8. Schedulers.from () - umožňuje vám vytvoriť plánovač z vykonávateľa s obmedzením počtu vytvorených vlákien. Keď je oblasť vlákien zaneprázdnená, úlohy budú zaradené do frontu.

Teraz, keď máte dobré zázemie v RxJava a RxAndroid, môžete prejsť na niekoľko príkladov kódu, aby ste lepšie pochopili tento koncept. Ak chcete začať, musíte do projektov build.gradle pridať závislosti RxJava a RxAndroid a synchronizovať projekt.

Programovanie.

Pozorovateľ je prihlásený na odber pozorovateľného objektu, aby mohol začať prijímať údaje dvoma spôsobmi:

  1. SubscribeOn(Schedulers.io()) – povie Observable, aby spustilo úlohu na vlákne na pozadí.
  2. ObservOn(AndroidSchedulers.mainThread()) – povie pozorovateľovi, aby prijímal údaje o vlákne používateľského rozhrania systému Android.

To je všetko, takže programátor môže napísať svoj prvý reaktívny programovací program s RxJava.

Podniky a dodávatelia middlewaru začali používať Reactive a v rokoch 2016 – 2018 došlo k obrovskému nárastu záujmu spoločností o prijatie tejto paradigmy.

Rx ponúka výkon vývojárov prostredníctvom efektívnosti zdrojov na úrovni komponentov pre internú logiku a transformáciu toku údajov, zatiaľ čo reaktívne systémy ponúkajú výkon pre architektov a DevOps prostredníctvom odolnosti a pružnosti na úrovni systému. Používajú sa na vytváranie „Cloud Native“ a iných rozsiahlych distribuovaných systémov. V praxi knihy o reaktívnom Java programovanie s metódami umožňujúcimi kombinovať princípy navrhovania reaktívnych systémov.

K dnešnému dňu existuje množstvo metodík na programovanie zložitých systémov v reálnom čase. Jedna z týchto metodík sa nazýva FRP (FRP). Absorboval dizajnový vzor tzv Pozorovateľ (Pozorovateľ) na jednej strane a na druhej strane, ako asi tušíte, princípy funkcionálneho programovania. V tomto článku sa budeme zaoberať funkčným reaktívnym programovaním na príklade jeho implementácie v knižnici Sodík pre jazyk Haskell.

Teoretická časť

Programy sú tradične rozdelené do troch tried:

  • Dávka
  • Interaktívne
  • prúdové lietadlo

Rozdiely medzi týmito tromi triedami programov spočívajú v type ich interakcie s vonkajším svetom.

Vykonávanie dávkových programov nie je žiadnym spôsobom synchronizované s vonkajším svetom. Môžu byť spustené v ľubovoľnom časovom bode a dokončené, keď sa spracujú počiatočné údaje a získa sa výsledok. Na rozdiel od dávkových programov interaktívne a reaktívne programy komunikujú s vonkajším svetom nepretržite v priebehu svojej práce od okamihu spustenia až po zastavenie.

Reaktívny program, na rozdiel od interaktívneho programu, je úzko synchronizovaný s vonkajším prostredím: vyžaduje sa, aby reagoval na udalosti vo vonkajšom svete s prijateľným oneskorením v rýchlosti výskytu týchto udalostí. Zároveň je prípustné, aby interaktívny program nechal vonkajšie prostredie čakať. Napríklad, textový editor je interaktívny program: používateľ, ktorý spustil proces opravy chýb v texte, musí počkať na jeho dokončenie, aby mohol pokračovať v úpravách. Autopilot je však reaktívny program, pretože keď sa vyskytne prekážka, musí okamžite korigovať kurz, aby mohol vykonať preletový manéver, pretože reálny svet sa nedá pozastaviť ako používateľ textového editora.

Tieto dve triedy programov sa objavili o niečo neskôr, keď sa počítače začali používať na riadenie strojov, a začali sa rozvíjať používateľské rozhrania. Odvtedy sa zmenilo veľa metód implementácie, z ktorých každá bola navrhnutá tak, aby napravila nedostatky predchádzajúcich. Najprv to boli jednoduché programy riadené udalosťami: v systéme bola alokovaná určitá množina udalostí, na ktoré bolo potrebné reagovať, a boli vytvorené handlery pre tieto udalosti. Handlery zase mohli generovať udalosti, ktoré smerovali do vonkajšieho sveta. Tento model bol jednoduchý a riešil celý rad jednoduchých interaktívnych problémov.

Ale postupom času sa interaktívne a reaktívne programy stali zložitejšími a programovanie riadené udalosťami sa zmenilo na peklo. Na syntézu systémov riadených udalosťami boli potrebné pokročilejšie nástroje. V tejto metodológii boli dve hlavné chyby:

  • implicitný stav
  • Nedeterminizmus

Na vyriešenie tohto problému bola najprv vytvorená šablóna pozorovateľ (pozorovateľ), ktorý transformoval udalosti na časovo premenlivé hodnoty. Sada týchto hodnôt predstavovala explicitný stav, v ktorom sa program nachádzal. Takže spracovanie udalostí bolo nahradené bežnou prácou s dátami pre dávkové programy. Vývojár mohol zmeniť pozorovateľné hodnoty a predplatiť obslužné programy na zmenu týchto hodnôt. Bolo možné implementovať závislé hodnoty, ktoré sa menili podľa daného algoritmu, pri zmene hodnôt, od ktorých záviseli.

Hoci udalosti v tomto prístupe pominuli, ich potreba stále pretrváva. Udalosť nemusí vždy znamenať zmenu hodnoty. Napríklad udalosť v reálnom čase znamená zvýšenie počítadla času o sekundu, ale udalosť alarmu každý deň v určitom čase neznamená vôbec žiadnu hodnotu. Samozrejme, môžete túto udalosť spájať aj s určitým významom, ale toto bude umelé zariadenie. Napríklad môžeme zadať hodnotu: alarm_time == zvyšok_rozdelenia (aktuálny_čas, 24*60*60). Ale to nás nebude zaujímať, keďže táto premenná je viazaná na sekundu a v skutočnosti sa hodnota mení dvakrát. Ak chcete zistiť, že sa alarm spustil, musí predplatiteľ určiť, že hodnota sa stala pravdivou a nie naopak. Hodnota bude pravdivá presne jednu sekundu a ak zmeníme periódu tikania zo sekundy na povedzme 100 milisekúnd, potom skutočná hodnota už nebude sekunda, ale týchto 100 milisekúnd.

Vznik metodológie funkcionálneho reaktívneho programovania je akousi odpoveďou funkcionalistov na vzor pozorovateľ. Vo FRP boli vyvinuté prístupy prehodnotené: udalosti (Udalosti) nezmizli, ale pozorované hodnoty sa tiež objavili a boli pomenované vlastnosti (správanie). Udalosť v tejto metodológii je diskrétne generovaná hodnota a charakteristika je nepretržite generovaná hodnota. Oba môžu byť prepojené: charakteristiky môžu generovať udalosti a udalosti môžu pôsobiť ako zdroje charakteristík.

Problém neurčitosti štátu je oveľa komplikovanejší. Vyplýva to zo skutočnosti, že systémy riadené udalosťami sú de facto asynchrónne. To má za následok vznik prechodných stavov systému, ktoré môžu byť z nejakého dôvodu neprijateľné. Na vyriešenie tohto problému sa objavilo takzvané synchrónne programovanie.

Praktická časť

Knižnica Sodík sa objavil ako realizačný projekt FRP so spoločným rozhraním v rôznych programovacích jazykoch. Obsahuje všetky prvky metodiky: primitíva (Udalosť, Správanie) a vzorce ich používania.

Primitívi a interakcia s vonkajším svetom

Dve hlavné primitívy, s ktorými musíme pracovať, sú:

  • udalosť a- udalosť s hodnotou typu a
  • Správanie a- charakteristika (alebo meniaca sa hodnota) druhu a

Pomocou funkcií môžeme vytvárať nové udalosti a hodnoty newEvent a nové správanie:

newEvent::Reactive(Udalosť a, a -> Reactive()) newBehavior::a -> Reactive(Správanie a, a -> Reactive())

Ako vidíte, obe tieto funkcie je možné volať len v monade. Reaktívny v dôsledku toho sa vráti samotné primitívum, ako aj funkcia, ktorá sa musí zavolať na aktiváciu udalosti alebo zmenu hodnoty. Funkcia vytvárania funkcie berie počiatočnú hodnotu ako prvý argument.

Na prepojenie reálneho sveta s reaktívnym programom existuje funkcia synchronizácia, a na prepojenie programu s vonkajším svetom je tu funkcia počúvaj:

Synchronizácia:: Reaktívne a -> IO a počúvanie:: Udalosť a -> (a -> IO ()) -> Reaktívne (IO ())

Prvá funkcia, ako už názov napovedá, vykonáva nejaký reaktívny kód synchrónne, umožňuje vám dostať sa do kontextu Reaktívny vytrhnuté z kontextu IO a druhý sa používa na pridanie obsluhy udalostí, ktoré sa vyskytujú v kontexte Reaktívny, vykonávajúci v kontexte IO. Funkcia počúvaj vráti funkciu nepočúvať Výzva k odpojeniu psovoda.

Implementuje sa teda akýsi transakčný mechanizmus. Keď robíme niečo vo vnútri reaktívnej monády, kód sa vykoná v rámci rovnakej transakcie v čase, keď je funkcia zavolaná. synchronizácia. Stav sa určuje iba mimo kontextu transakcie.

Toto je základ reaktívneho funkcionálneho programovania, ktorý je dostatočný na písanie programov. Môže to byť trochu mätúce, že môžete počúvať iba udalosti. Presne tak by to malo byť, ako uvidíme neskôr, medzi udalosťami a vlastnosťami sú úzke vzťahy.

Operácie na základných primitívoch

Pre pohodlie boli do metodológie pridané ďalšie funkcie, ktoré transformujú udalosti a charakteristiky. Uvažujme o niektorých z nich:

Udalosť, ktorá sa nikdy nestane -- (možno použiť ako stub) never:: Udalosť a -- Zlúčiť dve udalosti rovnakého typu do jednej -- (užitočné na definovanie jedného obslužného programu pre triedu udalosti) merge:: Udalosť a -> Udalosť a -> Udalosť a -- Vytiahnite hodnoty z udalostí Možno -- (oddeľte zrno od pliev) filterJust:: Udalosť (Možno a) -> Udalosť a -- Premeňte udalosť na charakteristiku s iniciálom hodnota -- (zmena hodnoty, keď nastanú udalosti) hold :: a -> Udalosť a -> Reaktívna (Správanie a) -- Premení charakteristiku na udalosť -- (generuje udalosti pri zmene hodnoty) aktualizácie:: Správanie a -> Udalosť a -- Zmení charakteristiku na udalosť -- (vyvolá aj udalosť pre počiatočnú hodnotu) value:: Správanie a -> Udalosť a -- Keď nastane udalosť, nadobudne hodnotu charakteristiky, -- použije funkciu a vygeneruje snímku udalosti:: (a -> b -> c) -> Udalosť a -> Správanie b - > Udalosť c -- Získa aktuálnu hodnotu vzorky:: Správanie a -> Reaktívne a -- Zredukuje opakované udalosti do jedného spojenia:: (a -> a -> a) -> Udalosť a -> Udalosť a -- Potlačí všetky udalosti okrem prvej raz:: Udalosť a -> Udalosť a -- Rozdelí udalosť z rozdelenia zoznamu viacerých udalostí:: Udalosť [a] -> Udalosť a

Sférické príklady vo vákuu

Skúsme niečo napísať:

Import FRP.Sodium main = do synchronizácie $ do -- vytvorenie udalosti (e1, triggerE1)<- newEvent -- создаём характеристику с начальным значением 0 (v1, changeV1) <- newBehavior 0 -- определяем обработчик для события listen e1 $ \_ ->putStrLn $ "e1 triggered" -- definujte handler na zmenu hodnoty charakteristiky počúvania (hodnota v1) $ \v -> putStrLn $ "v1 value: " ++ show v -- Generovanie udalosti bez hodnoty triggerE1 () -- Zmeňte hodnotu zmeny charakteristiky V1 13

Nainštalujte balík Sodík používaním Cabal a spustite príklad v tlmočníku:

# ak chceme pracovať v samostatnom sandboxe # vytvorte ho > cabal sandbox init # install > cabal install sodium > cabal repl GHCi, verzia 7.6.3: http://www.haskell.org/ghc/ :? o pomoc Načítavam balík ghc-prim ... linkovanie ... hotovo. Načítavam balík integer-gmp ... linkovanie ... hotovo. Načítava sa základňa balíkov ... prepojenie ... hotovo. # load example Prelude> :l Example.hs Kompilácia Main (Example.hs, interpretované) Ok, moduly načítané: Main. # spustite príklad *Main>

Teraz poďme experimentovať. Zakomentujme riadok, kde meníme našu hodnotu (changeV1 13) a reštartujeme príklad:

*Main> :l Príklad.hs Kompilácia Main (Príklad.hs, interpretovaný) Ok, moduly načítané: Main. *Hlavná> hlavná hodnota v1 spustená e1: 0

Ako vidíte, teraz je zobrazená počiatočná hodnota, je to kvôli funkcii hodnotu generuje prvú udalosť s počiatočnou hodnotou charakteristiky. Nahradíme funkciu hodnotu na aktualizácie a uvidíte, čo sa stane:

*Main> :l Príklad.hs Kompilácia Main (Príklad.hs, interpretovaný) Ok, moduly načítané: Main. *Hlavný> hlavný e1 spustený

Teraz sa počiatočná hodnota nezobrazuje, ale ak odkomentujeme riadok, v ktorom sme hodnotu menili, zmenená hodnota sa bude aj tak zobrazovať. Vráťme všetko tak, ako to bolo a vygenerujeme udalosť e1 dvakrát:

*Main> :l Príklad.hs Kompilácia Main (Príklad.hs, interpretovaný) Ok, moduly načítané: Main. *Hlavné> hlavné spustené e1 Hodnota spustenia e1 v1: 13

Ako vidíte, akcia vystrelila aj dvakrát. Skúsme sa tomu vyhnúť, prečo vo funkcii počúvaj nahradiť argument e1 na (raz e1), čím sa vytvorí nová udalosť, ktorá sa spustí raz:

*Main> :l Príklad.hs Kompilácia Main (Príklad.hs, interpretovaný) Ok, moduly načítané: Main. *Hlavná> hlavná hodnota v1 spustená e1: 13

Keď udalosť nemá argument, je pre nás dôležitý samotný fakt prítomnosti alebo neprítomnosti udalosti, teda funkcia raz pre kombinovanie podujatí je tou správnou voľbou. Ak je však prítomný argument, nie je to vždy vhodné. Prepíšme príklad takto:

<- newEvent (v1, changeV1) <- newBehavior 0 listen e1 $ \v ->putStrLn $ "e1 spustené s: " ++ show v listen (hodnota v1) $ \v -> putStrLn $ "v1 value: " ++ show v triggerE1 "a" triggerE1 "b" triggerE1 "c" changeV1 13

Podľa očakávania dostaneme všetky udalosti s hodnotami v rovnakom poradí, v akom boli vygenerované:

*Main> :l Príklad.hs Kompilácia Main (Príklad.hs, interpretovaný) Ok, moduly načítané: Main. *Hlavné> hlavné e1 spustené s: "a" e1 spustené s: "b" e1 spustené s: "c" hodnota v1: 13

Ak použijeme funkciu raz s e1, potom dostaneme len prvú udalosť, tak skúsme použiť funkciu splynúť, za ktorý nahrádzame argument e1 v počúvaj argument (splynúť (\_ a -> a) e1):

*Main> :l Príklad.hs Kompilácia Main (Príklad.hs, interpretovaný) Ok, moduly načítané: Main. *Main> main e1 spustené s: "c" v1 hodnota: 13

A skutočne, máme len poslednú udalosť.

Viac príkladov

Pozrime sa na zložitejšie príklady:

Import FRP.Sodium main = do sync $do(e1,triggerE1)<- newEvent -- создаём характеристику, изменяемую событием e1 v1 <- hold 0 e1 listen e1 $ \v ->putStrLn $ "e1 spustené s: " ++ zobraziť v počúvať (hodnota v1) $ \v -> putStrLn $ "hodnota v1 je: " ++ show v -- vydávať udalosti triggerE1 1 triggerE1 2 triggerE1 3

Tu je výstup:

*Main> :l Príklad.hs Kompilácia Main (Príklad.hs, interpretovaný) Ok, moduly načítané: Main. *Hlavný> hlavný e1 spustený s: 1 e1 spustený s: 2 e1 spustený s: 3 hodnota v1 je: 3

Charakteristická hodnota sa zobrazí iba raz, aj keď sa vygenerovalo niekoľko udalostí. Toto je zvláštnosť synchrónneho programovania: charakteristiky sú synchronizované s volaním synchronizácia. Aby sme to demonštrovali, trochu upravme náš príklad:

<- sync $ do (e1, triggerE1) <- newEvent v1 <- hold 0 e1 listen e1 $ \v ->putStrLn $ "e1 spustené s: " ++ show v listen (hodnota v1) $ \v -> putStrLn $ "hodnota v1 je: " ++ show v return triggerE1 sync $ triggerE1 1 synchronizácia $ triggerE1 2 synchronizácia $ triggerE1 3

Práve sme presunuli spúšťač udalosti do vonkajšieho sveta a nazývame ho v rôznych fázach synchronizácie:

*Main> :l Príklad.hs Kompilácia Main (Príklad.hs, interpretovaný) Ok, moduly načítané: Main. *Hlavná> hlavná hodnota v1 je: 0 e1 spustená s: 1 hodnota v1 je: 1 e1 spustená s: 2 hodnota v1 je: 2 e1 spustená s: 3 hodnota v1 je: 3

Teraz má každá udalosť novú hodnotu.

Ďalšie operácie na primitívoch

Zvážte nasledujúcu skupinu užitočných funkcií:

Zlúči udalosti pomocou mergeWith:: (a -> a -> a) -> Udalosť a -> Udalosť a -> Udalosť a -- Filtruje udalosti a ponechá len tie -- pre ktoré funkcia vráti true filterE:: (a -> Bool ) -> Udalosť a -> Udalosť a -- Umožňuje "vypnutie" udalostí -- keď je charakteristika False gate:: Event a -> Behavior Bool -> Event a -- Organizuje konvertor udalostí -- s interným stavom collectE: : (a -> s -> (b, s)) -> s -> Udalosť a -> Reaktívna (Udalosť b) -- Organizuje konvertor funkcií -- s vnútorným zhromažďovaním stavu:: (a -> s -> ( b , s)) -> s -> Správanie a -> Reaktívne (Správanie b) -- Vytvorí charakteristiku ako výsledok akumulácie udalostí kumul: a -> Udalosť (a -> a) -> Reaktívna (Správanie a )

To samozrejme nie sú všetky funkcie, ktoré knižnica poskytuje. Existujú aj oveľa exotickejšie veci, ktoré sú nad rámec tohto článku.

Príklady

Vyskúšajme si tieto funkcie v praxi. Začnime tým posledným, zorganizujte si niečo ako kalkulačku. Majme nejakú hodnotu, na ktorú môžeme použiť aritmetické funkcie a získať výsledok:

Import FRP.Sodium main = do triggerE1<- sync $ do (e1, triggerE1) <- newEvent -- пусть начальное значение будет равно 1 v1 <- accum (1:: Int) e1 listen (value v1) $ \v ->putStrLn $ "hodnota v1 je: " ++ show v return triggerE1 -- ​​​​pridajte 1 synchronizáciu $ triggerE1 (+ 1) -- vynásobte 2 synchronizácia $ triggerE1 (* 2) -- odčítajte 3 synchronizácia $ triggerE1 (+ (- 3) ) -- pridajte 5 synchronizácií $ triggerE1 (+ 5) -- zvýšte na 3 synchronizáciu $ triggerE1 (^ 3)

Poďme bežať:

*Main> :l Príklad.hs Kompilácia Main (Príklad.hs, interpretovaný) Ok, moduly načítané: Main. *Hlavná> hlavná hodnota v1 je: 1 hodnota v1 je: 2 hodnota v1 je: 4 hodnota v1 je: 1 hodnota v1 je: 6 hodnota v1 je: 216

Môže sa zdať, že súbor funkcií je dosť obmedzený, ale v skutočnosti to tak nie je. Koniec koncov, máme do činenia s Haskellom, aplikačné funktory a monády nezmizli. S charakteristikami a udalosťami môžeme vykonávať akékoľvek operácie, ktoré sme zvyknutí vykonávať na čistých hodnotách. Výsledkom je získanie nových charakteristík a udalostí. Pri charakteristikách sa implementuje trieda funktora a aplikačný funktor, pri udalostiach z pochopiteľných dôvodov iba funktor.

Napríklad:

<$>), (<*>)) import FRP.Sodium main = do(setA, setB)<- sync $ do (a, setA) <- newBehavior 0 (b, setB) <- newBehavior 0 -- Новая характеристика a + b let a_add_b = (+) <$>a<*>b -- Nová funkcia a * b nech a_mul_b = (*)<$>a<*>b počúvať (hodnota a) $ \v -> putStrLn $ "a = " ++ zobraziť v počúvať (hodnota b) $ \v -> putStrLn $ "b = " ++ zobraziť v počúvať (hodnota a_add_b) $ \v - > putStrLn $ "a + b = " ++ show v listen (hodnota a_mul_b) $ \v -> putStrLn $ "a * b = " ++ show v return (setA, setB) sync $ do setA 2 setB 3 sync $ setA 3 synchronizovať $setB 7

Tu je to, čo bude výstupom v tlmočníku:

λ> hlavná 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

Teraz sa pozrime, ako niečo také funguje s udalosťami:

ImportControl.Applicative((<$>)) import FRP.Sodium main = do sigA<- sync $ do (a, sigA) <- newEvent let a_mul_2 = (* 2) <$>a let a_pow_2 = (^2)<$>a počúvať $ \v -> putStrLn $ "a = " ++ show v počúvať a_mul_2 $ \v -> putStrLn $ "a * 2 = " ++ show v počúvať a_pow_2 $ \v -> putStrLn $ "a ^ 2 = "++ show v return sigA synchronizácia $do sigA 2 synchronizácia $sigA 3 synchronizácia $sigA 7

Tu je to, čo bude výstup:

λ> hlavné a = 2 a * 2 = 4 a ^ 2 = 4 a = 3 a * 2 = 6 a ^ 2 = 9 a = 7 a * 2 = 14 a ^ 2 = 49

Dokumentácia obsahuje zoznam inštancií tried, ktoré sú implementované pre Správanie a udalosť, ale nič vám nebráni v implementácii inštancií chýbajúcich tried.

Nevýhoda reaktivity

Funkčné reaktívne programovanie určite zjednodušuje vývoj komplexných systémov v reálnom čase, ale pri použití tohto prístupu je potrebné zvážiť veľa aspektov. Preto tu zvážime problémy, ktoré sa najčastejšie vyskytujú.

Nesimultánnosť

Synchrónne programovanie znamená určitý transakčný mechanizmus, ktorý zaisťuje konzistenciu po sebe nasledujúcich stavov systému a následne absenciu prechodných neočakávaných stavov. AT Sodík hovory sú zodpovedné za transakcie synchronizácia. Stav vnútri transakcie síce nie je definovaný, ale nemožno uvažovať, že všetko v nej prebieha súčasne. Hodnoty sa menia v určitom poradí, čo ovplyvňuje výsledok. Napríklad zdieľanie udalostí a charakteristík môže mať neočakávané účinky. Zvážte príklad:

ImportControl.Applicative((<$>)) import FRP.Sodium main = do setVal<- sync $ do (val, setVal) <- newBehavior 0 -- создаём булеву характеристику val >2 nech gt2 = (> 2)<$>val -- vytvorte udalosť s hodnotami, ktoré sú > 2 nech evt = brána (hodnota val) gt2 listen (value val) $ \v -> putStrLn $ "val = " ++ show v listen (hodnota gt2) $ \ v -> putStrLn $ "val > 2?" ++ show v listen evt $ \v -> putStrLn $ "val > 2: " ++ show v return setVal sync $ setVal 1 synchronizácia $ setVal 2 synchronizácia $ setVal 3 synchronizácia $ setVal 4 synchronizovať $setVal 0

Môžete očakávať takýto výstup:

Val = 0 val > 2? Falošná hodnota = 1 hodnota > 2? Falošná hodnota = 2 hodnota > 2 ? Falošná hodnota = 3 hodnota > 2 ? Skutočná hodnota > 2: 3 hodnota = 4 hodnota > 2 ? Skutočná hodnota > 2: 4 hodnota = 0 hodnota > 2? Nepravdivé

Avšak v skutočnosti línia hodnota > 2:3 bude chýbať a na konci bude riadok hodnota > 2:0. Dôvodom je udalosť zmeny hodnoty (hodnota) sa generuje pred výpočtom závislej charakteristiky gt2, a teda udalosť evt nenastane pre nastavenú hodnotu 3. Na konci, keď opäť nastavíme 0, výpočet charakteristiky gt2 je neskoro.

Vo všeobecnosti sú efekty rovnaké ako v analógovej a digitálnej elektronike: signálne preteky, na boj s ktorými používajú rôzne techniky. Najmä synchronizácia. Toto urobíme, aby tento kód fungoval správne:

ImportControl.Applicative((<$>)) 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 nech gt2 = (> 2)<$>val nech evt = gate (value" val) gt2 listen (value" val) $ \v -> putStrLn $ "val = " ++ show v listen (value" gt2) $ \v -> putStrLn $ "val > 2 ? " ++ show v listen evt $ \v -> putStrLn $ "val > 2: " ++ show v return (sigClk, setVal) -- Zaviedla sa nová synchronizačná funkcia -- ktorá volá synchronizačný signál -- na konci každá transakcia - - A nahradené ňou všetky hovory synchronizovať nech synchronizovať" a = synchronizovať $ a >> sigClk () synchronizovať" $ setVal 1 synchronizovať" $ setVal 2 synchronizovať" $ setVal 3 synchronizovať" $ setVal 4 synchronizovať" $ setVal 0

Teraz je náš výstup podľa očakávania:

λ> hlavná hodnota = 0 val > 2 ? Falošná hodnota = 1 hodnota > 2? Falošná hodnota = 2 hodnota > 2 ? Falošná hodnota = 3 hodnota > 2 ? Skutočná hodnota > 2: 3 hodnota = 4 hodnota > 2 ? Skutočná hodnota > 2: 4 hodnota = 0 hodnota > 2? Nepravdivé

lenivosť

Problémy iného druhu súvisia s lenivosťou výpočtov v Haskell. To vedie k tomu, že pri testovaní kódu v interprete môže jednoducho chýbať nejaký výstup na konci. Čo možno v tomto prípade navrhnúť, je urobiť na konci zbytočný krok synchronizácie, napr sync$return().

Záver

Nateraz to podľa mňa stačí. V súčasnosti jeden z autorov knižnice Sodík píše knihu o FRP. Dúfajme, že to nejakým spôsobom vyplní medzery v tejto oblasti programovania a poslúži na popularizáciu progresívnych prístupov v našich skostnatených mysliach.

Postupom času sa programovacie jazyky neustále menia a vyvíjajú v dôsledku objavovania sa nových technológií, moderných požiadaviek alebo jednoduchej túžby obnoviť štýl písania kódu. Reaktívne programovanie je možné implementovať pomocou rôznych rámcov, ako je napríklad Reactive Cocoa. Mení rozsah imperatívneho štýlu jazyka Objective-C a tento prístup k programovaniu má veľa čo ponúknuť štandardnej paradigme. To samozrejme priťahuje pozornosť vývojárov iOS.

ReactiveCocoa prináša do Objective-C deklaratívny štýl. Čo tým myslíme? Tradičný imperatívny štýl používaný jazykmi ako C, C++, Objective-C a Java atď. možno opísať takto: Píšete smernice pre počítačový program to sa musí urobiť určitým spôsobom. Inými slovami, hovoríte „ako to urobiť“. Zatiaľ čo deklaratívne programovanie vám umožňuje opísať tok riadenia ako postupnosť akcií, „čo robiť“, bez definovania „ako to urobiť“.

Imperatívne verzus funkčné programovanie

Imperatívny prístup k programovaniu zahŕňa podrobný popis každého kroku, ktorý musí počítač vykonať, aby dokončil úlohy. V skutočnosti sa imperatívny štýl používa v natívnych programovacích jazykoch (alebo sa používa pri písaní strojového kódu). Toto je mimochodom charakteristická vlastnosť väčšiny programovacích jazykov.

Naopak, funkčný prístup rieši problémy so súborom funkcií, ktoré je potrebné vykonať. Definujete vstupné parametre pre každú funkciu a to, čo každá funkcia vracia. Tieto dva prístupy k programovaniu sú veľmi odlišné.

Tu sú hlavné jazykové rozdiely:

1. Stavové zmeny

Pre čisto funkčné programovanie nedochádza k žiadnej zmene stavu, pretože neexistujú žiadne vedľajšie účinky. Vedľajší účinok znamená okrem návratovej hodnoty aj zmeny stavu v dôsledku nejakej vonkajšej interakcie. SR (referenčná transparentnosť) podvýrazu sa často definuje ako „neprítomnosť vedľajších účinkov“ a primárne sa týka čistých funkcií. SP nepovoľuje, aby vykonávanie funkcie malo externý prístup k volatilnému stavu funkcie, pretože každý podvýraz je podľa definície volaním funkcie.

Aby bolo všetko jasné, čisté funkcie majú nasledujúce atribúty:

  • jediným pozoruhodným výstupom je návratová hodnota
  • jedinou závislosťou vstupných parametrov sú argumenty
  • argumenty sú plne kvalifikované pred vygenerovaním akéhokoľvek výstupu

Napriek tomu, že funkčný prístup minimalizuje vedľajšie účinky, nedá sa im úplne vyhnúť, pretože sú neoddeliteľnou súčasťou každého vývoja.

Na druhej strane funkcie v imperatívnom programovaní nemajú referenčnú transparentnosť a to môže byť jediný rozdiel medzi deklaratívnym a imperatívnym prístupom. Vedľajšie účinky sa široko používajú na implementáciu stavu a I/O. Príkazy v zdrojovom jazyku môžu zmeniť stav, čo má za následok rôzne hodnoty pre rovnaký jazykový výraz.

Čo tak ReactiveCocoa? Toto je funkčný rámec pre Objective-C, čo je koncepčne imperatívny jazyk, ktorý nezahŕňa explicitne čisté funkcie. Pri pokuse vyhnúť sa zmene stavu nie sú vedľajšie účinky obmedzené.

2. Predmety prvej triedy

Vo funkčnom programovaní existujú objekty a funkcie, ktoré sú prvotriednymi objektmi. Čo to znamená? To znamená, že funkcie môžu byť odovzdané ako parameter, priradené k premennej alebo vrátené z funkcie. Prečo je to pohodlné? To uľahčuje správu vykonávacích blokov, vytváranie a kombinovanie funkcií rôzne cesty bez komplikácií, ako sú ukazovatele funkcií (char *(*(**foo)()); - bavte sa!).

Jazyky, ktoré používajú imperatívny prístup, majú svoje vlastné zvláštnosti, pokiaľ ide o prvotriedne výrazy. Čo tak Objective-C? Má bloky ako implementácie uzáveru. Funkcie vyššieho rádu (HFO) možno modelovať tak, že sa ako parametre vezmú bloky. V tomto prípade je blok uzáverom a zo špecifickej sady blokov možno vytvoriť funkciu vyššieho rádu.

Proces manipulácie FVP vo funkčných jazykoch je však viac rýchly spôsob a vyžaduje menej riadkov kódu.

3. Hlavné riadenie prietoku

Cykly imperatívneho štýlu sú vo funkčnom programovaní reprezentované ako volania funkcie rekurzie. Iterácia vo funkčných jazykoch sa zvyčajne vykonáva pomocou rekurzie. prečo? Asi kvôli zložitosti. Pre vývojárov Objective-C sa slučky zdajú byť programátorsky prívetivejšie. Rekurzie môžu spôsobiť ťažkosti, ako je nadmerná spotreba pamäte RAM.

Ale! Funkciu môžeme napísať bez použitia slučiek alebo rekurzií. Pre každú z nekonečne možných špecializovaných akcií, ktoré možno použiť na každý prvok kolekcie, funkčné programovanie používa opakovane použiteľné iteračné funkcie, ako napríklad „ mapa”, “zložiť”, “". Tieto funkcie sú užitočné pri reorganizácii zdrojový kód. Znižujú duplicitu a nevyžadujú písanie samostatnej funkcie. (čítajte ďalej, máme o tom viac informácií!)

4. Poradie vykonania

Deklaratívne výrazy zobrazujú iba logické vzťahy argumentov funkcie podvýrazu a pretrvávajúce stavové vzťahy. Takže pri absencii vedľajších účinkov sa prechod stavu každého volania funkcie vyskytuje nezávisle od ostatných.

Funkčné poradie vykonávania imperatívnych výrazov závisí od volatilného stavu. Preto je poradie vykonávania dôležité a je implicitne určené organizáciou zdrojového kódu. V tejto otázke môžeme poukázať na rozdielnosť stratégií hodnotenia oboch prístupov.

Lenivé hodnotenia alebo hodnotenia podľa potreby sú stratégie vo funkčných programovacích jazykoch. V takom prípade sa vyhodnotenie výrazu odloží, kým nebude potrebná jeho hodnota, čím sa vyhneme opakovaným hodnoteniam. Inými slovami, výrazy sa vyhodnotia len vtedy, keď sa vyhodnotí závislý výraz. Poradie operácií sa stane nedefinovaným.

Na rozdiel od toho rázne hodnotenie v imperatívnom jazyku znamená, že výraz bude vyhodnotený hneď, ako bude naviazaný na premennú. To znamená diktovať poradie vykonávania. Je teda jednoduchšie určiť, kedy budú podvýrazy (vrátane funkcií) vyhodnocované, pretože podvýrazy môžu mať vedľajšie účinky, ktoré ovplyvňujú vyhodnotenie iných výrazov.

5. Číslo kódu

To je dôležité, funkčný prístup vyžaduje písanie menšieho množstva kódu ako imperatívny. To znamená menej pádov, menej kódu na testovanie a produktívnejší vývojový cyklus. Keďže systém sa neustále vyvíja a rastie, je to dôležité.

Hlavné zložky ReactiveCocoa

Funkčné programovanie sa zaoberá pojmami známymi ako budúcnosť (reprezentácia premennej len na čítanie) a prísľub (reprezentácia premennej budúcnosti len na čítanie). čo je na nich dobré? V imperatívnom programovaní musíte pracovať s hodnotami, ktoré už existujú, čo vedie k potrebe synchronizácie asynchrónneho kódu a ďalším ťažkostiam. Koncepty futures a prísľuby vám však umožňujú pracovať s hodnotami, ktoré ešte neboli vytvorené (asynchrónny kód je napísaný synchrónnym spôsobom).


Signál

Budúcnosť a prísľub sú v reaktívnom programovaní reprezentované ako signály. - hlavná zložka ReactiveCocoa. Poskytuje príležitosť predstaviť tok udalostí, ktoré budú prezentované v budúcnosti. Prihlásite sa na odber signálu a získate prístup k udalostiam, ktoré sa stanú v priebehu času. Signál je vlákno poháňané tlačidlom a môže to byť stlačenie tlačidla, asynchrónne sieťové operácie, časovače, iné udalosti používateľského rozhrania alebo čokoľvek iné, čo sa časom mení. Dokážu prepojiť výsledky asynchrónnych operácií a efektívne kombinovať viaceré zdroje udalostí.

Následná sekvencia

Ďalším typom toku je sekvencia. Na rozdiel od signálu je sekvencia tok riadený ťahom. Ide o druh kolekcie, ktorá má podobný účel ako NSArray. RACSequence umožňuje vykonávať určité operácie, keď ich potrebujete, a nie sekvenčne, ako pri kolekcii NSArray. Hodnoty v sekvencii sa vyhodnotia iba vtedy, keď sú predvolene určené. Použitie iba časti sekvencie potenciálne zlepšuje výkon. RACS sekvencia umožňuje, aby sa s kolekciami Cocoa zaobchádzalo všeobecným a deklaratívnym spôsobom. RAC pridáva metódu -rac_sequence do väčšiny tried kolekcie kakaa, aby sa dali použiť ako RACS sekvencie.

Tím

V reakcii na určité akcie, príkaz RACC a prihlásiť sa na odber signálu. Týka sa to predovšetkým interakcií s používateľským rozhraním. Kategórie UIKit poskytuje ReactiveCocoa pre väčšinu ovládacích prvkov UIKit, dajte nám správny spôsob spracovania udalostí používateľského rozhrania. Predstavme si, že musíme zaregistrovať používateľa v reakcii na kliknutie na tlačidlo. V tomto prípade môže príkaz predstavovať sieťovú požiadavku. Keď sa proces spustí, tlačidlo zmení svoj stav na „neaktívne“ a naopak. Čo ešte? Môžeme poslať aktívny signál v príkaze (Reachable - dobrý príklad). Preto, ak je server nedostupný (čo je náš "signál zapnutý"), príkaz bude nedostupný a každý príkaz priradeného ovládacieho prvku bude odrážať tento stav.

Príklady základných operácií

Tu je niekoľko diagramov, ako fungujú základné operácie s RACSignals:

Zlúčiť

+ (RACSignal *)zlúčiť:(id )signály;


Toky výsledkov majú oba toky udalostí spojené dohromady. Takže "+zlúčiť" je užitočné, keď sa nestaráte o konkrétny zdroj udalostí, ale chceli by ste ich zvládnuť na jednom mieste. V našom príklade stateLabel.text používa 3 iný signál: prevedenie, dokončenie, chyby.

RACCommand *loginCommand = [ initWithSignalBlock:^RACSignal *(vstup ID) ( // prihlásme sa!)]; RACSignal *executionSignal = ; RACSignal *completionSignal = filter:^BOOL(RACEvent *udalosť) ( return event.eventType == RACEventTypeCompleted; )] map:^id(hodnota id) (​return @"Hotovo"; )]; )]; RACSignal *errorSignal = ; RAC(self.stateLabel, text) = ];

+ (RACSignal *)combineNajnovšie:(id )signály redukovať:(id (^)())reduceBlock;

Výsledkom je, že stream obsahuje najnovšie hodnoty prenášaných streamov. Ak na jednom z prúdov nezáleží, výsledok bude prázdny.


Kedy ho môžeme použiť? Vezmime si náš predchádzajúci príklad a pridáme k nemu viac logiky. Je užitočné povoliť tlačidlo prihlásenia iba vtedy, keď používateľ zadal správny e-mail a heslo, však? Toto pravidlo môžeme vyhlásiť takto:

ACSignal *enabledSignal = znizit:^id (NSString *e-mail, NSString *heslo) ( return @( && password.length > 3); )];

*Teraz trochu zmeňme náš prihlasovací príkaz a pripojte ho k skutočnému prihlasovaciemu tlačidlu

RACCommand *loginCommand = [ initWithEnabled:enabledSignal signalBlock:^RACSignal *(id input) ( // poďme sa prihlásiť! )]; ;

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

Môžete vytvoriť nové vlákna pre každú hodnotu v pôvodnom vlákne pomocou túto funkciu(f). Výsledný tok vracia nové signály na základe hodnôt vygenerovaných v pôvodných tokoch. Takže môže byť asynchrónny.


Predstavme si, že vaša žiadosť o autorizáciu do systému pozostáva z dvoch samostatných častí: získajte údaje z Facebooku (ID atď.) a odovzdajte ich Backendu. Jednou z požiadaviek je možnosť zrušiť prihlásenie. Preto musí klientsky kód zvládnuť stav procesu prihlásenia, aby ho bolo možné zrušiť. To dáva veľa štandardného kódu, najmä ak sa môžete prihlásiť z viacerých miest.

Ako vám ReactiveCocoa pomáha? Toto môže byť implementácia prihlásenia:

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

legenda:

+ - signál, ktorý vedie k otvoreniu FBSession. V prípade potreby to môže viesť k vstupu do Facebook.

- - signál, ktorý získava údaje o profile prostredníctvom relácie, ktorá sa prenáša ako seba.

Výhodou tohto prístupu je, že pre používateľa je celý tok nejasný, reprezentovaný jediným signálom, ktorý je možné zrušiť v ktorejkoľvek fáze, či už Facebook prihlásenie alebo zavolajte na Backend.

Filter

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

Výsledkom je, že prúd obsahuje hodnoty prúdu „a“, filtrované podľa danej funkcie.


RACSequence *sekvencia = @[@"Niektoré", @"príklad", @"z", @"sekvencia"].rac_sequence; RACSequence *filtredSequence = ; )];

Mapa

- (RACSignal *)map:(id (^)(hodnota id))blok;

Na rozdiel od FlattenMap, Map beží synchrónne. Hodnota vlastnosti "a" prechádza cez danú funkciu f (x + 1) a vracia mapovanú pôvodnú hodnotu.


Povedzme, že chcete zadať názov modelu na obrazovku a aplikovať naň niektoré atribúty. Mapa vstupuje do hry, keď je „Použitie niektorých atribútov“ opísané ako samostatná funkcia:

RAC(self.titleLabel, text) = initWithString:modelTitle atribúty:attributes]; )];

Ako to funguje: zjednocuje self.titleLabel.text so zmenami model.názov aplikovaním vlastných atribútov.

PSČ

+ (RACSignal *)zip:(id )streamy znížiť:(id (^)())reduceBlock;

Udalosti toku výsledkov sa generujú, keď každý z tokov vygeneruje rovnaký počet udalostí. Obsahuje hodnoty, jednu z každého z 3 kombinovaných tokov.


Pre niektoré praktické príklady možno zips opísať ako dispatch_group_notify Napríklad máte 3 samostatné signály a chcete spojiť ich odpovede v jednom bode:

NSArray *signály = @; návrat;

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

S časovačom nastaveným na určitý čas sa prvá hodnota toku "a" odovzdá do výsledného toku až po uplynutí času časovača. V prípade, že sa v danom časovom intervale vytvorí nová hodnota, ponechá si prvú hodnotu, čím zabráni jej odovzdaniu do výsledného toku. Namiesto toho sa v streame výsledkov zobrazí druhá hodnota.


Prekvapivý prípad: keď používateľ zmení pole vyhľadávania, musíme vyhľadať dopyt. Štandardný problém, však? Nie je to však veľmi efektívne na vytváranie a odosielanie sieťovej požiadavky pri každej zmene textu, pretože textField dokáže vygenerovať veľa takýchto udalostí za sekundu a vy tak skončíte s neefektívnym využívaním siete.
Riešením je pridať oneskorenie, po ktorom skutočne vykonáme sieťovú požiadavku. To sa zvyčajne dosiahne pridaním NTimeru. S ReactiveCocoa je to oveľa jednoduchšie!

[[ throttle:0.3] subscribeNext:^(NSString *text) ( // vykonanie sieťovej požiadavky )];

*Dôležitou poznámkou je, že všetky "predchádzajúce" textové polia sú upravené pred odstránením "posledných".

Meškania

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

Hodnota prijatá v toku „a“ sa oneskorí a po určitom časovom intervale sa prenesie do výsledného toku.


Ako -, oneskorenie iba oneskorí odoslanie "ďalších" a "dokončených" udalostí.

[ subscribeNext:^(NSString *text) ( )];

Čo milujeme na Reactive Cocoa

  • Predstavuje Cocoa Bindings pre iOS
  • Schopnosť vytvárať operácie s budúcimi údajmi. Tu je nejaká teória o budúcnosti a prísľuboch od Scaly.
  • Schopnosť reprezentovať asynchrónne operácie synchrónnym spôsobom. Reaktívne kakao zjednodušuje asynchrónne softvér, ako je napríklad sieťový kód.
  • Pohodlný rozklad. Kód, ktorý sa zaoberá používateľskými udalosťami a zmenami stavu aplikácie, môže byť veľmi zložitý a mätúci. Reactive Cocoa robí modely závislých operácií obzvlášť jednoduchými. Keď operácie reprezentujeme ako zreťazené vlákna (napr. spracovanie sieťových požiadaviek, užívateľské udalosti atď.), môžeme dosiahnuť vysokú modularitu a voľné prepojenie, čo vedie k väčšiemu opätovnému použitiu kódu.
  • Správanie a vzťahy medzi vlastnosťami sú definované ako deklaratívne.
  • Rieši problémy so synchronizáciou – ak skombinujete viacero signálov, potom existuje jedno jediné miesto na spracovanie všetkých výsledkov (či už ďalšej hodnoty, dokončenia alebo chybového signálu)

Pomocou rámca RAC môžete vytvárať a transformovať sekvencie hodnôt na lepšie, viac vysoký stupeň, spôsob. RAC uľahčuje riadenie všetkého, čo čaká na dokončenie asynchrónnej operácie: odozva siete, zmena závislej hodnoty a následná reakcia. Na prvý pohľad je ťažké sa s tým vyrovnať, ale ReactiveCocoa je nákazlivé!

Skontrolujte informácie. Je potrebné skontrolovať správnosť faktov a spoľahlivosť informácií uvedených v tomto článku. Na diskusnej stránke by mali byť vysvetlenia ... Wikipedia

Interaktivita je koncept, ktorý odhaľuje povahu a stupeň interakcie medzi objektmi. Používa sa v oblastiach: teória informácie, informatika a programovanie, telekomunikačné systémy, sociológia, priemyselný dizajn a iné. Vo ... ... Wikipédii

Tento článok by mal byť wikiifikovaný. Naformátujte ho prosím podľa pravidiel formátovania článkov. Tento výraz má iné významy, pozri Electromash (významy) ... Wikipedia

cudzie psychoterapeutické techniky- HĹBKOVÉ TECHNIKY Aktívna psychoterapia (Od Reichmanna). Analýza bytia (Binswanger). Analýza osudu (Sondi). Rozbor postáv (W. Reich). Analýza I (H. Kohut, E. Erickson). Analytická terapia hrou (M. Klein). Rodinná analytická terapia (Richter).… … Veľká psychologická encyklopédia

knihy

  • Reaktívne programovanie v C++. Navrhovanie paralelných a asynchrónnych aplikácií, Pai Praseed, Abraham Peter. Navrhovanie paralelných a asynchrónnych aplikácií pomocou knižnice RxCpp a moderného jazyka C++ 17 Podporované nástroje paralelného programovania Collaborative…
  • , Nurkevich T., Christensen B.. V týchto dňoch, keď sú programy asynchrónne a rýchla odozva je najdôležitejšou vlastnosťou, reaktívne programovanie pomôže písať spoľahlivejší, lepšie škálovateľný a rýchlejší kód.…
  • Reaktívne programovanie s RxJava, Tomas Nurkevich, Ben Christensen. V dnešnej dobe, keď sú programy asynchrónne a rýchla odozva je najdôležitejšou vlastnosťou, vám reaktívne programovanie pomôže napísať spoľahlivejší, lepšie škálovateľný a rýchlejší kód.…

Choď.

Reaktívne programovanie na prvý pohľad znie ako názov rodiacej sa paradigmy, ale v skutočnosti sa vzťahuje na metódu programovania, ktorá využíva na prácu s asynchrónnymi dátovými tokmi prístup riadený udalosťami. Na základe neustále aktuálnych údajov na ne reaktívne systémy reagujú vykonaním série udalostí.
Reaktívne programovanie sa riadi vzorom návrhu Observer, ktorý možno definovať nasledovne: ak dôjde k zmene stavu v jednom objekte, všetky ostatné objekty sú náležite upozornené a aktualizované. Takže namiesto dotazovania udalostí na zmeny sa udalosti posúvajú asynchrónne, aby ich pozorovatelia mohli spracovať. V tomto príklade sú pozorovatelia funkcie, ktoré sa vykonajú pri odoslaní udalosti. A spomínaný dátový tok je skutočne pozorovateľný.

Takmer všetky jazyky a rámce používajú tento prístup vo svojom ekosystéme a najnovšie verzie Java- nie je výnimkou. V tomto článku vysvetlím, ako môžete použiť reaktívne programovanie Najnovšia verzia JAX-RS vo funkciách Java EE 8 a Java 8.

Reaktívny manifest

Reaktívny manifest uvádza štyri základné aspekty, podľa ktorých musí byť aplikácia flexibilnejšia, voľnejšie prepojená a ľahko škálovateľná, a preto musí byť reaktívna. Hovorí, že aplikácia musí byť responzívna, flexibilná (a teda škálovateľná), odolná a riadená správami.

Základným cieľom je skutočne citlivá aplikácia. Povedzme, že máte aplikáciu, ktorá má jedno veľké vlákno spracúvajúce požiadavky používateľov, a keď je práca hotová, toto vlákno posiela odpovede späť pôvodným žiadateľom. Keď aplikácia dostane viac požiadaviek, než dokáže spracovať, tento tok sa stane prekážkou a aplikácia stratí svoju bývalú odozvu. Aby aplikácia zostala responzívna, musí byť škálovateľná a odolná. Odolná aplikácia je tá, ktorá má funkciu automatického obnovenia. Podľa skúseností väčšiny vývojárov iba architektúra riadená správami umožňuje, aby bola aplikácia škálovateľná, robustná a pohotová.

Reaktívne programovanie bolo predstavené v jazykoch Java 8 a Java EE 8. Jazyk Java zaviedol pojmy ako CompletionStage a jeho implementácia CompletableFuture a Java začala tieto funkcie používať v špecifikáciách, ako je Reactive Client API v JAX-RS.

JAX-RS 2.1 Reactive Client API

Pozrime sa, ako možno použiť reaktívne programovanie v aplikáciách Java EE 8. Na pochopenie procesu potrebujete základné znalosti Java EE API.

Predstavený JAX-RS 2.1 Nová cesta vytvorenie klienta REST s podporou reaktívneho programovania. Predvolená implementácia vyvolávača ponúkaného JAX-RS je synchrónna, čo znamená, že vytvoreného klienta odošle blokovacie volanie do koncového bodu servera. Príklad implementácie je uvedený vo výpise 1.

Odpoveď odpovede = ClientBuilder.newClient() .target("http://localhost:8080/service-url") .request() .get();
Počnúc verziou 2.0 poskytuje JAX-RS podporu pre vytváranie asynchrónneho vyvolávača na klientskom rozhraní API jednoduchým volaním metódy async(), ako je uvedené vo výpise 2.

Budúcnosť odpoveď = ClientBuilder.newClient() .target("http://localhost:8080/service-url") .request() .async() .get();
Použitie asynchrónneho vyvolávača na klientovi vráti budúcu inštanciu typu javax.ws.rs.core.Response . To môže mať za následok vyžiadanie odpovede, volanie future.get() alebo registráciu spätného volania, ktoré sa zavolá, keď bude dostupná odpoveď HTTP. Obidve implementácie sú vhodné pre asynchrónne programovanie, ale veci majú tendenciu byť komplikovanejšie, ak chcete zoskupiť spätné volania alebo pridať podmienené prípady k týmto minimám asynchrónneho vykonávania.

JAX-RS 2.1 poskytuje reaktívny spôsob na prekonanie týchto problémov s novým JAX-RS Reactive Client API pre zostavenie klienta. Je to také jednoduché ako volanie metódy rx() počas zostavovania klienta. Vo výpise 3 metóda rx() vracia reaktívny vyvolávač, ktorý existuje počas vykonávania klienta, a klient vracia odpoveď s typom CompletionStage.rx() , ktorý umožňuje prechod zo synchrónneho vyvolávača na asynchrónny vyvolávač jednoduchým hovor.

Fáza dokončenia odpoveď = ClientBuilder.newClient() .target("http://localhost:8080/service-url") .request() .rx() .get();
Fáza dokončenia<Т> - nové rozhranie, predstavený v Java 8. Predstavuje výpočet, ktorý môže byť krokom v rámci väčšieho výpočtu, ako už názov napovedá. Toto je jediná reaktivita Java 8, ktorá sa dostala do JAX-RS.
Po prijatí inštancie odpovede môžem zavolať AcceptAsync() , kde môžem poskytnúť časť kódu, ktorý sa vykoná asynchrónne, keď bude odpoveď k dispozícii, ako je uvedené vo výpise 4.

Response.thenAcceptAsync(res -> ( Teplota t = res.readEntity(Temperature.class); //robte veci s t ));
Pridanie reaktivity ku koncovému bodu REST

Reaktívny prístup nie je v JAX-RS obmedzený na stranu klienta; dá sa použiť aj na strane servera. Ako príklad si najprv vytvorím jednoduchý skript, v ktorom si môžem vyžiadať zoznam miest pre jeden cieľ. Pre každú polohu vykonám samostatný hovor s údajmi o polohe do iného bodu, aby som získal hodnoty teploty. Interakcia destinácií bude taká, ako je znázornené na obrázku 1.

Obrázok 1. Interakcia medzi destináciami

Najprv len definujem model domény a potom služby pre každý model. Výpis 5 ukazuje, ako je definovaná trieda Forecast, ktorá zahŕňa triedy Location a Temperature.

Public class Temperature ( private Double temperature; private String scale; // getters & setters ) public class Location (String name; public Location() () public Location (String name) ( this.name = name; ) // getters & setters ) public class Forecast ( private Location location; private Temperature temperature; public Forecast(Location location) ( this.location = location; ) public Forecast setTemperature(final Temperature temperature) ( this.temperature = teplota; return this; ) // getters )
Na zabalenie zoznamu predpovedí je vo výpise 6 implementovaná trieda ServiceResponse.

Verejná trieda ServiceResponse( súkromný dlhý čas spracovania; súkromný zoznam prognózy = nový ArrayList<>(); public void setProcessingTime(long processingTime) ( this.processingTime = processingTime; ) public ServiceResponse forecasts(List predpovede) ( this.forecasts = predpovede; vrátiť toto; ) // getters )
LocationResource, zobrazený vo výpise 7, definuje tri vzory umiestnení vrátených s cestou/umiestnením .

@Path("/location") public class LocationResource ( @GET @Produces(MediaType.APPLICATION_JSON) public Response getLocations() ( Zoznam miesta = nový ArrayList<>(); Locations.add(new Location("Londýn")); Locations.add(new Location("Istanbul")); Locations.add(new Location("Praha")); return Response.ok(new GenericEntity >(umiestnenia)()).build(); ))
TemperatureResource , zobrazený vo výpise 8, vracia náhodne vygenerovanú hodnotu teploty medzi 30 a 50 pre dané miesto. Do implementácie bolo pridané oneskorenie 500 ms na simuláciu čítania snímača.

@Path("/temperature") public class TemperatureResource ( @GET @Path("/(city)") @Produces(MediaType.APPLICATION_JSON) public Response getAverageTemperature(@PathParam("city") String cityName) ( Teplota teploty = new Temperature(); temperature.setTemperature((double) (new Random().nextInt(20) + 30)); temperature.setScale("Celsius"); try ( Thread.sleep(500); ) catch (InterruptedException ignorovaná) ( ignored.printStackTrace(); ) return Response.ok(temperature).build(); ) )
Najprv ukážem implementáciu synchrónneho ForecastResource (pozri Výpis 9), ktorý vracia všetky miesta. Potom pre každú polohu zavolá službu teploty, aby získal hodnoty v stupňoch Celzia.

@Path("/forecast") verejná trieda 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(); List Locations = locationTarget .request() .get(new GenericType >()()); forEach(location -> ( Teplota teplota = teplotaTarget .resolveTemplate("mesto", location.getName()) .request() .get(Temperature.class); response.getForecasts().add(new Forecast(location) .setTemperature (teplota)); )); long endTime = System.currentTimeMillis(); response.setProcessingTime(endTime - startTime); return Response.ok(response).build(); ))
Keď je predpovedaný cieľ vyžiadaný ako /forecast , dostanete výstup podobný výpisu 10. Všimnite si, že spracovanie požiadavky trvalo 1,533 ms, čo je logické, pretože vyžiadanie teplôt z troch rôznych miest synchrónne sčítava až 1,5 ms.

( "predpovede": [ ( "miesto": ( "meno": "Londýn" ), "teplota": ( "mierka": "Celsius", "teplota": 33) ), ( "miesto": ( "meno" ": "Istanbul" ), "teplota": ( "stupnica": "Celsius", "teplota": 38) ), ( "miesto": ( "názov": "Praha" ), "teplota": ( "stupnica" ": "Celsius", "teplota": 46 ) ) ], "čas spracovania": 1533 )
Zatiaľ ide všetko podľa plánu. Je čas zaviesť reaktívne programovanie na strane servera, kde je možné paralelne volať do každého miesta po prijatí všetkých miest. To môže jasne zlepšiť synchrónny tok znázornený vyššie. Toto sa vykonáva vo výpise 11, ktorý zobrazuje definíciu reaktívnej verzie predpovednej služby.

@Path("/reactiveForecast") verejná trieda 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(); // Vytvorenie fázy na získanie umiestnení CompletionStage > locationCS = locationTarget.request() .rx() .get(new GenericType >() ()); // Vytvorením samostatnej fázy vo fáze miest, // opísanej vyššie, zhromaždite zoznam predpovedí // ako v jednom veľkom konečnom štádiu CompletionStage. > forecastCS = locationCS.thenCompose(locations -> ( // Vytvorenie fázy na získanie predpovedí // ako zoznam CompletionStage List > forecastList = // Streamujte miesta a spracujte každé z nich // samostatne locations.stream().map(location -> ( // Vytvorte krok na získanie // hodnôt teploty iba jedného mesta // podľa názvu final CompletionStage tempCS = temperatureTarget .resolveTemplate("mesto", location.getName()) .request() .rx() .get(Temperature.class); // Potom vytvorte CompletableFuture, ktorá // obsahuje inštanciu predpovede s // umiestnením a hodnotou teploty return CompletableFuture.completedFuture(new Forecast(location)) .thenCombine(tempCS, Forecast::setTemperature); )).collect(Collectors.toList()); // Vráti konečnú inštanciu CompletableFuture, kde // všetky prezentované skompletizovateľné budúce objekty sú // dokončené return CompletableFuture.allOf(forecastList.toArray(new CompletableFuture)) .thenApply(v -> forecastList.stream() .map(CompletionStage::toCompletableFuture ) .map(CompletableFuture::join) .collect(Collectors.toList())); )); // Vytvorte inštanciu ServiceResponse, ktorá // obsahuje úplný zoznam prognózy // spolu s časom spracovania. // Vytvorte jeho budúcnosť a skombinujte ju s // forecastCS na získanie predpovedí // a vložte do služby odpoveď CompletableFuture.completedFuture(new ServiceResponse()) .thenCombine(forecastCS, ServiceResponse::forecasts) .whenCompleteAsync((response, throwable) - > ( response.setProcessingTime(System.currentTimeMillis() - startTime); async.resume(response); )); ))
Reaktívna implementácia sa môže zdať na prvý pohľad komplikovaná, no po bližšom pohľade si všimnete, že je celkom jednoduchá. V implementácii ForecastReactiveResource najskôr uskutočním klientske volanie do lokalizačných služieb pomocou JAX-RS Reactive Client API. Ako som spomenul vyššie, toto je doplnok pre Java EE 8 a pomáha vytvoriť reaktívne volanie jednoducho pomocou metódy rx().

Teraz vytváram novú fázu založenú na polohe, aby som zhromaždil zoznam predpovedí. Budú uložené ako zoznam prognóz v jednej veľkej fáze dokončenia s názvom forecastCS. Nakoniec vytvorím odpoveď na servisné volanie iba pomocou forecastCS .

Teraz zhromaždíme prognózy ako zoznam etáp dokončenia definovaných v premennej forecastList . Na vytvorenie fázy dokončenia pre každú predpoveď odovzdám údaje o polohe a potom vytvorím premennú tempCS, opäť pomocou rozhrania JAX-RS Reactive Client API, ktoré volá službu teploty s názvom mesta. Tu používam metódu resolveTemplate() na zostavenie klienta, čo mi umožňuje odovzdať názov mesta staviteľovi ako parameter.

Ako posledný krok streamovania zavolám CompletableFuture.completedFuture() , pričom ako parameter odovzdám novú inštanciu Forecast. Kombinujem túto budúcnosť s fázou tempCS, aby som mal hodnotu teploty pre iterované miesta.

Metóda CompletableFuture.allOf() vo výpise 11 konvertuje zoznam štádií dokončenia na forecastCS . Vykonaním tohto kroku sa vráti veľká splniteľná budúcnosť, keď budú dokončené všetky dodané splniteľné futures.

Odpoveď služby je inštanciou triedy ServiceResponse, takže vytvorím dokončenú budúcnosť a potom zreťazím fázu dokončenia forecastCS so zoznamom prognóz a vypočítam čas odozvy služby.

Samozrejme, reaktívne programovanie iba núti stranu servera, aby sa vykonávala asynchrónne; strana klienta bude blokovať, kým server nepošle odpoveď späť žiadateľovi. Na prekonanie tohto problému je možné použiť Server Sent Events (SSE) na odoslanie čiastočnej odpovede hneď, ako je k dispozícii, takže hodnoty teploty pre každé miesto sa odosielajú klientovi jedna po druhej. Výstup ForecastReactiveResource bude podobný výpisu 12. Ako je uvedené vo výstupe, čas spracovania je 515 ms, čo je ideálny čas vykonania na získanie hodnôt teploty z jedného miesta.

( "predpovede": [ ( "miesto": ( "meno": "Londýn" ), "teplota": ( "mierka": "Celsius", "teplota": 49) ), ( "miesto": ( "meno" ": "Istanbul" ), "teplota": ( "stupnica": "Celsius", "teplota": 32 ) ), ( "miesto": ( "názov": "Praha" ), "teplota": ( "stupnica" ": "Celsius", "teplota": 45 ) ) ], "čas spracovania": 515 )
Záver

V príkladoch v tomto článku som najprv ukázal synchrónny spôsob získavania predpovedí pomocou služieb polohy a teploty. Potom som prešiel na reaktívny prístup, aby som medzi servisnými volaniami vykonal asynchrónne spracovanie. Keď používate JAX-RS Reactive Client API v Java EE 8 spolu s triedami CompletionStage a CompletableFuture dostupnými v Java 8, sila asynchrónneho spracovania sa uvoľní prostredníctvom reaktívneho programovania.

Reaktívne programovanie je viac než len implementácia asynchrónneho modelu zo synchrónneho; zjednodušuje tiež koncepty, ako je štádium hniezdenia. Čím viac sa bude používať, tým ľahšie bude spravovať zložité scenáre v paralelnom programovaní.

Ďakujem za tvoju pozornosť. Ako vždy uvítame vaše pripomienky a otázky.

Môžete pomôcť a previesť nejaké prostriedky na rozvoj stránky



Načítava...
Hore