Reaktiv programmering - Reactive programming

I computing er reaktiv programmering et deklarativt programmeringsparadigme, der beskæftiger sig med datastrømme og udbredelse af ændringer. Med dette paradigme er det let at udtrykke statiske (f.eks. Arrays) eller dynamiske (f.eks. Hændelsesemitterende) datastrømme og også kommunikere, at der findes en udledt afhængighed inden for den tilhørende udførelsesmodel , hvilket letter automatisk spredning af de ændrede data flyde.

For eksempel vil en imperativ programmeringsindstilling betyde, at der tildeles resultatet af i det øjeblik udtrykket evalueres, og senere kan værdierne for og kan ændres uden nogen indvirkning på værdien af . På den anden side ved reaktiv programmering opdateres værdien af automatisk, når værdierne for eller ændres, uden at programmet eksplicit skal genudføre sætningen for at bestemme den aktuelt tildelte værdi af

var b = 1
var c = 2
var a = b + c
b = 10
console.log(a) // 3 (not 12 because "=" is not a reactive assignment operator)

// now imagine you have a special operator "$=" that changes the value of a variable (executes code on the right side of the operator and assigns result to left side variable) not only when explicitly initialized, but also when referenced variables (on the right side of the operator) are changed
var b = 1
var c = 2
var a $= b + c
b = 10
console.log(a) // 12

Et andet eksempel er et hardware -beskrivelsessprog som Verilog , hvor reaktiv programmering gør det muligt at modellere ændringer, når de formerer sig gennem kredsløb.

Reaktiv programmering er blevet foreslået som en måde at forenkle oprettelsen af ​​interaktive brugergrænseflader og systemanimation i næsten realtid.

For eksempel i en model -view -controller (MVC) arkitektur kan reaktiv programmering lette ændringer i en underliggende model , der afspejles automatisk i en tilknyttet visning .

Tilgange til oprettelse af reaktive programmeringssprog

Flere populære fremgangsmåder anvendes til oprettelse af reaktive programmeringssprog. Specifikation af dedikerede sprog, der er specifikke for forskellige domænebegrænsninger . Sådanne begrænsninger er normalt kendetegnet ved realtid, integreret computing eller hardware beskrivelse. En anden tilgang involverer specifikationen af sprog til generelle formål , der inkluderer understøttelse af reaktivitet. Andre metoder er formuleret i definitionen, og anvendelsen af programmering biblioteker , eller indlejret domænespecifikke sprog , der gør det muligt reaktivitet ved siden af eller oven på programmeringssprog. Specifikation og anvendelse af disse forskellige fremgangsmåder resulterer i afvejninger mellem sproglige muligheder . Generelt, jo mere begrænset et sprog er, desto mere er dets tilknyttede kompilatorer og analyseværktøjer i stand til at informere udviklere (f.eks. Ved analyse af, om programmer er i stand til at udføre i realtid). Funktionelle afvejninger i specificitet kan resultere i forringelse af et sprogs generelle anvendelighed.

Programmering af modeller og semantik

En række modeller og semantik styrer familien af ​​reaktiv programmering. Vi kan løst opdele dem langs følgende dimensioner:

  • Synkronisering: er den underliggende tidsmodel synkron versus asynkron?
  • Determinisme: Deterministisk versus ikke-deterministisk i både evalueringsproces og resultater
  • Opdateringsproces: tilbagekald kontra dataforløb versus aktør

Implementeringsteknikker og udfordringer

Essensen af ​​implementeringer

Reaktive programmeringssprogskørselstider repræsenteres af en graf, der identificerer afhængighederne blandt de involverede reaktive værdier. I en sådan graf repræsenterer noder handlingen med computing og kanter afhængighedsrelationer. En sådan runtime anvender grafen for at hjælpe den med at holde styr på de forskellige beregninger, som skal udføres på ny, når en involveret input ændrer værdi.

