Reaktiv programmering

Inom databehandling är reaktiv programmering ett deklarativt programmeringsparadigm som handlar om dataströmmar och spridning av förändring . Med detta paradigm är det möjligt att enkelt uttrycka statiska (t.ex. arrayer) eller dynamiska (t.ex. händelsesändare) dataströmmar och även kommunicera att ett antaget beroende inom den associerade exekveringsmodellen existerar, vilket underlättar den automatiska spridningen av de ändrade data flöde. [ citat behövs ]

, i en imperativ programmeringsinställning skulle a := b + c betyda att a tilldelas resultatet av b + c i det ögonblick då uttrycket utvärderas, och senare kan värdena för b och c ändras utan att effekt på värdet av en . Å andra sidan, i reaktiv programmering, uppdateras värdet på a automatiskt när värdena för b eller c ändras, utan att programmet behöver explicit köra om påståendet a := b + c för att bestämma det för närvarande tilldelade värdet på en . [ citat behövs ]

   
   
     
  
 


   
   
     
  
  var  b  =  1  var  c  =  2  var  a  =  b  +  c  b  =  10  konsol  .  log  (  a  )  // 3 (inte 12 eftersom "=" inte är en reaktiv tilldelningsoperator)  // tänk dig nu att du har en speciell operator "$=" som ändrar värdet på en variabel (kör kod på höger sida av operator och tilldelar resultat till vänstersidig variabel) inte bara när de explicit initieras, utan också när refererade variabler (på höger sida av operatorn) ändras  var  b  =  1  var  c  =  2  var  a  $  =  b  +  c  b  =  10  konsol  .  log  (  a  )  // 12 

Ett annat exempel är ett hårdvarubeskrivningsspråk som Verilog , där reaktiv programmering gör det möjligt att modellera förändringar när de fortplantar sig genom kretsar. [ citat behövs ]

Reaktiv programmering har föreslagits som ett sätt att förenkla skapandet av interaktiva användargränssnitt och systemanimering i nästan realtid. [ citat behövs ]

Till exempel, i en modell-vy-styrenhet (MVC)-arkitektur, kan reaktiv programmering underlätta ändringar i en underliggande modell som återspeglas automatiskt i en tillhörande vy .

Tillvägagångssätt för att skapa reaktiva programmeringsspråk

Flera populära metoder används för att skapa reaktiva programmeringsspråk. Specifikation av dedikerade språk som är specifika för olika domänbegränsningar . Sådana begränsningar kännetecknas vanligtvis av realtids-, inbäddad databehandling eller hårdvarubeskrivning. Ett annat tillvägagångssätt involverar specifikationen av allmänna språk som inkluderar stöd för reaktivitet. Andra tillvägagångssätt är artikulerade i definitionen och användningen av programmeringsbibliotek, eller inbäddade domänspecifika språk , som möjliggör reaktivitet vid sidan av eller ovanpå programmeringsspråket. Specifikation och användning av dessa olika tillvägagångssätt resulterar i avvägningar mellan språkkunskaper . I allmänhet gäller att ju mer begränsat ett språk är, desto mer kan dess associerade kompilatorer och analysverktyg informera utvecklare (t.ex. när de utför analys av om program kan köras i verklig realtid). Funktionella avvägningar i specificitet kan resultera i försämring av ett språks allmänna tillämplighet.

Programmeringsmodeller och semantik

En mängd olika modeller och semantik styr reaktiv programmering. Vi kan löst dela dem längs följande dimensioner:

  • Synchrony: synkron kontra asynkron modell av tid
  • Determinism: deterministisk kontra icke-deterministisk utvärderingsprocess och resultat
  • Uppdateringsprocess: callback kontra dataflöde kontra aktör

Implementeringstekniker och utmaningar

Kärnan i implementeringar

Reaktiva programmeringsspråks körtider representeras av en graf som identifierar beroenden mellan de inblandade reaktiva värdena. I en sådan graf noder beräkningshandlingen och kantermodellberoenderelationer . En sådan körtid använder nämnda graf för att hjälpa den att hålla reda på de olika beräkningarna, som måste utföras på nytt, när en involverad ingång ändrar värde.

Ändra spridningsalgoritmer

