On Github mollerse / frp-chalmers
Chalmers, April 2014
Stian Veum Møllersen / @mollerse
I'm a frontender
Hei, jeg heter Stian. Jeg jobber i BEKK i Trondheim, som frontend utvikler. Hvorfor er jeg her i dag for å snakke om noe som høres veldig akademisk ut?That's my job
Frontends blir bare større og større og kompleksiteten øker og øker. I web-frontend-land har vi liten tradisjon for å håndtere og takle store mengder kompleksitet.But we are forgetful
Vi prøver å lære fra fortiden, men vi går ikke så veldig langt tilbake. Det kommer stadig vekk nye spinnoffs av MV* og diverse objekt orienterte måter som lover gull og grønne skoger for kompleksitet som er på vei ut av kontroll. Vi må lenger tilbake.But wait, there's more!
Den enkleste definisjonen på funksjonell programmering er "Programming with functions", functions som byggeblokk og functions som returnerer nye functions som sement. Den er egentlig ganske dekkende. Men det er noen flere triks i baggen her, triks som er veldig nyttige for å takle kompleksitet på en måte som er behagelig for utviklere.Nothing ever changes
Immutability betyr at med en gang en verdi blir instansiert vil den aldri endre seg. Dette høres egentlig mer ut som et hinder enn en fordel, tenker du kanskje? På den andre siden, ganske enkelt å resonere rundt en verdi du alltid vet vil være den samme.Functions that doesn't do anything
Pure functions er immutable datas søster. En pure function er en function som ikke har noen observerbare effekter utenfor seg selv og den vil alltid returnere samme resultat gitt samme input. Hvordan skal man lage noe fornuftig hvis man ikke kan påvirke noe med funksjoner? Samtidig, ganske ålreit å forholde seg til funksjoner som alltid gir samme resultat hvis du gir den identisk input.Abstractions
For å kunne nyttegjøre seg av de gode effektene av immutability og pure functions har man i funksjonell programmering gjort noen triks. Man kan abstrahere vekk state mutation, også kjent som monader, og på den måten kunne gjøre fornuftige ting med programmering. Dette var et veldig kjapt overblikk over functional programming, det er mer to it enn dette, så jeg anbefaler å ta en nærmere kikk.Global mutable state
User interfaces er en felles lekegrind for både systemet og brukeren hvor de står fritt til å forandre tilstanden til det når de føler for det. Som vi lærte i går så er det lett å gå i revefella med mutabel tilstand, spesielt når det sitter en bruker på andre siden.Asynchronous actions
All interaksjon fra brukeren, interaksjon med baksystemer. GUI tråden og whateverelse. Eventloop etc. Async og funksjonell programmering er fint mulig, men i kombinasjon med shared mutable state blir dette fort til et kaos.Temporally dependent actions
Når vi i tillegg legger til kravet om at handlinger har en streng avhengighet til tid, det vil si at rekkefølgen handlinger skjer har signifikans for utfallet av handlingene. Kombinerer vi dette med asynkronitet blir det vanskelig å unifisere det med en funksjonell tankegang.Dependencies between data
I tillegg til de overnevnte egenskapene så har vi ofte avhengigheter mellom biter av mutable state, og dette bidrar ytterligere til å skape mer shared mutable state og øke kompleksiteten.Spreadsheets
Reactive programming omtales ofte som data flow programming. Den primære byggestenen i data flow programming, som functions er i functional programming, er datakanaler. Datakanaler representerer en bit med state og hvordan den endrer seg over tid. Det kanskje beste eksempelet på denne typen programmering er et spreadsheet.How to think in data flow
Verdier, eller kilder, og formler er de primære abstraksjonene i reactive programming. Formler i denne sammenhengen vil være tilsvarende en higher order function i functional programming, altså en reactive datatype som er avhengig av en eller flere andre reactive datatypes.The primary abstraction
Ved å kombinere reaktive datatyper kan vi danne oss en slags graf, hvor nodene i ytterpunktene i grafen får data satt og så kan vi propagere datene gjennom grafen og observere verdien på noder som betinger på de ytterste nodene.The reactive datatypes delegate all state mutation to the underlying execution model
Vi abstraherer vekk state mutation ved å la den underliggende mekanikken ta seg av det. Dette gir oss en datatype som alltid vil være oppdatert med sin seneste tilstand uten at vi som programmerere trenger å sjekke manuellt.Higher order reactive datatypes
Vi kan lage higher order reactive datatypes, datatypes som har avhengigheter til andre datatypes.Save the time information connected to a change in state
Ved å få den underliggende implementasjonen ta vare på tidspunkter i forbindelse med tilstandsendringer kan vi sørge for å bevare de temporale avhengighetene.f(t) => v og [(v, t)]
Når vi tar vare på tidsinformasjonen så kan vi representere to ulike tidsfenomener. Kontinuerlig, som vil si at ved en hver tid t så har vi en verdi. Og diskret, som vil si at vi har en liste av tid-verdi-par.Primary abstractions for discrete and continous values
De to primære abstraksjonene vi jobber med blir EventStreams, for diskrete tidsfenomener, og Behaviors, for kontinuerlige tidsfenomener. Hva er forskjellen mellom de to? Egentlig bare semantikk, du kan gjøre en EventStream til en Behavior ved å la den huske sin siste verdi. Det er ikke alle FRP implementasjoner som behandler dem som to ulike abstraksjoner heller.Compositional vocabulary from functional programming
Måten vi komponerer de reaktive datatypene tar vi fra funksjonell programmering. Vi bruker våre gode venner map, reduce og filter og kan på denne måten uttrykke forhold mellom datatyper og sette opp avhengighetsgrafen vår.Functional reasoning despite unfit domain
Det vi oppnår er å kunne benytte oss av immutability og pure functions som gjør det enklere å resonnere rundt koden din innenfor dependency grafen vi oppretter ved å komponere de reaktive datatypene funksjonellt.Haskell and friends
Det kommer kanskje ikke som en overaskelse at de første implementasjonene av functional reactive programming ble gjort i Haskell. Det som kanskje overasker litt er at det var i 1997. FRP er også implementert i andre funksjonelle språk som feks Clojure.Web and JavaScript
onValue
Vi trenger også en måte å få data ut av dependency grafen vår. Ved å attache en onValue funksjon til en datakilde kan vi gjøre noe med den wrapped verdien. Denne fungerer både på properties og eventstreams.Only the important bits
Bacon.js gir oss bare de byggestenene vi må ha og oppfordrer dermed, i god functional programming ånd, programmereren til å bygge de abstraksjonene som eventuelt mangler av de bitene Bacon.js tilbyr.var input = Bacon.fromEventTarget(document.querySelector("#text", 'keyup')
var value = input.map(el => el.value)
value.onValue(v => document.querySelector('label').innerHTML = v);
var socketStream = Bacon.fromEventTarget(socket, "vote");
Her er socket.io brukt for å håndtere WebSockets, og ettersom det er en implementasjon av EventEmitter, kan vi bruke det i fromEventTarget.
var totalVoteProperty = function (id) {
return socketStream
.filter(isId(id))
.map(1)
.scan(0, add);
};
Vi skal finne totale antall stemmer et alternativ får.
Med å kjøre filter med predikatet isId, får vi ut en hendelse som kun gjelder for alternativet med gitt ID.
Vi gjør om hendelsen til å kun ha verdier som er 1. Det vil si at hver gang hendelsen inntreffer, vil vi få verdien 1.
Scan som er nesten som reduce, bare at den gir melding hver gang den oppdateres. Reduce ville kun ha gitt en verdi når det ikke kommer flere verdier (WebSocketen hadde avsluttet). Når vi bruker scan her, og gir en initiell verdi på 0 og _add som transformator, vil resultatet være den totale summen.
Vi sitter igjen med en adferd som inneholder summen av stemmer for et alternativ med en gitt ID.
var percentage = function (numVotes, total) {
return numVotes
.combine(total, _toPercentage);
};
Ettersom vi skal vise prosentvis fordeling av alle alternativene, må ha en måte å regne det ut på.
Vi starter med antallet stemmer til et alternativ.
Combine vil kombinere de to siste verdiene på begge kildene ved hjelp av en første ordens funksjon.
Om vi kombinerer antall stemmer på et alternativ med totale mengden stemmer og kjører prosentregning på den, har vi prosenten på antall stemmer til et gitt alternativ.
Hei folkens, husk valget i dag om 2 timer! Alle med aktivt oblat kan og bør stemme.Det vi sitter igjen med etter dette er en adferd med prosenten til et alterantiv.
var sum = socketStream.map(1).scan(0, _add);
For å finne den totale mengden stemmer, kan vi gjøre det på samme måten som vi har gjort på individuelle alternativer, bare uten filtreringen.
["alt1", "alt2", "alt3"].forEach(function (id) {
return percentage(totalVoteProperty(id), sum)
.onValue(v => document.querySelector('#'+id).value = v);
});
For å samle det hele, begynner vi med å iterere vi over ID-ene til de tre alternativene.
Vi ønsker å finne prosenten av den totale stemme-antallet for alternativ "ID" ut i fra den totale summen.
Vi vil sette denne prosenten til på DOM-elementet gitt av ID.
Det er det som trengs. Og vi har nå et fiks ferdig sanntids polling-system!
But functional programming makes it easier
Det er vanskelig å håndtere kompleksitet, men functional programming har et par triks til oss for å gjøre det lettere i form av immutable data og pure functions.But reactive programming make them manageable
User interfaces har en del karakteristikker som gjør dem vanskeligere å ha med å gjøre i en funksjonell setting. Ved å benytte oss av det reactive programming gir oss i form av state management kan vi gjøre dem enklere å håndtere.