Skift formeringsalgoritmer

De mest almindelige metoder til datapropagering er:

  • Træk : Værdiforbrugeren er faktisk proaktiv , ved at den regelmæssigt forespørger den observerede kilde om værdier og reagerer, når en relevant værdi er tilgængelig. Denne praksis med regelmæssig kontrol af begivenheder eller værdiændringer kaldes almindeligvis polling .
  • Push : Værdiforbrugeren modtager en værdi fra kilden, når værdien bliver tilgængelig. Disse værdier er selvstændige, f.eks. Indeholder de alle nødvendige oplysninger, og der er ikke behov for yderligere forespørgsler fra forbrugeren.
  • Push-pull : Værdiforbrugeren modtager en ændringsmeddelelse , som er en kort beskrivelse af ændringen, fx "en vis værdi ændret"-dette er push- delen. Meddelelsen indeholder imidlertid ikke alle de nødvendige oplysninger (dvs. indeholder ikke de faktiske værdier), så forbrugeren skal spørge kilden om mere information (den specifikke værdi), efter at den har modtaget meddelelsen - dette er trækdelen . Denne metode bruges almindeligvis, når der er en stor mængde data, som forbrugerne potentielt kan være interesserede i. Så for at reducere gennemstrømning og latenstid sendes der kun lette meddelelser; og derefter vil de forbrugere, der kræver mere information, anmode om disse specifikke oplysninger. Denne tilgang har også den ulempe, at kilden kan blive overvældet af mange anmodninger om yderligere oplysninger, efter at en meddelelse er sendt.

Hvad skal skubbe?

På implementeringsniveau består hændelsesreaktion af spredning på tværs af en grafs information, som karakteriserer eksistensen af ​​forandring. Følgelig bliver beregninger, der påvirkes af en sådan ændring, forældede og skal markeres til genudførelse. Sådanne beregninger er derefter normalt karakteriseret ved den transitive lukning af ændringen i den tilhørende kilde. Ændringsspredning kan derefter føre til en opdatering af værdien af ​​grafens dræn .

Grafudbredt information kan bestå af en node's komplette tilstand, dvs. beregningsresultatet af den involverede knude. I sådanne tilfælde ignoreres nodens tidligere output derefter. En anden metode involverer deltapropagering, dvs. inkrementel forandringsspredning . I dette tilfælde spredes information langs en grafs kanter, som kun består af delta s, der beskriver, hvordan den tidligere knude blev ændret. Denne fremgangsmåde er især vigtig, når noder indeholder store mængder statsdata , som ellers ville være dyre at genberegne fra bunden.

Delta-udbredelse er i det væsentlige en optimering, der er blevet grundigt undersøgt via disciplinen inkrementel computing , hvis tilgang kræver runtime-tilfredshed, der involverer view-update-problemet . Dette problem er berygtet karakteriseret ved brugen af databaseenheder , der er ansvarlige for vedligeholdelsen af ​​ændrede datavisninger.

En anden almindelig optimering er ansættelse af unary ændringsakkumulering og batchformering . En sådan løsning kan være hurtigere, fordi den reducerer kommunikationen mellem involverede noder. Optimeringsstrategier kan derefter bruges til at begrunde arten af ​​ændringerne indeholdt i og foretage ændringer i overensstemmelse hermed. f.eks. to ændringer i batchen kan annullere hinanden og dermed simpelthen ignoreres. Endnu en tilgængelig fremgangsmåde beskrives som udbredelse af ugyldighedsmeddelelser . Denne fremgangsmåde får noder med ugyldigt input til at trække opdateringer, hvilket resulterer i opdatering af deres egne output.