De vanligaste metoderna för dataspridning är:

  • Pull : Värdekonsumenten är i själva verket proaktiv genom att den regelbundet frågar efter värden från den observerade källan och reagerar närhelst ett relevant värde är tillgängligt. Denna praxis att regelbundet kontrollera efter händelser eller värdeförändringar kallas ofta för polling .
  • Push : Värdekonsumenten får ett värde från källan när värdet blir tillgängligt. Dessa värden är fristående, t.ex. innehåller de all nödvändig information, och ingen ytterligare information behöver efterfrågas av konsumenten.
  • Push-pull : Värdekonsumenten får ett ändringsmeddelande , som är en kort beskrivning av ändringen, t.ex. "något värde ändrat" – detta är push- delen. Aviseringen innehåller dock inte all nödvändig information (vilket innebär att den inte innehåller de faktiska värdena), så konsumenten måste fråga källan för mer information (det specifika värdet) efter att den tagit emot meddelandet – detta är pull - delen . Den här metoden används ofta när det finns en stor mängd data som konsumenterna potentiellt kan vara intresserade av. Så för att minska genomströmning och latens skickas endast lätta aviseringar; och sedan kommer de konsumenter som behöver mer information att begära den specifika informationen. Detta tillvägagångssätt har också nackdelen att källan kan bli överväldigad av många förfrågningar om ytterligare information efter att ett meddelande har skickats.

Vad ska man trycka på?

På implementeringsnivån består händelsereaktion av spridningen över en grafs information, vilket kännetecknar förekomsten av förändring. Följaktligen blir beräkningar som påverkas av en sådan förändring inaktuella och måste flaggas för omkörning. Sådana beräkningar kännetecknas sedan vanligtvis av den transitiva stängningen av förändringen i dess associerade källa. Ändringsutbredning kan då leda till en uppdatering av värdet på grafens sänkor .

Grafpropagerad information kan bestå av en nods fullständiga tillstånd, dvs. beräkningsresultatet för den involverade noden. I sådana fall ignoreras sedan nodens tidigare utdata. En annan metod involverar deltautbredning dvs inkrementell förändringsutbredning . I det här fallet sprids information längs en grafs kanter, som endast består av delta som beskriver hur den tidigare noden ändrades. Detta tillvägagångssätt är särskilt viktigt när noder innehåller stora mängder tillståndsdata , vilket annars skulle vara dyrt att räkna om från början.

Delta-utbredning är i huvudsak en optimering som har studerats i stor utsträckning via disciplinen inkrementell beräkning , vars tillvägagångssätt kräver runtime-tillfredsställelse som involverar vyuppdateringsproblemet . Detta problem kännetecknas ökänt av användningen av databasenheter , som är ansvariga för underhållet av ändrade datavyer.

En annan vanlig optimering är användning av unär förändringsackumulering och batchutbredning . En sådan lösning kan vara snabbare eftersom den minskar kommunikationen mellan inblandade noder. Optimeringsstrategier kan sedan användas som resonerar om karaktären av de förändringar som finns inom, och göra ändringar i enlighet därmed. t.ex. kan två förändringar i batchen avbryta varandra och därmed helt enkelt ignoreras. Ytterligare ett annat tillgängligt tillvägagångssätt beskrivs som spridning av meddelande om ogiltighet . Detta tillvägagångssätt gör att noder med ogiltig indata drar uppdateringar, vilket resulterar i uppdatering av deras egna utgångar.

Det finns två huvudsakliga sätt som används för att bygga en beroendegraf :

  1. Grafen över beroenden bibehålls implicit i en händelseloop . Registrering av explicita återuppringningar resulterar sedan i skapandet av implicita beroenden. Därför lämnas kontrollinversionen , som induceras via callback, på plats. Men att göra återuppringningar funktionella (dvs. returnera tillståndsvärde istället för enhetsvärde) kräver att sådana återuppringningar blir sammansatta.
  2. En graf över beroenden är programspecifik och genererad av en programmerare. Detta underlättar en adressering av återuppringningens kontrollinversion på två sätt: antingen specificeras en graf explicit (typiskt med användning av ett domänspecifikt språk (DSL), som kan vara inbäddat), eller så definieras en graf implicit med uttryck och generering med hjälp av en effektiv , arketypiskt språk .

Implementeringsutmaningar i reaktiv programmering

Fel