Der er to hovedmåder, der anvendes i opbygningen af ​​en afhængighedsgraf :

  1. Grafen over afhængigheder bevares implicit inden for en hændelsessløjfe . Registrering af eksplicitte tilbagekald, resulterer derefter i oprettelsen af ​​implicitte afhængigheder. Derfor lades kontrolinversion , der induceres via tilbagekald, således på plads. Men for at gøre tilbagekald funktionelle (dvs. returnerende tilstandsværdi i stedet for enhedsværdi) er det nødvendigt, at sådanne tilbagekald bliver kompositoriske.
  2. En graf over afhængigheder er programspecifik og genereret af en programmør. Dette letter en adressering af tilbagekaldets kontrolinversion på to måder: enten angives en graf eksplicit (typisk ved hjælp af et domænespecifikt sprog (DSL), som kan være integreret), eller en graf er implicit defineret med udtryk og generering ved hjælp af et effektivt , arketypisk sprog .

Implementeringsudfordringer i reaktiv programmering

Fejl

Ved udbredelse af ændringer er det muligt at vælge udbredelsesordrer, så værdien af ​​et udtryk ikke er en naturlig konsekvens af kildeprogrammet. Vi kan let illustrere dette med et eksempel. Antag at secondser en reaktiv værdi, der ændres hvert sekund for at repræsentere den aktuelle tid (i sekunder). Overvej dette udtryk:

t = seconds + 1
g = (t > seconds)
Reaktiv programmering glitches.svg

Fordi dette udtryk taltid skal være større end seconds, bør dette udtryk altid evaluere til en sand værdi. Desværre kan dette afhænge af evalueringsrækkefølgen. Ved secondsændringer skal to udtryk opdateres: seconds + 1og betinget. Hvis den første evaluerer før den anden, så vil denne invariant holde. Hvis de betingede opdateringer imidlertid først ved hjælp af den gamle værdi af tog den nye værdi af seconds, evalueres udtrykket til en falsk værdi. Dette kaldes en fejl .

Nogle reaktive sprog er fejlfrie og beviser denne egenskab. Dette opnås normalt ved topologisk at sortere udtryk og opdatere værdier i topologisk rækkefølge. Dette kan imidlertid have konsekvenser for ydeevnen, såsom forsinkelse af levering af værdier (på grund af rækkefølgen af ​​udbredelse). I nogle tilfælde tillader reaktive sprog derfor fejl, og udviklere skal være opmærksom på muligheden for, at værdier midlertidigt ikke svarer til programkilden, og at nogle udtryk kan evaluere flere gange (f.eks. t > secondsKan evaluere to gange: én gang når ny værdi af secondsankommer, og endnu en gang ved topdateringer).

Cykliske afhængigheder

Topologisk sortering af afhængigheder afhænger af, at afhængighedsgrafen er en rettet acyklisk graf (DAG). I praksis kan et program definere en afhængighedsgraf, der har cyklusser. Normalt forventer reaktive programmeringssprog, at sådanne cyklusser bliver "brudt" ved at placere et element langs en "bagkant" for at tillade reaktiv opdatering at afslutte. Typisk giver sprog en operatør som delayden, der bruges af opdateringsmekanismen til dette formål, da en delayindebærer, at det følgende skal evalueres i "næste tidstrin" (så den aktuelle evaluering kan afsluttes).

Interaktion med muterbar tilstand

Reaktive sprog antager typisk, at deres udtryk er rent funktionelle . Dette gør det muligt for en opdateringsmekanisme at vælge forskellige ordrer, hvor der skal udføres opdateringer, og lade den specifikke ordre være uspecificeret (hvilket muliggør optimeringer). Når et reaktivt sprog er indlejret i et programmeringssprog med tilstand, kan det imidlertid være muligt for programmører at udføre mutable operationer. Hvordan man gør denne interaktion glat, er stadig et åbent problem.

I nogle tilfælde er det muligt at have principielle delløsninger. To sådanne løsninger omfatter:

  • Et sprog kan tilbyde en forestilling om "mutable cell". En muterbar celle er en, som det reaktive opdateringssystem er opmærksom på, så ændringer foretaget i cellen spreder sig til resten af ​​det reaktive program. Dette gør det muligt for den ikke-reaktive del af programmet at udføre en traditionel mutation, samtidig med at reaktiv kode gøres opmærksom på og reagerer på denne opdatering, og dermed bevarer sammenhængen i forholdet mellem værdier i programmet. Et eksempel på et reaktivt sprog, der giver en sådan celle, er FrTime.
  • Korrekt indkapslede objektorienterede biblioteker tilbyder en indkapslet statsopfattelse. I princippet er det derfor muligt for et sådant bibliotek at interagere problemfrit med den reaktive del af et sprog. For eksempel kan tilbagekald installeres i getters i det objektorienterede bibliotek for at underrette den reaktive opdateringsmotor om tilstandsændringer, og ændringer i den reaktive komponent kan skubbes til det objektorienterede bibliotek gennem getters. FrTime anvender en sådan strategi.

Dynamisk opdatering af grafen for afhængigheder

I nogle reaktive sprog er grafen for afhængigheder statisk , dvs. grafen er fast i hele programmets udførelse. På andre sprog kan grafen være dynamisk , dvs. den kan ændre sig, når programmet udføres. For et enkelt eksempel, overvej dette illustrative eksempel (hvor secondser en reaktiv værdi):

t =
  if ((seconds mod 2) == 0):
    seconds + 1
  else:
    seconds - 1
  end
t + 1

Hvert sekund ændres værdien af ​​dette udtryk til et andet reaktivt udtryk, som t + 1derefter afhænger af. Derfor opdateres grafen for afhængigheder hvert sekund.

Tilladelse til dynamisk opdatering af afhængigheder giver betydelig udtrykskraft (f.eks. Forekommer dynamiske afhængigheder rutinemæssigt i grafiske brugergrænsefladeprogrammer ). Den reaktive opdateringsmotor skal imidlertid beslutte, om udtryk skal rekonstrueres hver gang, eller om et ekspressions knude skal være konstrueret, men inaktiv; i sidstnævnte tilfælde skal du sikre, at de ikke deltager i beregningen, når de ikke skal være aktive.

Begreber

Grader af eksplicititet

Reaktive programmeringssprog kan variere fra meget eksplicitte sprog, hvor datastrømme sættes op ved hjælp af pile, til implicit, hvor datastrømmene er afledt af sprogkonstruktioner, der ligner dem, der er nødvendige eller funktionel programmering. F.eks. Ved implicit løftet funktionel reaktiv programmering (FRP) kan et funktionsopkald implicit implicit bevirke, at en knude i en datastrømningsgraf konstrueres. Reaktive programmeringsbiblioteker til dynamiske sprog (f.eks. Lisp "Cells" - og Python "Trellis" -bibliotekerne) kan konstruere en afhængighedsgraf ud fra runtime -analyse af de værdier, der læses under en funktions udførelse, hvilket gør datastrømspecifikationer både implicitte og dynamiske.

Nogle gange refererer udtrykket reaktiv programmering til det arkitektoniske niveau af software engineering, hvor individuelle noder i dataflowgrafen er almindelige programmer, der kommunikerer med hinanden.

Statisk eller dynamisk

Reaktiv programmering kan være rent statisk, hvor datastrømmene er konfigureret statisk, eller være dynamisk, hvor datastrømmene kan ændre sig under udførelsen af ​​et program.

Brugen af ​​dataskiftere i dataflowgrafen kan i et vist omfang få en statisk dataflowgraf til at fremstå som dynamisk og sløre sondringen lidt. Ægte dynamisk reaktiv programmering kan dog bruge tvingende programmering til at rekonstruere dataflowgrafen.

Højere orden reaktiv programmering

Reaktiv programmering kan siges at være af højere orden, hvis den understøtter tanken om, at datastrømme kan bruges til at konstruere andre datastrømme. Det vil sige, at den resulterende værdi ud af en datastrøm er en anden dataflowgraf, der udføres ved hjælp af den samme evalueringsmodel som den første.