När förändringar sprids är det möjligt att välja spridningsordningar så att värdet av ett uttryck inte är en naturlig konsekvens av källprogrammet. Vi kan enkelt illustrera detta med ett exempel. Antag att sekunder är ett reaktivt värde som ändras varje sekund för att representera den aktuella tiden (i sekunder). Tänk på detta uttryck:

t = sekunder + 1 g = (t > sekunder)
Reactive programming glitches.svg

Eftersom t alltid bör vara större än sekunder bör detta uttryck alltid utvärderas till ett sant värde. Tyvärr kan detta bero på utvärderingsordningen. När sekunderna ändras måste två uttryck uppdateras: sekunder + 1 och det villkorliga. Om den första utvärderas före den andra, kommer denna invariant att gälla. Om det villkorliga dock uppdateras först, med det gamla värdet på t och det nya värdet på sekunder , kommer uttrycket att utvärderas till ett falskt värde. Detta kallas en glitch .

Vissa reaktiva språk är felfria och bevisar denna egenskap [ citat behövs ] . Detta uppnås vanligtvis genom att topologiskt sortera uttryck och uppdatera värden i topologisk ordning. Detta kan dock få prestandaimplikationer, som att fördröja leveransen av värden (på grund av spridningsordningen). I vissa fall tillåter därför reaktiva språk fel, och utvecklare måste vara medvetna om möjligheten att värden tillfälligt misslyckas med att överensstämma med programkällan, och att vissa uttryck kan utvärderas flera gånger (till exempel kan t > sekunder utvärderas två gånger : en gång när det nya värdet på sekunder kommer och en gång till när t uppdateras).

Cykliska beroenden

Topologisk sortering av beroenden beror på att beroendegrafen är en riktad acyklisk graf (DAG). I praktiken kan ett program definiera en beroendegraf som har cykler. Vanligtvis förväntar reaktiva programmeringsspråk att sådana cykler "bryts" genom att placera något element längs en "bakkant" för att tillåta reaktiv uppdatering att avslutas. Vanligtvis tillhandahåller språk en operatörsliknande fördröjning som används av uppdateringsmekanismen för detta ändamål, eftersom en fördröjning innebär att det som följer måste utvärderas i "nästa gångsteg" (så att den aktuella utvärderingen kan avslutas).

Interaktion med föränderligt tillstånd

Reaktiva språk antar vanligtvis att deras uttryck är rent funktionella . Detta tillåter en uppdateringsmekanism att välja olika ordningsföljder för att utföra uppdateringar och lämna den specifika ordningen ospecificerad (och därmed möjliggöra optimeringar). När ett reaktivt språk är inbäddat i ett programmeringsspråk med tillstånd, kan det dock vara möjligt för programmerare att utföra föränderliga operationer. Hur man gör denna interaktion smidig är fortfarande ett öppet problem.

I vissa fall går det att ha principiella dellösningar. Två sådana lösningar inkluderar:

  • Ett språk kan erbjuda begreppet "föränderlig cell". En föränderlig cell är en som det reaktiva uppdateringssystemet är medvetet om, så att ändringar som görs i cellen sprids till resten av det reaktiva programmet. Detta gör det möjligt för den icke-reaktiva delen av programmet att utföra en traditionell mutation samtidigt som den gör det möjligt för reaktiv kod att vara medveten om och svara på denna uppdatering, vilket bibehåller konsistensen i förhållandet mellan värden i programmet. Ett exempel på ett reaktivt språk som tillhandahåller en sådan cell är FrTime.
  • Korrekt inkapslade objektorienterade bibliotek erbjuder en inkapslad föreställning om tillstånd. I princip är det därför möjligt för ett sådant bibliotek att interagera smidigt med den reaktiva delen av ett språk. Till exempel kan callbacks installeras i det objektorienterade bibliotekets getters för att meddela den reaktiva uppdateringsmotorn om tillståndsändringar, och ändringar i den reaktiva komponenten kan skjutas till det objektorienterade biblioteket genom getters. FrTime använder en sådan strategi.

Dynamisk uppdatering av grafen över beroenden

I vissa reaktiva språk är grafen över beroenden statisk , dvs grafen är fixerad under programmets körning. På andra språk kan grafen vara dynamisk , dvs den kan ändras när programmet körs. För ett enkelt exempel, överväg detta illustrativa exempel (där sekunder är ett reaktivt värde):