Dataflowdifferentiering

Ideelt spredes alle dataændringer øjeblikkeligt, men dette kan ikke garanteres i praksis. I stedet kan det være nødvendigt at give forskellige dele af dataflowgrafen forskellige evalueringsprioriteter. Dette kan kaldes differentieret reaktiv programmering .

For eksempel i en tekstbehandler behøver markeringen af ​​stavefejl ikke at være helt synkroniseret med indsættelsen af ​​tegn. Her kan differentieret reaktiv programmering potentielt bruges til at give stavekontrollen lavere prioritet, så den kan blive forsinket og samtidig holde andre datastrømme øjeblikkelige.

En sådan differentiering introducerer imidlertid yderligere designkompleksitet. For eksempel at beslutte, hvordan man definerer de forskellige datastrømningsområder, og hvordan man håndterer hændelsesoverførsel mellem forskellige datastrømningsområder.

Evalueringsmodeller for reaktiv programmering

Evaluering af reaktive programmer er ikke nødvendigvis baseret på, hvordan stakbaserede programmeringssprog evalueres. I stedet, når nogle data ændres, spredes ændringen til alle data, der er afledt delvist eller fuldstændigt fra de data, der blev ændret. Denne forandringsspredning kan opnås på en række måder, hvor den mest naturlige måde måske er en ugyldiggørelse/doven-revalideringsordning.

Det kan være problematisk blot at naivt udbrede en ændring ved hjælp af en stak på grund af potentiel eksponentiel opdateringskompleksitet, hvis datastrukturen har en bestemt form. En sådan form kan beskrives som "form for gentagne diamanter" og har følgende struktur: A n → B n → A n+1 , A n → C n → A n+1 , hvor n = 1,2 ... Dette problem kunne løses ved kun at udbrede ugyldighed, når nogle data ikke allerede er ugyldige, og senere re-validere dataene, når det er nødvendigt ved hjælp af doven evaluering .

Et iboende problem for reaktiv programmering er, at de fleste beregninger, der ville blive evalueret og glemt i et normalt programmeringssprog, skal repræsenteres i hukommelsen som datastrukturer. Dette kan potentielt gøre reaktiv programmering meget hukommelseskrævende. Men forskning om det, der kaldes sænkning, kan muligvis overvinde dette problem.

På den anden side er reaktiv programmering en form for det, der kan beskrives som "eksplicit parallelisme", og kan derfor være gavnligt for at udnytte kraften i parallel hardware.

Ligheder med observatørmønster

Reaktiv programmering har principielle ligheder med det observatormønster, der almindeligvis bruges i objektorienteret programmering . Imidlertid ville integrering af dataflowbegreberne i programmeringssproget gøre det lettere at udtrykke dem og kunne derfor øge dataflowgrafens granularitet. For eksempel beskriver observatormønsteret almindeligt datastrømme mellem hele objekter/klasser, hvorimod objektorienteret reaktiv programmering kunne målrette medlemmerne af objekter/klasser.

Tilgange

Imperativ

Det er muligt at fusionere reaktiv programmering med almindelig imperativ programmering. I et sådant paradigme fungerer tvingende programmer på reaktive datastrukturer. En sådan opsætning er analog med tvangsmæssig tvingende programmering ; Selvom tvingende tvingende programmering administrerer tovejsbegrænsninger, håndterer reaktiv tvingende programmering imidlertid envejs dataflowbegrænsninger.

Objektorienteret

Objektorienteret reaktiv programmering (OORP) er en kombination af objektorienteret programmering og reaktiv programmering. Den måske mest naturlige måde at lave en sådan kombination på er som følger: I stedet for metoder og felter har objekter reaktioner, der automatisk revurderer, når de andre reaktioner, de er afhængige af, er blevet ændret.

Hvis et OORP -sprog fastholder sine tvingende metoder, ville det også falde ind under kategorien imperativ reaktiv programmering.

Funktionel

Funktionel reaktiv programmering (FRP) er et programmeringsparadigme for reaktiv programmering om funktionel programmering .

Skuespiller baseret

Aktører er blevet foreslået at designe reaktive systemer, ofte i kombination med Functional reactive programmering (FRP) for at udvikle distribuerede reaktive systemer.

Regel baseret

En relativt ny kategori af programmeringssprog bruger begrænsninger (regler) som hovedprogrammeringskoncept. Den består af reaktioner på begivenheder, som holder alle begrænsninger tilfredse. Dette letter ikke kun hændelsesbaserede reaktioner, men det gør reaktive programmer medvirkende til softwareens rigtighed. Et eksempel på et regelbaseret reaktivt programmeringssprog er Ampersand, som er grundlagt i relation til algebra .

Implementeringer

  • ReactiveX , en API til implementering af reaktiv programmering med streams, observerbare og operatører med flere sprogimplementeringer, herunder RxJs, RxJava, .NET, RxPy og RxSwift.
  • Elm (programmeringssprog) Reaktiv sammensætning af webbrugergrænseflade.
  • Reactive Streams , en JVM-standard for asynkron strømbehandling med ikke-blokerende modtryk
  • ObservableComputations , en cross-platform .NET implementering.

Se også

Referencer

  1. ^ Trellis, Model-view-controller og observatørmønsteret , telesamfund.
  2. ^ "Indlejring af dynamisk dataflow i et opkaldsværdi-sprog" . cs.brown.edu . Hentet 2016-10-09 .
  3. ^ "Crossing State Lines: Tilpasning af objektorienterede rammer til funktionelle reaktive sprog" . cs.brown.edu . Hentet 2016-10-09 .
  4. ^ "Reaktiv programmering - Servicekunsten | IT -styringsguiden" . theartofservice.com . Hentet 2016-07-02 .
  5. ^ Burchett, Kimberley; Cooper, Gregory H; Krishnamurthi, Shriram, "Sænkning: en statisk optimeringsteknik til gennemsigtig funktionel reaktivitet", Proceedings of the 2007 ACM SIGPLAN symposium on Partial assessment and semantics-based program manipulation (PDF) , s. 71–80.
  6. ^ Demetrescu, Camil; Finocchi, Irene; Ribichini, Andrea (22. oktober 2011), "Reactive Imperative Programming with Dataflow Constraints", Proceedings of the 2011 ACM international Conference on Object orienterede programmeringssystemers sprog og applikationer , Oopsla '11, s. 407–26, doi : 10.1145/2048066.2048100 , ISBN 9781450309400, S2CID  7285961.
  7. ^ Van den Vonder, Sam; Renaux, Thierry; Oeyen, Bjarno; De Koster, Joeri; De Meuter, Wolfgang (2020), "Tackling the Awkward Squad for Reactive Programming: The Actor-Reactor Model", Leibniz International Proceedings in Informatics (LIPIcs) , 166 , s. 19: 1–19: 29, doi : 10.4230/LIPIcs .ECOOP.2020.19 , ISBN 9783959771542.
  8. ^ Shibanai, Kazuhiro; Watanabe, Takuo (2018), "Distributed Functional Reactive Programming on Actor-Based Runtime", Proceedings of the 8. ACM SIGPLAN International Workshop on Programming Based on Actors, Agents, and Decentralized Control , Agere 2018, s. 13–22, doi : 10.1145/3281366.3281370 , ISBN 9781450360661, S2CID  53113447.
  9. ^ Joosten, Stef (2018), "Relationsalgebra som programmeringssprog ved hjælp af Ampersand -kompilatoren", Journal of Logical and Algebraic Methods in Programming , 100 , s. 113–29, doi : 10.1016/j.jlamp.2018.04.002.

eksterne links