t = if ((sekunders mod 2) == 0): sekunder + 1 annat: sekunder - 1 slut t + 1

Varje sekund ändras värdet på detta uttryck till ett annat reaktivt uttryck, vilket t + 1 sedan beror på. Därför uppdateras grafen över beroenden varje sekund.

Att tillåta dynamisk uppdatering av beroenden ger betydande uttryckskraft (till exempel förekommer dynamiska beroenden rutinmässigt i program för grafiskt användargränssnitt (GUI). Den reaktiva uppdateringsmotorn måste dock bestämma om uttryck ska rekonstrueras varje gång, eller om ett uttrycks nod ska vara konstruerad men inaktiv; i det senare fallet, se till att de inte deltar i beräkningen när de inte ska vara aktiva.

Begrepp

Grader av explicithet

Reaktiva programmeringsspråk kan sträcka sig från mycket explicita där dataflöden ställs in med hjälp av pilar, till implicita där dataflödena härrör från språkkonstruktioner som liknar de för imperativ eller funktionell programmering. Till exempel, i implicit upplyft funktionell reaktiv programmering (FRP) kan ett funktionsanrop implicit orsaka att en nod i ett dataflödesdiagram konstrueras. Reaktiva programmeringsbibliotek för dynamiska språk (som Lisp "Cells" och Python "Trellis" biblioteken) kan konstruera en beroendegraf från runtime-analys av de värden som läses under en funktions exekvering, vilket gör att dataflödesspecifikationer kan vara både implicita och dynamiska.

Ibland syftar termen reaktiv programmering på den arkitektoniska nivån av mjukvaruteknik, där enskilda noder i dataflödesgrafen är vanliga program som kommunicerar med varandra.

Statisk eller dynamisk

Reaktiv programmering kan vara rent statisk där dataflödena ställs upp statiskt, eller vara dynamiska där dataflödena kan ändras under exekvering av ett program.

Användningen av dataswitchar i dataflödesgrafen skulle i viss mån kunna få en statisk dataflödesgraf att framstå som dynamisk och att distinktionen suddas ut något. Äkta dynamisk reaktiv programmering skulle dock kunna använda imperativ programmering för att rekonstruera dataflödesdiagrammet.

Reaktiv programmering av högre ordning

Reaktiv programmering skulle kunna sägas vara av högre ordning om den stödjer tanken att dataflöden skulle kunna användas för att konstruera andra dataflöden. Det vill säga det resulterande värdet av ett dataflöde är ett annat dataflödesdiagram som exekveras med samma utvärderingsmodell som den första.

Dataflödesdifferentiering

Helst sprids alla dataändringar omedelbart, men detta kan inte garanteras i praktiken. Istället kan det vara nödvändigt att ge olika delar av dataflödesdiagrammet olika utvärderingsprioriteringar. Detta kan kallas differentierad reaktiv programmering .

Till exempel, i en ordbehandlare behöver markeringen av stavfel inte vara helt synkroniserad med infogningen av tecken. Här kan differentierad reaktiv programmering potentiellt användas för att ge stavningskontrollen lägre prioritet, vilket gör att den kan försenas samtidigt som andra dataflöden hålls omedelbara.

En sådan differentiering introducerar dock ytterligare designkomplexitet. Till exempel att bestämma hur de olika dataflödesområdena ska definieras och hur man hanterar händelseöverföring mellan olika dataflödesområden.

Utvärderingsmodeller för reaktiv programmering

Utvärdering av reaktiva program baseras inte nödvändigtvis på hur stackbaserade programmeringsspråk utvärderas. Istället, när vissa data ändras, sprids ändringen till all data som härrör helt eller delvis från den data som ändrades. Denna förändringsförökning skulle kunna uppnås på ett antal sätt, där det kanske mest naturliga sättet är ett ogiltigförklarande/lat-återvalideringsschema.

Det kan vara problematiskt att helt enkelt naivt sprida en förändring med en stack, på grund av potentiell exponentiell uppdateringskomplexitet om datastrukturen har en viss form. En sådan form kan beskrivas som "upprepad diamantform" och har följande struktur: A n →B n →A n+1 , An →C n A n + 1 , där n = 1,2 ... Detta problem kan lösas genom att sprida ogiltigförklaring endast när vissa data inte redan är ogiltigförklarade, och senare omvalidera data när det behövs med hjälp av lat utvärdering .

Ett inneboende problem för reaktiv programmering är att de flesta beräkningar som skulle utvärderas och glömmas bort i ett normalt programmeringsspråk, behöver representeras i minnet som datastrukturer. [ citat behövs ] Detta kan potentiellt göra reaktiv programmering mycket minneskrävande. Men forskning om vad som kallas sänkning skulle potentiellt kunna övervinna detta problem.

Å andra sidan är reaktiv programmering en form av vad som skulle kunna beskrivas som "explicit parallellism" [ citat behövs ] , och kan därför vara fördelaktigt för att utnyttja kraften hos parallell hårdvara.

Likheter med observatörsmönster

Reaktiv programmering har huvudsakliga likheter med det observatörsmönster som vanligtvis används i objektorienterad programmering . Att integrera dataflödeskoncepten i programmeringsspråket skulle dock göra det lättare att uttrycka dem och skulle därför kunna öka dataflödesgrafens granularitet. Till exempel beskriver observatörsmönstret vanligtvis dataflöden mellan hela objekt/klasser, medan objektorienterad reaktiv programmering kan rikta in sig på medlemmarna av objekt/klasser.

Närmar sig

Nödvändigt

Det är möjligt att förena reaktiv programmering med vanlig imperativ programmering. I ett sådant paradigm verkar imperativa program på reaktiva datastrukturer. En sådan uppställning är analog med imperativ begränsningsprogrammering ; Men medan imperativ begränsningsprogrammering hanterar dubbelriktade dataflödesbegränsningar, hanterar imperativ reaktiv programmering enkelriktade dataflödesbegränsningar.

Objektorienterad

Objektorienterad reaktiv programmering (OORP) är en kombination av objektorienterad programmering och reaktiv programmering. Det kanske mest naturliga sättet att göra en sådan kombination är följande: istället för metoder och fält har objekt reaktioner som automatiskt omvärderas när de andra reaktionerna de är beroende av har modifierats. [ citat behövs ]

Om ett OORP-språk bibehåller sina imperativa metoder, skulle det också falla under kategorin imperativ reaktiv programmering.

Funktionell

Funktionell reaktiv programmering (FRP) är ett programmeringsparadigm för reaktiv programmering på funktionell programmering .

Skådespelare baserad

Aktörer har föreslagits att designa reaktiva system, ofta i kombination med funktionell reaktiv programmering (FRP) för att utveckla distribuerade reaktiva system.

Regelbaserad

En relativt ny kategori av programmeringsspråk använder begränsningar (regler) som huvudprogrammeringskoncept. Den består av reaktioner på händelser, som håller alla begränsningar uppfyllda. Detta underlättar inte bara händelsebaserade reaktioner, utan det gör även reaktiva program avgörande för programvarans korrekthet. Ett exempel på ett regelbaserat reaktivt programmeringsspråk är Ampersand, som är grundat i relation algebra .

Genomföranden

Se även

  1. ^ Spaljé, Model-view-controller och observatörsmönster , Tele-gemenskap .
  2. ^ "Bädda in dynamiskt dataflöde i ett Call-by-Value-språk" . cs.brown.edu . Hämtad 2016-10-09 .
  3. ^ "Crossing State Lines: Anpassa objektorienterade ramar till funktionella reaktiva språk" . cs.brown.edu . Hämtad 2016-10-09 .
  4. ^ "Reaktiv programmering - Konsten att tjäna | IT-ledningsguiden" . theartofservice.com . Hämtad 2016-07-02 .
  5. ^ Burchett, Kimberley; Cooper, Gregory H; Krishnamurthi, Shriram, "Lowering: a static optimization technique for transparent functional reactivity", Proceedings of the 2007 ACM SIGPLAN symposium on Partial evaluation 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 oriented programming system languages ​​and applications , Oopsla '11, s. 407–26, doi : 10.1105/6.0208 6.0208 6.0208 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) , vol. 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 8th 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), "Relation Algebra as programming language using the Ampersand compiler", Journal of Logical and Algebraic Methods in Programming , vol. 100, s. 113–29, doi : 10.1016/j.jlamp.2018.04.002 , S2CID 52932824 .

externa länkar