clojure reactive programming - programmer books€¦ · clojure and clojurescript to help build...

354

Upload: others

Post on 27-May-2020

30 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 2: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 3: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ClojureReactiveProgramming

Page 4: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

TableofContents

ClojureReactiveProgramming

Credits

AbouttheAuthor

Acknowledgments

AbouttheReviewers

www.PacktPub.com

Supportfiles,eBooks,discountoffers,andmore

Whysubscribe?

FreeaccessforPacktaccountholders

Preface

Whatthisbookcovers

Whatyouneedforthisbook

Whothisbookisfor

Conventions

Readerfeedback

Customersupport

Downloadingtheexamplecode

Errata

Piracy

Questions

1.WhatisReactiveProgramming?

AtasteofReactiveProgramming

Creatingtime

Morecolors

Makingitreactive

Exercise1.1

Abitofhistory

Dataflowprogramming

Object-orientedReactiveProgramming

Page 5: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Themostwidelyusedreactiveprogram

TheObserverdesignpattern

FunctionalReactiveProgramming

Higher-orderFRP

Signalsandevents

Implementationchallenges

First-orderFRP

Asynchronousdataflow

ArrowizedFRP

ApplicationsofFRP

Asynchronousprogrammingandnetworking

ComplexGUIsandanimations

Summary

2.ALookatReactiveExtensions

TheObserverpatternrevisited

Observer–anIterator’sdual

CreatingObservables

CustomObservables

ManipulatingObservables

Flatmapandfriends

Onemoreflatmapfortheroad

Errorhandling

OnError

Catch

Retry

Backpressure

Sample

Backpressurestrategies

Summary

3.AsynchronousProgrammingandNetworking

Buildingastockmarketmonitoringapplication

Page 6: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Rollingaverages

Identifyingproblemswithourcurrentapproach

RemovingincidentalcomplexitywithRxClojure

Observablerollingaverages

Summary

4.Introductiontocore.async

Asynchronousprogrammingandconcurrency

core.async

Communicatingsequentialprocesses

Rewritingthestockmarketapplicationwithcore.async

Implementingtheapplicationcode

Errorhandling

Backpressure

Fixedbuffer

Droppingbuffer

Slidingbuffer

Transducers

Transducersandcore.async

Summary

5.CreatingYourOwnCESFrameworkwithcore.async

AminimalCESframework

ClojureorClojureScript?

DesigningthepublicAPI

Implementingtokens

Implementingeventstreams

Implementingbehaviors

Exercises

Exercise5.1

Exercise5.2

Arespondentapplication

CESversuscore.async

Page 7: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Summary

6.BuildingaSimpleClojureScriptGamewithReagi

Settinguptheproject

Gameentities

Puttingitalltogether

Modelinguserinputaseventstreams

Workingwiththeactivekeysstream

ReagiandotherCESframeworks

Summary

7.TheUIasaFunction

TheproblemwithcomplexwebUIs

EnterReact.js

Lessonsfromfunctionalprogramming

ClojureScriptandOm

BuildingasimpleContactsapplicationwithOm

TheContactsapplicationstate

SettinguptheContactsproject

Applicationcomponents

Omcursors

Fillingintheblanks

Intercomponentcommunication

CreatinganagileboardwithOm

Theboardstate

Componentsoverview

Lifecycleandcomponentlocalstate

Remainingcomponents

Utilityfunctions

Exercises

Summary

8.Futures

Clojurefutures

Page 8: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Fetchingdatainparallel

Imminent–acomposablefutureslibraryforClojure

Creatingfutures

Combinatorsandeventhandlers

Themoviesexamplerevisited

FuturesandblockingIO

Summary

9.AReactiveAPItoAmazonWebServices

Theproblem

Infrastructureautomation

AWSresourcesdashboard

CloudFormation

ThedescribeStacksendpoint

ThedescribeStackResourcesendpoint

EC2

ThedescribeInstancesendpoint

RDS

ThedescribeDBInstancesendpoint

Designingthesolution

RunningtheAWSstubserver

Settingupthedashboardproject

CreatingAWSObservables

CombiningtheAWSObservables

Puttingitalltogether

Exercises

Summary

A.TheAlgebraofLibraryDesign

Thesemanticsofmap

Functors

TheOptionFunctor

Findingtheaverageofages

Page 9: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ApplicativeFunctors

Gatheringstatsaboutages

Monads

Summary

B.Bibliography

Index

Page 10: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 11: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ClojureReactiveProgramming

Page 12: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 13: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ClojureReactiveProgrammingCopyright©2015PacktPublishing

Allrightsreserved.Nopartofthisbookmaybereproduced,storedinaretrievalsystem,ortransmittedinanyformorbyanymeans,withoutthepriorwrittenpermissionofthepublisher,exceptinthecaseofbriefquotationsembeddedincriticalarticlesorreviews.

Everyefforthasbeenmadeinthepreparationofthisbooktoensuretheaccuracyoftheinformationpresented.However,theinformationcontainedinthisbookissoldwithoutwarranty,eitherexpressorimplied.Neithertheauthor,norPacktPublishing,anditsdealersanddistributorswillbeheldliableforanydamagescausedorallegedtobecauseddirectlyorindirectlybythisbook.

PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthecompaniesandproductsmentionedinthisbookbytheappropriateuseofcapitals.However,PacktPublishingcannotguaranteetheaccuracyofthisinformation.

Firstpublished:March2015

Productionreference:1160315

PublishedbyPacktPublishingLtd.

LiveryPlace

35LiveryStreet

BirminghamB32PB,UK.

ISBN978-1-78398-666-8

www.packtpub.com

Page 14: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 15: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

CreditsAuthor

LeonardoBorges

Reviewers

EduardBondarenko

ColinJones

MichaelKohl

FalkoRiemenschneider

AcquisitionEditor

HarshaBharwani

ContentDevelopmentEditor

ArunNadar

TechnicalEditor

TanviBhatt

CopyEditors

VikrantPhadke

SameenSiddiqui

ProjectCoordinator

NehaBhatnagar

Proofreaders

SimranBhogal

MariaGould

Indexer

MariammalChettiyar

Graphics

AbhinashSahu

ProductionCoordinator

ManuJoseph

CoverWork

ManuJoseph

Page 16: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 17: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

AbouttheAuthorLeonardoBorgesisaprogramminglanguagesenthusiastwholoveswritingcode,contributingtoopensourcesoftware,andspeakingonsubjectshefeelsstronglyabout.Afternearly5yearsofconsultingatThoughtWorks,whereheworkedintwocommercialClojureprojects,amongmanyothers,heisnowasoftwareengineeratAtlassian.HeusesClojureandClojureScripttohelpbuildreal-timecollaborativeeditingtechnology.Thisishisfirstfull-lengthbook,buthecontributedacoupleofchapterstoClojureCookbook,O’Reilly.

LeonardohasfoundedandrunstheSydneyClojureUserGroupinAustralia.Healsowritespostsaboutsoftware,focusingonfunctionalprogramming,onhiswebsite(http://www.leonardoborges.com).Whenheisn’twritingcode,heenjoysridingmotorcycles,weightlifting,andplayingtheguitar.

Page 18: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 19: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

AcknowledgmentsIwouldliketotakethisopportunityandstartbythankingmyfamily:mygrandparents,AltamirandAlba,fortheirtirelesssupport;mymother,Sônia,forherunconditionalloveandmotivation;andmyuncle,AltamirFilho,forsupportingmewhenIdecidedtogotoschoolatnightsothatIcouldstartworkingasaprogrammer.Withoutthem,Iwouldhaveneverpursuedsoftwareengineering.

Iwouldalsoliketothankmyfiancee,Enif,whoansweredwitharesounding“yes”whenaskedwhetherIshouldtakeupthechallengeofwritingabook.Herpatience,love,support,andwordsofencouragementwereinvaluable.

Duringthewritingprocess,PacktPublishinginvolvedseveralreviewersandtheirfeedbackwasextremelyusefulinmakingthisabetterbook.Tothesereviewers,thankyou.

Iamalsosincerelygratefulformyfriendswhoprovidedcrucialfeedbackonkeychapters,encouragingmeateverystepoftheway:ClaudioNatoli,FábioLessa,FabioPereira,JulianGamble,SteveBuikhuizen,andmanyothers,whowouldtakemultiplepagestolist.

Lastbutnotleast,awarmthankstothestaffatPacktPublishing,whohelpedmealongthewholeprocess,beingfirmandresponsible,yetunderstanding.

Eachofyouhelpedmakethishappen.Thankyou!

Page 20: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 21: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

AbouttheReviewersEduardBondarenkoisasoftwaredeveloperlivinginKiev,Ukraine.HestartedprogrammingusingBasiconZXSpectrumalongtimeago.Later,heworkedinthewebdevelopmentdomain.

HehasusedRubyonRailsforabout8years.HavingusedRubyforalongtime,hediscoveredClojureinearly2009,andlikedthelanguage.BesidesRubyandClojure,heisinterestedinErlang,Go,Scala,andHaskelldevelopment.

ColinJonesisdirectorofsoftwareservicesat8thLight,wherehebuildsweb,mobile,anddesktopsystemsforclientsofallsizes.He’stheauthorofMasteringClojureMacros:WriteCleaner,Faster,SmarterCode,PragmaticBookshelf.ColinparticipatesactivelyintheClojureopensourcecommunity,includingworkontheClojureKoans,REPLy,leiningen,andmakessmallcontributionstoClojureitself.

MichaelKohlhasbeendevelopingwithRubysince2004andgotacquaintedwithClojurein2009.Hehasworkedasasystemsadministrator,journalist,systemsengineer,Germanteacher,softwaredeveloper,andpenetrationtester.HecurrentlymakeshislivingasaseniorRubyonRailsdeveloper.HepreviouslyworkedwithPacktPublishingasatechnicalreviewerforRubyandMongoDBWebDevelopmentBeginner’sGuide.

FalkoRiemenschneiderstartedprogrammingin1989.Inthelast15years,hehasworkedonnumerousJavaEnterprisesoftwareprojectsforbackendsaswellasfrontends.He’sespeciallyinterestedindesigningcomplexrich-userinterfaces.In2012,henoticedandlearnedClojure.HequicklycameincontactwithideassuchasFRPandCSPthatshowgreatpotentialforaradicallysimplerUIarchitecturefordesktopandin-browserclients.

Falkoworksforitemis,aGermany-basedsoftwareconsultancyfirmwithstrongcompetenceforlanguage-andmodel-basedsoftwaredevelopment.HecofoundedaClojureusergroup,andencouragesotherdeveloperswithinandoutsideitemistolearnfunctionalprogramming.

Falkopostsregularlyonhttp://www.falkoriemenschneider.de.

Page 22: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 23: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

www.PacktPub.com

Page 24: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Supportfiles,eBooks,discountoffers,andmoreForsupportfilesanddownloadsrelatedtoyourbook,pleasevisitwww.PacktPub.com.

DidyouknowthatPacktofferseBookversionsofeverybookpublished,withPDFandePubfilesavailable?YoucanupgradetotheeBookversionatwww.PacktPub.comandasaprintbookcustomer,youareentitledtoadiscountontheeBookcopy.Getintouchwithusat<[email protected]>formoredetails.

Atwww.PacktPub.com,youcanalsoreadacollectionoffreetechnicalarticles,signupforarangeoffreenewslettersandreceiveexclusivediscountsandoffersonPacktbooksandeBooks.

https://www2.packtpub.com/books/subscription/packtlib

DoyouneedinstantsolutionstoyourITquestions?PacktLibisPackt’sonlinedigitalbooklibrary.Here,youcansearch,access,andreadPackt’sentirelibraryofbooks.

Page 25: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Whysubscribe?FullysearchableacrosseverybookpublishedbyPacktCopyandpaste,print,andbookmarkcontentOndemandandaccessibleviaawebbrowser

Page 26: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

FreeaccessforPacktaccountholdersIfyouhaveanaccountwithPacktatwww.PacktPub.com,youcanusethistoaccessPacktLibtodayandview9entirelyfreebooks.Simplyuseyourlogincredentialsforimmediateaccess.

Page 27: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 28: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

PrefaceHighlyconcurrentapplicationssuchasuserinterfaceshavetraditionallymanagedstatethroughthemutationofglobalvariables.Variousactionsarecoordinatedviaeventhandlers,whichareproceduralinnature.

Overtime,thecomplexityofasystemincreases.Newfeaturerequestscomein,anditbecomesharderandhardertoreasonabouttheapplication.

Functionalprogrammingpresentsitselfasanextremelypowerfulallyinbuildingreliablesystemsbyeliminatingmutablestatesandallowingapplicationstobewritteninadeclarativeandcomposableway.

SuchprinciplesgaverisetoFunctionalReactiveProgrammingandCompositionalEventSystems(CES),programmingparadigmsthatareexceptionallyusefulinbuildingasynchronousandconcurrentapplications.Theyallowyoutomodelmutablestatesinafunctionalstyle.

Thisbookisdevotedtotheseideasandpresentsanumberofdifferenttoolsandtechniquestohelpmanagetheincreasingcomplexityofmodernsystems.

Page 29: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

WhatthisbookcoversChapter1,WhatisReactiveProgramming?,startsbyguidingyouthroughacompellingexampleofareactiveapplicationwritteninClojureScript.ItthentakesyouonajourneythroughthehistoryofReactiveProgramming,duringwhichsomeimportantterminologyisintroduced,settingthetoneforthefollowingchapters.

Chapter2,ALookatReactiveExtensions,exploresthebasicsofthisReactiveProgrammingframework.Itsabstractionsareintroducedandimportantsubjectssuchaserrorhandlingandbackpressurearediscussed.

Chapter3,AsynchronousProgrammingandNetworking,walksyouthroughbuildingastockmarketapplication.ItstartsbyusingamoretraditionalapproachandthenswitchestoanimplementationbasedonReactiveExtensions,examiningthetrade-offsbetweenthetwo.

Chapter4,Introductiontocore.async,describescore.async,alibraryforasynchronousprogramminginClojure.Here,youlearnaboutthebuildingblocksofCommunicatingSequentialProcessesandhowReactiveApplicationsarebuiltwithcore.async.

Chapter5,CreatingYourOwnCESFrameworkwithcore.async,embarksontheambitiousendeavorofbuildingaCESframework.Itleveragesknowledgegainedinthepreviouschapterandusescore.asyncasthefoundationfortheframework.

Chapter6,BuildingaSimpleClojureScriptGamewithReagi,showcasesadomainwhereReactiveframeworkshavebeenusedforgreateffectsingamesdevelopment.

Chapter7,TheUIasaFunction,shiftsgearsandshowshowtheprinciplesoffunctionalprogrammingcanbeappliedtowebUIdevelopmentthroughthelensofOm,aClojureScriptbindingforFacebook’sReact.

Chapter8,Futures,presentsfuturesasaviablealternativetosomeclasses’reactiveapplications.ItexaminesthelimitationsofClojurefuturesandpresentsanalternative:imminent,alibraryofcomposablefuturesforClojure.

Chapter9,AReactiveAPItoAmazonWebServices,describesacasestudytakenfromarealproject,wherealotoftheconceptsintroducedthroughoutthisbookhavebeenputtogethertointeractwithathird-partyservice.

AppendixA,TheAlgebraofLibraryDesign,introducesconceptsfromCategoryTheorythatarehelpfulinbuildingreusableabstractions.Theappendixisoptionalandwon’thinderlearninginthepreviouschapters.ItpresentstheprinciplesusedindesigningthefutureslibraryseeninChapter8,Futures.

AppendixB,Bibliography,providesallthereferencesusedthroughoutthebook.

Page 30: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 31: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

WhatyouneedforthisbookThisbookassumesthatyouhaveareasonablymoderndesktoporlaptopcomputeraswellasaworkingClojureenvironmentwithleiningen(seehttp://leiningen.org/)properlyconfigured.

Installationinstructionsdependonyourplatformandcanbefoundontheleiningenwebsite(seehttp://leiningen.org/#install).

Youarefreetouseanytexteditorofyourchoice,butpopularchoicesincludeEclipse(seehttps://eclipse.org/downloads/)withtheCounterclockwiseplugin(seehttps://github.com/laurentpetit/ccw),IntelliJ(https://www.jetbrains.com/idea/)withtheCursiveplugin(seehttps://cursiveclojure.com/),LightTable(seehttp://lighttable.com/),Emacs,andVim.

Page 32: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 33: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

WhothisbookisforThisbookisforClojuredeveloperswhoarecurrentlybuildingorplanningtobuildasynchronousandconcurrentapplicationsandwhoareinterestedinhowtheycanapplytheprinciplesandtoolsofReactiveProgrammingtotheirdailyjobs.

KnowledgeofClojureandleiningen—apopularClojurebuildtool—isrequired.

ThebookalsofeaturesseveralClojureScriptexamples,andassuch,familiaritywithClojureScriptandwebdevelopmentingeneralwillbehelpful.

Notwithstanding,thechaptershavebeencarefullywritteninsuchawaythataslongasyoupossessknowledgeofClojure,followingtheseexamplesshouldonlyrequirealittleextraeffort.

Asthisbookprogresses,itlaysoutthebuildingblocksrequiredbylaterchapters,andassuchmyrecommendationisthatyoustartwithChapter1,WhatisReactiveProgramming?,andworkyourwaythroughsubsequentchaptersinorder.

AclearexceptiontothisisAppendixA,TheAlgebraofLibraryDesign,whichisoptionalandcanbereadindependentoftheothers—althoughreadingChapter8,Futures,mightprovideausefulbackground.

Page 34: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 35: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ConventionsInthisbook,youwillfindanumberofstylesoftextthatdistinguishbetweendifferentkindsofinformation.Herearesomeexamplesofthesestyles,andanexplanationoftheirmeaning.

Codewordsintext,databasetablenames,foldernames,filenames,fileextensions,pathnames,dummyURLs,userinput,andTwitterhandlesareshownasfollows:“Wecanincludeothercontextsthroughtheuseoftheincludedirective.”

Ablockofcodeissetasfollows:

(defnumbers(atom[]))

(defnadder[keyrefold-statenew-state]

(print"Currentsumis"(reduce+new-state)))

(add-watchnumbers:adderadder)

Whenwewishtodrawyourattentiontoaparticularpartofacodeblock,therelevantlinesoritemsaresetinbold:

(->(repeat-obs5)

(rx/subscribeprn-to-repl))

;;5

;;5

Anycommand-lineinputoroutputiswrittenasfollows:

leinrun-msin-wave.server

Newtermsandimportantwordsareshowninbold.Wordsthatyouseeonthescreen,inmenus,ordialogboxes,forexample,appearinthetextlikethis:“IfthiswasawebapplicationouruserswouldbepresentedwithawebservererrorsuchastheHTTPcode500–InternalServerError.”

NoteWarningsorimportantnotesappearinaboxlikethis.

TipTipsandtricksappearlikethis.

Page 36: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 37: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ReaderfeedbackFeedbackfromourreadersisalwayswelcome.Letusknowwhatyouthinkaboutthisbook—whatyoulikedormayhavedisliked.Readerfeedbackisimportantforustodeveloptitlesthatyoureallygetthemostoutof.

Tosendusgeneralfeedback,simplysendane-mailto<[email protected]>,andmentionthebooktitleviathesubjectofyourmessage.

Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,seeourauthorguideatwww.packtpub.com/authors.

Page 38: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 39: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

CustomersupportNowthatyouaretheproudownerofaPacktbook,wehaveanumberofthingstohelpyoutogetthemostfromyourpurchase.

Page 40: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

DownloadingtheexamplecodeYoucandownloadtheexamplecodefilesforallPacktbooksyouhavepurchasedfromyouraccountathttp://www.packtpub.com.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.

Page 41: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ErrataAlthoughwehavetakeneverycaretoensuretheaccuracyofourcontent,mistakesdohappen.Ifyoufindamistakeinoneofourbooks—maybeamistakeinthetextorthecode—wewouldbegratefulifyouwouldreportthistous.Bydoingso,youcansaveotherreadersfromfrustrationandhelpusimprovesubsequentversionsofthisbook.Ifyoufindanyerrata,pleasereportthembyvisitinghttp://www.packtpub.com/submit-errata,selectingyourbook,clickingontheerratasubmissionformlink,andenteringthedetailsofyourerrata.Onceyourerrataareverified,yoursubmissionwillbeacceptedandtheerratawillbeuploadedonourwebsite,oraddedtoanylistofexistingerrata,undertheErratasectionofthattitle.Anyexistingerratacanbeviewedbyselectingyourtitlefromhttp://www.packtpub.com/support.

Page 42: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

PiracyPiracyofcopyrightmaterialontheInternetisanongoingproblemacrossallmedia.AtPackt,wetaketheprotectionofourcopyrightandlicensesveryseriously.Ifyoucomeacrossanyillegalcopiesofourworks,inanyform,ontheInternet,pleaseprovideuswiththelocationaddressorwebsitenameimmediatelysothatwecanpursuearemedy.

Pleasecontactusat<[email protected]>withalinktothesuspectedpiratedmaterial.

Weappreciateyourhelpinprotectingourauthors,andourabilitytobringyouvaluablecontent.

Page 43: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

QuestionsYoucancontactusat<[email protected]>ifyouarehavingaproblemwithanyaspectofthisbook,andwewilldoourbesttoaddressit.

Page 44: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 45: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Chapter1.WhatisReactiveProgramming?ReactiveProgrammingisbothanoverloadedtermandabroadtopic.Assuch,thisbookwillfocusonaspecificformulationofReactiveProgrammingcalledCompositionalEventSystems(CES).

BeforecoveringsomehistoryandbackgroundbehindReactiveProgrammingandCES,Iwouldliketoopenwithaworkingandhopefullycompellingexample:ananimationinwhichwedrawasinewaveontoawebpage.

Thesinewaveissimplythegraphrepresentationofthesinefunction.Itisasmooth,repetitiveoscillation,andattheendofouranimationitwilllooklikethefollowingscreenshot:

ThisexamplewillhighlighthowCES:

UrgesustothinkaboutwhatwewouldliketodoasopposedtohowEncouragessmall,specificabstractionsthatcanbecomposedtogetherProducesterseandmaintainablecodethatiseasytochange

ThecoreofthisprogramboilsdowntofourlinesofClojureScript:

(->sine-wave

(.take600)

(.subscribe(fn[{:keys[xy]}]

(fill-rectxy"orange"))))

Simplybylookingatthiscodeitisimpossibletodeterminepreciselywhatitdoes.However,dotakethetimetoreadandimaginewhatitcoulddo.

First,wehaveavariablecalledsine-wave,whichrepresentsthe2Dcoordinateswewilldrawontothewebpage.Thenextlinegivesustheintuitionthatsine-waveissomesortofcollection-likeabstraction:weuse.taketoretrieve600coordinatesfromit.

Finally,we.subscribetothis“collection”bypassingitacallback.Thiscallbackwillbecalledforeachiteminthesine-wave,finallydrawingatthegivenxandycoordinatesusingthefill-rectfunction.

Thisisquiteabittotakeinfornowaswehaven’tseenanyothercodeyet—butthatwas

Page 46: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

thepointofthislittleexercise:eventhoughweknownothingaboutthespecificsofthisexample,weareabletodevelopanintuitionofhowitmightwork.

Let’sseewhatelseisnecessarytomakethissnippetanimateasinewaveonourscreen.

Page 47: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

AtasteofReactiveProgrammingThisexampleisbuiltinClojureScriptandusesHTML5CanvasforrenderingandRxJS(seehttps://github.com/Reactive-Extensions/RxJS)—aframeworkforReactiveProgramminginJavaScript.

Beforewestart,keepinmindthatwewillnotgointothedetailsoftheseframeworksyet—thatwillhappenlaterinthisbook.ThismeansI’llbeaskingyoutotakequiteafewthingsatfacevalue,sodon’tworryifyoudon’timmediatelygrasphowthingswork.ThepurposeofthisexampleistosimplygetusstartedintheworldofReactiveProgramming.

Forthisproject,wewillbeusingChestnut(seehttps://github.com/plexus/chestnut)—aleiningentemplateforClojureScriptthatgivesusasampleworkingapplicationwecanuseasaskeleton.

Tocreateournewproject,headovertothecommandlineandinvokeleiningenasfollows:

leinnewchestnutsin-wave

cdsin-wave

Next,weneedtomodifyacoupleofthingsinthegeneratedproject.Openupsin-wave/resources/index.htmlandupdateittolooklikethefollowing:

<!DOCTYPEhtml>

<html>

<head>

<linkhref="css/style.css"rel="stylesheet"type="text/css">

</head>

<body>

<divid="app"></div>

<scriptsrc="/js/rx.all.js"type="text/javascript"></script>

<scriptsrc="/js/app.js"type="text/javascript"></script>

<canvasid="myCanvas"width="650"height="200"style="border:1pxsolid

#d3d3d3;">

</body>

</html>

ThissimplyensuresthatweimportbothourapplicationcodeandRxJS.Wehaven’tdownloadedRxJSyetsolet’sdothisnow.Browsetohttps://github.com/Reactive-Extensions/RxJS/blob/master/dist/rx.all.jsandsavethisfiletosin-wave/resources/public.TheprevioussnippetsalsoaddanHTML5Canvaselementontowhichwewillbedrawing.

Now,open/src/cljs/sin_wave/core.cljs.Thisiswhereourapplicationcodewilllive.Youcanignorewhatiscurrentlythere.Makesureyouhaveacleanslatelikethefollowingone:

(nssin-wave.core)

(defnmain[])

Finally,gobacktothecommandline—underthesin-wavefolder—andstartupthefollowingapplication:

Page 48: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

leinrun-msin-wave.server

2015-01-0219:52:34.116:INFO:oejs.Server:jetty-7.6.13.v20130916

2015-01-0219:52:34.158:INFO:oejs.AbstractConnector:Started

[email protected]:10555

Startingfigwheel.

Startingwebserveronport10555.

CompilingClojureScript.

Figwheel:Startingserverathttp://localhost:3449

Figwheel:Servingfilesfrom'(dev-resources|resources)/public'

Oncethepreviouscommandfinishes,theapplicationwillbeavailableathttp://localhost:10555,whereyouwillfindablank,rectangularcanvas.Wearenowreadytobegin.

ThemainreasonweareusingtheChestnuttemplateforthisexampleisthatitperformshot-reloadingofourapplicationcodeviawebsockets.Thismeanswecanhavethebrowserandtheeditorsidebyside,andasweupdateourcode,wewillseetheresultsimmediatelyinthebrowserwithouthavingtoreloadthepage.

Tovalidatethatthisisworking,openyourwebbrowser’sconsolesothatyoucanseetheoutputofthescriptsinthepage.Thenaddthisto/src/cljs/sin_wave/core.cljsasfollows:

(.logjs/console"helloclojurescript")

Youshouldhaveseenthehelloclojurescriptmessageprintedtoyourbrowser’sconsole.Makesureyouhaveaworkingenvironmentuptothispointaswewillberelyingonthisworkflowtointeractivelybuildourapplication.

ItisalsoagoodideatomakesureweclearthecanvaseverytimeChestnutreloadsourfile.Thisissimpleenoughtodobyaddingthefollowingsnippettoourcorenamespace:

(defcanvas(.getElementByIdjs/document"myCanvas"))

(defctx(.getContextcanvas"2d"))

;;Clearcanvasbeforedoinganythingelse

(.clearRectctx00(.-widthcanvas)(.-heightcanvas))

Page 49: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

CreatingtimeNowthatwehaveaworkingenvironment,wecanprogresswithouranimation.Itisprobablyagoodideatospecifyhowoftenwewouldliketohaveanewanimationframe.

Thiseffectivelymeansaddingtheconceptoftimetoourapplication.You’refreetoplaywithdifferentvalues,butlet’sstartwithanewframeevery10milliseconds:

(defintervaljs/Rx.Observable.interval)

(deftime(interval10))

AsRxJSisaJavaScriptlibrary,weneedtouseClojureScript’sinteroperabilitytocallitsfunctions.Forconvenience,webindtheintervalfunctionofRxJStoalocalvar.Wewillusethisapproachthroughoutthisbookwhenappropriate.

Next,wecreateaninfinitestreamofnumbers—startingat0—thatwillhaveanewelementevery10milliseconds.Let’smakesurethisisworkingasexpected:

(->time

(.take5)

(.subscribe(fn[n]

(.logjs/consolen))))

;;0

;;1

;;2

;;3

;;4

TipIusethetermstreamverylooselyhere.Itwillbedefinedmorepreciselylaterinthisbook.

Remembertimeisinfinite,soweuse.takeinordertoavoidindefinitelyprintingoutnumberstotheconsole.

Ournextstepistocalculatethe2Dcoordinaterepresentingasegmentofthesinewavewecandraw.Thiswillbegivenbythefollowingfunctions:

(defndeg-to-rad[n]

(*(/Math/PI180)n))

(defnsine-coord[x]

(let[sin(Math/sin(deg-to-radx))

y(-100(*sin90))]

{:xx

:yy

:sinsin}))

Thesine-coordfunctiontakesanxpointofour2DCanvasandcalculatestheypointbasedonthesineofx.Theconstants100and90simplycontrolhowtallandsharptheslopeshouldbe.Asanexample,trycalculatingthesinecoordinatewhenxis50:

(.logjs/console(str(sine-coord50)))

;;{:x50,:y31.05600011929198,:sin0.766044443118978}

Page 50: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Wewillbeusingtimeasthesourceforthevaluesofx.Creatingthesinewavenowisonlyamatterofcombiningbothtimeandsine-coord:

(defsine-wave

(.maptimesine-coord))

Justliketime,sine-waveisaninfinitestream.Thedifferenceisthatinsteadofjustintegers,wewillnowhavethexandycoordinatesofoursinewave,asdemonstratedinthefollowing:

(->sine-wave

(.take5)

(.subscribe(fn[xysin]

(.logjs/console(strxysin)))))

;;{:x0,:y100,:sin0}

;;{:x1,:y98.42928342064448,:sin0.01745240643728351}

;;{:x2,:y96.85904529677491,:sin0.03489949670250097}

;;{:x3,:y95.28976393813505,:sin0.052335956242943835}

;;{:x4,:y93.72191736302872,:sin0.0697564737441253}

Thisbringsustotheoriginalcodesnippetwhichpiquedourinterest,alongsideafunctiontoperformtheactualdrawing:

(defnfill-rect[xycolour]

(set!(.-fillStylectx)colour)

(.fillRectctxxy22))

(->sine-wave

(.take600)

(.subscribe(fn[{:keys[xy]}]

(fill-rectxy"orange"))))

Asthispoint,wecansavethefileagainandwatchasthesinewavewehavejustcreatedgracefullyappearsonthescreen.

Page 51: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

MorecolorsOneofthepointsthisexamplesetsouttoillustrateishowthinkingintermsofverysimpleabstractionsandthenbuildingmorecomplexonesontopofthemmakeforcodethatissimplertomaintainandeasiertomodify.

Assuch,wewillnowupdateouranimationtodrawthesinewaveindifferentcolors.Inthiscase,wewouldliketodrawthewaveinredifthesineofxisnegativeandblueotherwise.

Wealreadyhavethesinevaluecomingthroughthesine-wavestream,soallweneedtodoistotransformthisstreamintoonethatwillgiveusthecolorsaccordingtotheprecedingcriteria:

(defcolour(.mapsine-wave

(fn[{:keys[sin]}]

(if(<sin0)

"red"

"blue"))))

Thenextstepistoaddthenewstreamintothemaindrawingloop—remembertocommentthepreviousonesothatwedon’tendupwithmultiplewavesbeingdrawnatthesametime:

(->(.zipsine-wavecolour#(vector%%2))

(.take600)

(.subscribe(fn[[{:keys[xy]}colour]]

(fill-rectxycolour))))

Oncewesavethefile,weshouldseeanewsinewavealternatingbetweenredandblueasthesineofxoscillatesfrom–1to1.

Page 52: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

MakingitreactiveAsfunasthishasbeensofar,theanimationwehavecreatedisn’treallyreactive.Sure,itdoesreacttotimeitself,butthatistheverynatureofanimation.Aswewilllatersee,ReactiveProgrammingissocalledbecauseprogramsreacttoexternalinputssuchasmouseornetworkevents.

Wewill,therefore,updatetheanimationsothattheuserisincontrolofwhenthecolorswitchoccurs:thewavewillstartredandswitchtobluewhentheuserclicksanywherewithinthecanvasarea.Furtherclickswillsimplyalternatebetweenredandblue.

Westartbycreatinginfinite—asperthedefinitionoftime—streamsforourcolorprimitivesasfollows:

(defred(.maptime(fn[_]"red")))

(defblue(.maptime(fn[_]"blue")))

Ontheirown,redandbluearen’tthatinterestingastheirvaluesdon’tchange.Wecanthinkofthemasconstantstreams.Theybecomealotmoreinterestingwhencombinedwithanotherinfinitestreamthatcyclesbetweenthembasedonuserinput:

(defconcatjs/Rx.Observable.concat)

(defdeferjs/Rx.Observable.defer)

(deffrom-eventjs/Rx.Observable.fromEvent)

(defmouse-click(from-eventcanvas"click"))

(defcycle-colour

(concat(.takeUntilredmouse-click)

(defer#(concat(.takeUntilbluemouse-click)

cycle-colour))))

Thisisourmostcomplexupdatesofar.Ifyoulookclosely,youwillalsonoticethatcycle-colourisarecursivestream;thatis,itisdefinedintermsofitself.

Whenwefirstsawcodeofthisnature,wetookaleapoffaithintryingtounderstandwhatitdoes.Afteraquickread,however,werealizedthatcycle-colourfollowscloselyhowwemighthavetalkedabouttheproblem:wewillusereduntilamouseclickoccurs,afterwhichwewilluseblueuntilanothermouseclickoccurs.Then,westarttherecursion.

Thechangetoouranimationloopisminimal:

(->(.zipsine-wavecycle-colour#(vector%%2))

(.take600)

(.subscribe(fn[[{:keys[xy]}colour]]

(fill-rectxycolour))))

Thepurposeofthisbookistohelpyoudeveloptheinstinctrequiredtomodelproblemsinthewaydemonstratedhere.Aftereachchapter,moreandmoreofthisexamplewillmakesense.Additionally,anumberofframeworkswillbeusedbothinClojureScriptandClojuretogiveyouawiderangeoftoolstochoosefrom.

Page 53: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Beforewemoveontothat,wemusttakealittledetourandunderstandhowwegothere.

Page 54: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Exercise1.1Modifythepreviousexampleinsuchawaythatthesinewaveisdrawnusingallrainbowcolors.Thedrawingloopshouldlooklikethefollowing:

(->(.zipsine-waverainbow-colours#(vector%%2))

(.take600)

(.subscribe(fn[[{:keys[xy]}colour]]

(fill-rectxycolour))))

Yourtaskistoimplementtherainbow-coloursstream.Aseverythingupuntilnowhasbeenverylightonexplanations,youmightchoosetocomebacktothisexerciselater,oncewehavecoveredmoreaboutCES.

Therepeat,scan,andflatMapfunctionsmaybeusefulinsolvingthisexercise.BesuretoconsultRxJs’APIathttps://github.com/Reactive-Extensions/RxJS/blob/master/doc/libraries/rx.complete.md.

Page 55: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 56: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

AbitofhistoryBeforewetalkaboutwhatReactiveProgrammingis,itisimportanttounderstandhowotherrelevantprogrammingparadigmsinfluencedhowwedevelopsoftware.Thiswillalsohelpusunderstandthemotivationsbehindreactiveprogramming.

Withfewexceptionsmostofushavebeentaught—eitherself-taughtoratschool/university—imperativeprogramminglanguagessuchasCandPascalorobject-orientedlanguagessuchasJavaandC++.

Inbothcases,theimperativeprogrammingparadigm—ofwhichobject-orientedlanguagesarepart—dictateswewriteprogramsasaseriesofstatementsthatmodifyprogramstate.

Inordertounderstandwhatthismeans,let’slookatashortprogramwritteninpseudo-codethatcalculatesthesumandthemeanvalueofalistofnumbers:

numbers:=[1,2,3,4,5,6]

sum:=0

foreachnumberinnumbers

sum:=sum+number

end

mean:=sum/count(numbers)

TipThemeanvalueistheaverageofthenumbersinthelist,obtainedbydividingthesumbythenumberofelements.

First,wecreateanewarrayofintegers,callednumbers,withnumbersfrom1to6,inclusive.Then,weinitializesumtozero.Next,weiterateoverthearrayofintegers,oneatatime,addingtosumthevalueofeachnumber.

Lastly,wecalculateandassigntheaverageofthenumbersinthelisttothemeanlocalvariable.Thisconcludestheprogramlogic.

Thisprogramwouldprint21forthesumand3forthemean,ifexecuted.

Thoughasimpleexample,ithighlightsitsimperativestyle:wesetupanapplicationstate—sum—andthenexplicitlytellthecomputerhowtomodifythatstateinordertocalculatetheresult.

Page 57: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

DataflowprogrammingThepreviousexamplehasaninterestingproperty:thevalueofmeanclearlyhasadependencyonthecontentsofsum.

Dataflowprogrammingmakesthisrelationshipexplicit.Itmodelsapplicationsasadependencygraphthroughwhichdataflows—fromoperationtooperation—andasvalueschange,thesechangesarepropagatedtoitsdependencies.

Historically,dataflowprogramminghasbeensupportedbycustom-builtlanguagessuchasLucidandBLODI,assuch,leavingothergeneralpurposeprogramminglanguagesout.

Let’sseehowthisnewinsightwouldimpactourpreviousexample.Weknowthatoncethelastlinegetsexecuted,thevalueofmeanisassignedandwon’tchangeunlessweexplicitlyreassignthevariable.

However,let’simagineforasecondthatthepseudo-languageweusedearlierdoessupportdataflowprogramming.Inthatcase,assigningmeantoanexpressionthatreferstobothsumandcount,suchassum/count(numbers),wouldbeenoughtocreatethedirecteddependencygraphinthefollowingdiagram:

Notethatadirectsideeffectofthisrelationshipisthatanimplicitdependencyfromsumtonumbersisalsocreated.Thismeansthatifnumberschange,thechangeispropagatedthroughthegraph,firstupdatingsumandthenfinallyupdatingmean.

ThisiswhereReactiveProgrammingcomesin.Thisparadigmbuildsondataflowprogrammingandchangepropagationtobringthisstyleofprogrammingtolanguagesthatdon’thavenativesupportforit.

Forimperativeprogramminglanguages,ReactiveProgrammingcanbemadeavailablevialibrariesorlanguageextensions.Wedon’tcoverthisapproachinthisbook,butshouldthereaderwantmoreinformationonthesubject,pleaserefertodc-lib(seehttps://code.google.com/p/dc-lib/)foranexample.ItisaframeworkthataddsReactiveProgrammingsupporttoC++viadataflowconstraints.

Page 58: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Object-orientedReactiveProgrammingWhendesigninginteractiveapplicationssuchasdesktopGraphicalUserInterfaces(GUIs),weareessentiallyusinganobject-orientedapproachtoReactiveProgramming.Wewillbuildasimplecalculatorapplicationtodemonstratethisstyle.

TipClojureisn’tanobject-orientedlanguage,butwewillbeinteractingwithpartsoftheJavaAPItobuilduserinterfacesthatweredevelopedinanOOparadigm,hencethetitleofthissection.

Let’sstartbycreatinganewleiningenprojectfromthecommandline:

leinnewcalculator

Thiswillcreateadirectorycalledcalculatorinthecurrentfolder.Next,opentheproject.cljfileinyourfavoritetexteditorandaddadependencyonSeesaw,aClojurelibraryforworkingwithJavaSwing:

(defprojectcalculator"0.1.0-SNAPSHOT"

:description"FIXME:writedescription"

:url"http://example.com/FIXME"

:license{:name"EclipsePublicLicense"

:url"http://www.eclipse.org/legal/epl-v10.html"}

:dependencies[[org.clojure/clojure"1.5.1"]

[seesaw"1.4.4"]])

Atthetimeofthiswriting,thelatestSeesawversionavailableis1.4.4.

Next,inthesrc/calculator/core.cljfile,we’llstartbyrequiringtheSeesawlibraryandcreatingthevisualcomponentswe’llbeusing:

(nscalculator.core

(:require[seesaw.core:refer:all]))

(native!)

(defmain-frame(frame:title"Calculator":on-close:exit))

(deffield-x(text"1"))

(deffield-y(text"2"))

(defresult-label(label"Typenumbersintheboxestoaddthemup!"))

TheprecedingsnippetcreatesawindowwiththetitleCalculatorthatendstheprogramwhenclosed.Wealsocreatetwotextinputfields,field-xandfield-y,aswellasalabelthatwillbeusedtodisplaytheresults,aptlynamedresult-label.

Wewouldlikethelabeltobeupdatedautomaticallyassoonasausertypesanewnumberinanyoftheinputfields.Thefollowingcodedoesexactlythat:

(defnupdate-sum[e]

(try

(text!result-label

Page 59: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

(str"Sumis"(+(Integer/parseInt(textfield-x))

(Integer/parseInt(textfield-y)))))

(catchExceptione

(println"Errorparsinginput."))))

(listenfield-x:key-releasedupdate-sum)

(listenfield-y:key-releasedupdate-sum)

Thefirstfunction,update-sum,isoureventhandler.Itsetsthetextofresult-labeltothesumofthevaluesinfield-xandfield-y.Weusetry/catchhereasareallybasicwaytohandleerrorssincethekeypressedmightnothavebeenanumber.Wethenaddtheeventhandlertothe:key-releasedeventofbothinputfields.

TipInrealapplications,weneverwantacatchblocksuchasthepreviousone.Thisisconsideredbadstyle,andthecatchblockshoulddosomethingmoreusefulsuchasloggingtheexception,firinganotification,orresumingtheapplicationifpossible.

Wearealmostdone.Allweneedtodonowisaddthecomponentswehavecreatedsofartoourmain-frameandfinallydisplayitasfollows:

(config!main-frame:content

(border-panel

:north(horizontal-panel:items[field-xfield-y])

:centerresult-label

:border5))

(defn-main[&args]

(->main-framepack!show!))

Nowwecansavethefileandruntheprogramfromthecommandlineintheproject’srootdirectory:

leinrun-mcalculator.core

Youshouldseesomethinglikethefollowingscreenshot:

Experimentbytypingsomenumbersineitherorbothtextinputfieldsandwatchhowthevalueofthelabelchangesautomatically,displayingthesumofbothnumbers.

Congratulations!Youhavejustcreatedyourfirstreactiveapplication!

Asalludedtopreviously,thisapplicationisreactivebecausethevalueoftheresultlabelreactstouserinputandisupdatedautomatically.However,thisisn’tthewholestory—itlacksincomposabilityandrequiresustospecifythehow,notthewhatofwhatwe’retryingtoachieve.

Page 60: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Asfamiliarasthisstyleofprogrammingmaybe,makingapplicationsreactivethiswayisn’talwaysideal.

Givenpreviousdiscussions,wenoticewestillhadtobefairlyexplicitinsettinguptherelationshipsbetweenthevariouscomponentsasevidencedbyhavingtowriteacustomhandlerandbindittobothinputfields.

Aswewillseethroughouttherestofthisbook,thereisamuchbetterwaytohandlesimilarscenarios.

Page 61: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ThemostwidelyusedreactiveprogramBothexamplesintheprevioussectionwillfeelfamiliartosomereaders.Ifwecalltheinputtextfields“cells”andtheresultlabel’shandlera“formula”,wenowhavethenomenclatureusedinmodernspreadsheetapplicationssuchasMicrosoftExcel.

ThetermReactiveProgramminghasonlybeeninuseinrecentyears,buttheideaofareactiveapplicationisn’tnew.Thefirstelectronicspreadsheetdatesbackto1969whenRenePardoandRemyLandau,thenrecentgraduatesfromHarvardUniversity,createdLANPAR(LANguageforProgrammingArraysatRandom)[1].

ItwasinventedtosolveaproblemthatBellCanadaandAT&Thadatthetime:theirbudgetingformshad2000cellsthat,whenmodified,forcedasoftwarere-writetakinganywherefromsixmonthstotwoyears.

Tothisday,electronicspreadsheetsremainapowerfulandusefultoolforprofessionalsofvariousfields.

Page 62: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

TheObserverdesignpatternAnothersimilaritythekeenreadermayhavenoticediswiththeObserverdesignpattern.Itismainlyusedinobject-orientedapplicationsasawayforobjectstocommunicatewitheachotherwithouthavinganyknowledgeofwhodependsonitschanges.

InClojure,asimpleversionoftheObserverpatterncanbeimplementedusingwatches:

(defnumbers(atom[]))

(defnadder[keyrefold-statenew-state]

(print"Currentsumis"(reduce+new-state)))

(add-watchnumbers:adderadder)

Westartbycreatingourprogramstate,inthiscaseanatomholdinganemptyvector.Next,wecreateawatchfunctionthatknowshowtosumallnumbersinnumbers.Finally,weaddourwatchfunctiontothenumbersatomunderthe:adderkey(usefulforremovingwatches).

TheadderkeyconformswiththeAPIcontractrequiredbyadd-watchandreceivesfourarguments.Inthisexample,weonlycareaboutnew-state.

Now,wheneverweupdatethevalueofnumbers,itswatchwillbeexecuted,asdemonstratedinthefollowing:

(swap!numbersconj1)

;;Currentsumis1

(swap!numbersconj2)

;;Currentsumis3

(swap!numbersconj7)

;;Currentsumis10

Thehighlightedlinesaboveindicatetheresultthatisprintedonthescreeneachtimeweupdatetheatom.

Thoughuseful,theObserverpatternstillrequiressomeamountofworkinsettingupthedependenciesandtherequiredprogramstateinadditiontobeinghardtocompose.

Thatbeingsaid,thispatternhasbeenextendedandisatthecoreofoneoftheReactiveProgrammingframeworkswewilllookatlaterinthisbook,Microsoft’sReactiveExtensions(Rx).

Page 63: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

FunctionalReactiveProgrammingJustlikeReactiveProgramming,FunctionalReactiveProgramming—FRPforshort—hasunfortunatelybecomeanoverloadedterm.

FrameworkssuchasRxJava(seehttps://github.com/ReactiveX/RxJava),ReactiveCocoa(seehttps://github.com/ReactiveCocoa/ReactiveCocoa),andBacon.js(seehttps://baconjs.github.io/)becameextremelypopularinrecentyearsandhadpositionedthemselvesincorrectlyasFRPlibraries.Thisledtotheconfusionsurroundingtheterminology.

Aswewillsee,theseframeworksdonotimplementFRPbutratherareinspiredbyit.

Intheinterestofusingthecorrectterminologyaswellasunderstandingwhat“inspiredbyFRP”means,wewillhaveabrieflookatthedifferentformulationsofFRP.

Page 64: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Higher-orderFRPHigher-orderFRPreferstotheoriginalresearchonFRPdevelopedbyConalElliottandPaulHudakintheirpaperFunctionalReactiveAnimation[2]from1997.ThispaperpresentsFran,adomain-specificlanguageembeddedinHaskellforcreatingreactiveanimations.Ithassincebeenimplementedinseverallanguagesasalibraryaswellaspurposebuiltreactivelanguages.

Ifyourecallthecalculatorexamplewecreatedafewpagesago,wecanseehowthatstyleofReactiveProgrammingrequiresustomanagestateexplicitlybydirectlyreadingandwritingfrom/totheinputfields.AsClojuredevelopers,weknowthatavoidingstateandmutabledataisagoodprincipletokeepinmindwhenbuildingsoftware.ThisprincipleisatthecoreofFunctionalProgramming:

(->>[123456]

(mapinc)

(filtereven?)

(reduce+))

;;12

Thisshortprogramincrementsbyoneallelementsintheoriginallist,filtersallevennumbers,andaddsthemupusingreduce.

Notehowwedidn’thavetoexplicitlymanagelocalstatethroughateachstepofthecomputation.

Differentlyfromimperativeprogramming,wefocusonwhatwewanttodo,forexampleiteration,andnothowwewantittobedone,forexampleusingaforloop.Thisiswhytheimplementationmatchesourdescriptionoftheprogramclosely.Thisisknownasdeclarativeprogramming.

FRPbringsthesamephilosophytoReactiveProgramming.AstheHaskellprogramminglanguagewikionthesubjecthaswiselyputit:

FRPisabouthandlingtime-varyingvaluesliketheywereregularvalues.

Putanotherway,FRPisadeclarativewayofmodelingsystemsthatrespondtoinputovertime.

Bothstatementstouchontheconceptoftime.We’llbeexploringthatinthenextsection,whereweintroducethekeyabstractionsprovidedbyFRP:signals(orbehaviors)andevents.

Page 65: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 66: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

SignalsandeventsSofarwehavebeendealingwiththeideaofprogramsthatreacttouserinput.Thisisofcourseonlyasmallsubsetofreactivesystemsbutisenoughforthepurposesofthisdiscussion.

Userinputhappensseveraltimesthroughtheexecutionofaprogram:keypresses,mousedrags,andclicksarebutafewexamplesofhowausermightinteractwithoursystem.Alltheseinteractionshappenoveraperiodoftime.FRPrecognizesthattimeisanimportantaspectofreactiveprogramsandmakesitafirst-classcitizenthroughitsabstractions.

Bothsignals(alsocalledbehaviors)andeventsarerelatedtotime.Signalsrepresentcontinuous,time-varyingvalues.Events,ontheotherhand,representdiscreteoccurrencesatagivenpointintime.

Forexample,timeisitselfasignal.Itvariescontinuouslyandindefinitely.Ontheotherhand,akeypressbyauserisanevent,adiscreteoccurrence.

Itisimportanttonote,however,thatthesemanticsofhowasignalchangesneednotbecontinuous.Imagineasignalthatrepresentsthecurrent(x,y)coordinatesofyourmousepointer.

Thissignalissaidtochangediscretelyasitdependsontheusermovingthemousepointer—anevent—whichisn’tacontinuousaction.

Page 67: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 68: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ImplementationchallengesPerhapsthemostdefiningcharacteristicofclassicalFRPistheuseofcontinuoustime.

ThismeansFRPassumesthatsignalsarechangingallthetime,eveniftheirvalueisstillthesame,leadingtoneedlessrecomputation.Forexample,themousepositionsignalwilltriggerupdatestotheapplicationdependencygraph—liketheonewesawpreviouslyforthemeanprogram—evenwhenthemouseisstationary.

AnotherproblemisthatclassicalFRPissynchronousbydefault:eventsareprocessedinorder,oneatatime.Harmlessatfirst,thiscancausedelays,whichwouldrenderanapplicationunresponsiveshouldaneventtakesubstantiallylongertoprocess.

PaulHudakandothersfurtheredresearchonhigher-orderFRP[7][8]toaddresstheseissues,butthatcameatthecostofexpressivity.

TheotherformulationsofFRPaimtoovercometheseimplementationchallenges.

Throughouttherestofthechapter,I’llbeusingsignalsandbehaviorsinterchangeably.

Page 69: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

First-orderFRPThemostwell-knownreactivelanguageinthiscategoryisElm(seehttp://elm-lang.org/),anFRPlanguagethatcompilestoJavaScript.ItwascreatedbyEvanCzaplickiandpresentedinhispaperElm:ConcurrentFRPforFunctionalGUIs[3].

Elmmakessomesignificantchangestohigher-orderFRP.

Itabandonstheideaofcontinuoustimeandisentirelyevent-driven.Asaresult,itsolvestheproblemofneedlessrecomputationhighlightedearlier.First-orderFRPcombinesbothbehaviorsandeventsintosignalswhich,incontrasttohigher-orderFRP,arediscrete.

Additionally,first-orderFRPallowstheprogrammertospecifywhensynchronousprocessingofeventsisn’tnecessary,preventingunnecessaryprocessingdelays.

Finally,Elmisastrictprogramminglanguage—meaningargumentstofunctionsareevaluatedeagerly—andthatisaconsciousdecisionasitpreventsspaceandtimeleaks,whicharepossibleinalazylanguagesuchasHaskell.

TipInanFRPlibrarysuchasFran,implementedinalazylanguage,memoryusagecangrowunwieldyascomputationsaredeferredtotheabsolutelylastpossiblemoment,thereforecausingaspaceleak.Theselargercomputations,accumulatedovertimeduetolaziness,canthencauseunexpecteddelayswhenfinallyexecuted,causingtimeleaks.

Page 70: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

AsynchronousdataflowAsynchronousDataFlowgenerallyreferstoframeworkssuchasReactiveExtensions(Rx),ReactiveCocoa,andBacon.js.Itiscalledassuchasitcompletelyeliminatessynchronousupdates.

TheseframeworksintroducetheconceptofObservableSequences[4],sometimescalledEventStreams.

ThisformulationofFRPhastheadvantageofnotbeingconfinedtofunctionallanguages.Therefore,evenimperativelanguageslikeJavacantakeadvantageofthisstyleofprogramming.

Arguably,theseframeworkswereresponsiblefortheconfusionaroundFRPterminology.ConalElliottatsomepointsuggestedthetermCES(seehttps://twitter.com/conal/status/468875014461468677).

Ihavesinceadoptedthisterminology(seehttp://vimeo.com/100688924)asIbelieveithighlightstwoimportantfactors:

AfundamentaldifferencebetweenCESandFRP:CESisentirelyevent-drivenCESishighlycomposableviacombinators,takinginspirationfromFRP

CESisthemainfocusofthisbook.

Page 71: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ArrowizedFRPThisisthelastformulationwewilllookat.ArrowizedFRP[5]introducestwomaindifferencesoverhigher-orderFRP:itusessignalfunctionsinsteadofsignalsandisbuiltontopofJohnHughes’Arrowcombinators[6].

Itismostlyaboutadifferentwayofstructuringcodeandcanbeimplementedasalibrary.Asanexample,ElmsupportsArrowizedFRPviaitsAutomaton(seehttps://github.com/evancz/automaton)library.

TipThefirstdraftofthischaptergroupedthedifferentformulationsofFRPunderthebroadcategoriesofContinuousandDiscreteFRP.ThankstoEvanCzaplicki’sexcellenttalkControllingTimeandSpace:understandingthemanyformulationsofFRP(seehttps://www.youtube.com/watch?v=Agu6jipKfYw),Iwasabletoborrowthemorespecificcategoriesusedhere.ThesecomeinhandywhendiscussingthedifferentapproachestoFRP.

Page 72: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 73: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ApplicationsofFRPThedifferentFRPformulationsarebeingusedtodayinseveralproblemspacesbyprofessionalsandbigorganizationsalike.Throughoutthisbook,we’lllookatseveralexamplesofhowCEScanbeapplied.Someoftheseareinterrelatedasmostmodernprogramshaveseveralcross-cuttingconcerns,butwewillhighlighttwomainareas.

Page 74: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

AsynchronousprogrammingandnetworkingGUIsareagreatexampleofasynchronousprogramming.Onceyouopenaweboradesktopapplication,itsimplysitsthere,idle,waitingforuserinput.

Thisstateisoftencalledtheeventormaineventloop.Itissimplywaitingforexternalstimuli,suchasakeypress,amousebuttonclick,newdatafromthenetwork,orevenasimpletimer.

Eachofthesestimuliisassociatedwithaneventhandlerthatgetscalledwhenoneoftheseeventshappen,hencetheasynchronousnatureofGUIsystems.

Thisisastyleofprogrammingwehavebeenusedtoformanyyears,butasbusinessanduserneedsgrow,theseapplicationsgrowincomplexityaswell,andbetterabstractionsareneededtohandlethedependenciesbetweenallthecomponentsofanapplication.

AnothergreatexamplethatdealswithmanagingcomplexityaroundnetworktrafficisNetflix,whichusesCEStoprovideareactiveAPItotheirbackendservices.

Page 75: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ComplexGUIsandanimationsGamesare,perhaps,thebestexampleofcomplexuserinterfacesastheyhaveintricaterequirementsarounduserinputandanimations.

TheElmlanguagewementionedbeforeisoneofthemostexcitingeffortsinbuildingcomplexGUIs.AnotherexampleisFlapjax,alsotargetedatwebapplications,butisprovidedasaJavaScriptlibrarythatcanbeintegratedwithexistingJavaScriptcodebases.

Page 76: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 77: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

SummaryReactiveProgrammingisallaboutbuildingresponsiveapplications.Thereareseveralwaysinwhichwecanmakeourapplicationsreactive.Someareoldideas:dataflowprogramming,electronicspreadsheets,andtheObserverpatternareallexamples.ButCESinparticularhasbecomepopularinrecentyears.

CESaimstobringtoReactiveProgrammingthedeclarativewayofmodelingproblemsthatisatthecoreofFunctionalProgramming.Weshouldworryaboutwhatandnotabouthow.

Innextchapters,wewilllearnhowwecanapplyCEStoourownprograms.

Page 78: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 79: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Chapter2.ALookatReactiveExtensionsReactiveExtensions—orRx—isaReactiveProgramminglibraryfromMicrosofttobuildcomplexasynchronousprograms.Itmodelstime-varyingvaluesandeventsasobservablesequencesandisimplementedbyextendingtheObserverdesignpattern.

Itsfirsttargetplatformwas.NET,butNetflixhasportedRxtotheJVMunderthenameRxJava.MicrosoftalsodevelopsandmaintainsaportofRxtoJavaScriptcalledRxJS,whichisthetoolweusedtobuildthesine-waveapplication.ThetwoportsworkatreatforussinceClojurerunsontheJVMandClojureScriptinJavaScriptenvironments.

AswesawinChapter1,WhatisReactiveProgramming?,RxisinspiredbyFunctionalReactiveProgrammingbutusesdifferentterminology.InFRP,thetwomainabstractionsarebehaviorsandevents.Althoughtheimplementationdetailsaredifferent,observablesequencesrepresentevents.Rxalsoprovidesabehavior-likeabstractionthroughanotherdatatypecalledBehaviorSubject.

Inthischapter,wewill:

ExploreRx’smainabstraction:observablesLearnaboutthedualitybetweeniteratorsandobservablesCreateandmanipulateobservablesequences

Page 80: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

TheObserverpatternrevisitedInChapter1,WhatisReactiveProgramming?,wesawabriefoverviewoftheObserverdesignpatternandasimpleimplementationofitinClojureusingwatches.Here’showwedidit:

(defnumbers(atom[]))

(defnadder[keyrefold-statenew-state]

(print"Currentsumis"(reduce+new-state)))

(add-watchnumbers:adderadder)

Intheprecedingexample,ourobservablesubjectisthevar,numbers.Theobserveristheadderwatch.Whentheobservablechanges,itpushesitschangestotheobserversynchronously.

Now,contrastthistoworkingwithsequences:

(->>[123456]

(mapinc)

(filtereven?)

(reduce+))

Thistimearound,thevectoristhesubjectbeingobservedandthefunctionsprocessingitcanbethoughtofastheobservers.However,thisworksinapull-basedmodel.Thevectordoesn’tpushanyelementsdownthesequence.Instead,mapandfriendsaskthesequenceformoreelements.Thisisasynchronousoperation.

Rxmakessequences—andmore—behavelikeobservablessothatyoucanstillmap,filter,andcomposethemjustasyouwouldcomposefunctionsovernormalsequences.

Page 81: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Observer–anIterator’sdualClojure’ssequenceoperatorssuchasmap,filter,reduce,andsoonsupportJavaIterables.Asthenameimplies,anIterableisanobjectthatcanbeiteratedover.Atalowlevel,thisissupportedbyretrievinganIteratorreferencefromsuchobject.Java’sIteratorinterfacelookslikethefollowing:

publicinterfaceIterator<E>{

booleanhasNext();

Enext();

voidremove();

}

Whenpassedinanobjectthatimplementsthisinterface,Clojure’ssequenceoperatorspulldatafromitbyusingthenextmethod,whileusingthehasNextmethodtoknowwhentostop.

TipTheremovemethodisrequiredtoremoveitslastelementfromtheunderlyingcollection.Thisin-placemutationisclearlyunsafeinamultithreadedenvironment.WheneverClojureimplementsthisinterfaceforthepurposesofinteroperability,theremovemethodsimplythrowsUnsupportedOperationException.

Anobservable,ontheotherhand,hasobserverssubscribedtoit.Observershavethefollowinginterface:

publicinterfaceObserver<T>{

voidonCompleted();

voidonError(Throwablee);

voidonNext(Tt);

}

Aswecansee,anObserverimplementingthisinterfacewillhaveitsonNextmethodcalledwiththenextvalueavailablefromwhateverobservableit’ssubscribedto.Hence,itbeingapush-basednotificationmodel.

Thisduality[4]becomesclearerifwelookatboththeinterfacessidebyside:

Iterator<E>{Observer<T>{

booleanhasNext();voidonCompleted();

Enext();voidonError(Throwablee);

voidremove();voidonNext(Tt);

}}

Observablesprovidetheabilitytohaveproducerspushitemsasynchronouslytoconsumers.Afewexampleswillhelpsolidifyourunderstanding.

Page 82: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 83: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

CreatingObservablesThischapterisallaboutReactiveExtensions,solet’sgoaheadandcreateaprojectcalledrx-playgroundthatwewillbeusinginourexploratorytour.WewilluseRxClojure(seehttps://github.com/ReactiveX/RxClojure),alibrarythatprovidesClojurebindingsforRxJava()(seehttps://github.com/ReactiveX/RxJava):

$leinnewrx-playground

OpentheprojectfileandaddadependencyonRxJava’sClojurebindings:

(defprojectrx-playground"0.1.0-SNAPSHOT"

:description"FIXME:writedescription"

:url"http://example.com/FIXME"

:license{:name"EclipsePublicLicense"

:url"http://www.eclipse.org/legal/epl-v10.html"}

:dependencies[[org.clojure/clojure"1.5.1"]

[io.reactivex/rxclojure"1.0.0"]])"]])

Now,fireupaREPLintheproject’srootdirectorysothatwecanstartcreatingsomeobservables:

$leinrepl

ThefirstthingweneedtodoisimportRxClojure,solet’sgetthisoutofthewaybytypingthefollowingintheREPL:

(require'[rx.lang.clojure.core:asrx])

(import'(rxObservable))

Thesimplestwaytocreateanewobservableisbycallingthejustreturnfunction:

(defobs(rx/return10))

Now,wecansubscribetoit:

(rx/subscribeobs

(fn[value]

(prn(str"Gotvalue:"value))))

Thiswillprintthestring"Gotvalue:10"totheREPL.

Thesubscribefunctionofanobservableallowsustoregisterhandlersforthreemainthingsthathappenthroughoutitslifecycle:newvalues,errors,oranotificationthattheobservableisdoneemittingvalues.ThiscorrespondstotheonNext,onError,andonCompletedmethodsoftheObserverinterface,respectively.

Intheprecedingexample,wearesimplysubscribingtoonNext,whichiswhywegetnotifiedabouttheobservable’sonlyvalue,10.

Asingle-valueObservableisn’tterriblyinterestingthough.Let’screateandinteractwithonethatemitsmultiplevalues:

(->(rx/seq->o[12345678910])

(rx/subscribeprn))

Page 84: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Thiswillprintthenumbersfrom1to10,inclusive,totheREPL.seq->oisawaytocreateobservablesfromClojuresequences.ItjustsohappensthattheprecedingsnippetcanberewrittenusingRx’sownrangeoperator:

(->(rx/range110)

(rx/subscribeprn))

Ofcourse,thisdoesn’tyetpresentanyadvantagestoworkingwithrawvaluesorsequencesinClojure.

Butwhatifweneedanobservablethatemitsanundefinednumberofintegersatagiveninterval?ThisbecomeschallengingtorepresentasasequenceinClojure,butRxmakesittrivial:

(import'(java.util.concurrentTimeUnit))

(rx/subscribe(Observable/interval100TimeUnit/MILLISECONDS)

prn-to-repl)

TipRxClojuredoesn’tyetprovidebindingstoallofRxJava’sAPI.Theintervalmethodisonesuchexample.We’rerequiredtouseinteroperabilityandcallthemethoddirectlyontheObservableclassfromRxJava.

Observable/intervaltakesasargumentsanumberandatimeunit.Inthiscase,wearetellingittoemitaninteger—startingfromzero—every100milliseconds.IfwetypethisinanREPL-connectededitor,however,twothingswillhappen:

Wewillnotseeanyoutput(dependingonyourREPL;thisistrueforEmacs)Wewillhavearoguethreademittingnumbersindefinitely

BothissuesarisefromthefactthatObservable/intervalisthefirstfactorymethodwehaveusedthatdoesn’temitvaluessynchronously.Instead,itreturnsanObservablethatdeferstheworktoaseparatethread.

Thefirstissueissimpleenoughtofix.Functionssuchasprnwillprinttowhateverthedynamicvar*out*isboundto.WhenworkingincertainREPLenvironmentssuchasEmacs’,thisisboundtotheREPLstream,whichiswhywecangenerallyseeeverythingweprint.

However,sinceRxisdeferringtheworktoaseparatethread,*out*isn’tboundtotheREPLstreamanymoresowedon’tseetheoutput.Inordertofixthis,weneedtocapturethecurrentvalueof*out*andbinditinoursubscription.ThiswillbeincrediblyusefulasweexperimentwithRxintheREPL.Assuch,let’screateahelperfunctionforit:

(defrepl-out*out*)

(defnprn-to-repl[&args]

(binding[*out*repl-out]

(applyprnargs)))

Thefirstthingwedoiscreateavarrepl-outthatcontainsthecurrentREPLstream.Next,wecreateafunctionprn-to-replthatworksjustlikeprn,exceptitusesthebindingmacrotocreateanewbindingfor*out*thatisvalidwithinthatscope.

Page 85: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Thisstillleavesuswiththeroguethreadproblem.NowistheappropriatetimetomentionthatthesubscribemethodfromanObservablereturnsasubscriptionobject.Byholdingontoareferencetoit,wecancallitsunsubscribemethodtoindicatethatwearenolongerinterestedinthevaluesproducedbythatobservable.

Puttingitalltogether,ourintervalexamplecanberewrittenlikethefollowing:

(defsubscription(rx/subscribe(Observable/interval100

TimeUnit/MILLISECONDS)

prn-to-repl))

(Thread/sleep1000)

(rx/unsubscribesubscription)

Wecreateanewintervalobservableandimmediatelysubscribetoit,justaswedidbefore.Thistime,however,weassigntheresultingsubscriptiontoalocalvar.Notethatitnowusesourhelperfunctionprn-to-repl,sowewillstartseeingvaluesbeingprintedtotheREPLstraightaway.

Next,wesleepthecurrent—theREPL—threadforasecond.ThisisenoughtimefortheObservabletoproducenumbersfrom0to9.That’sroughlywhentheREPLthreadwakesupandunsubscribesfromthatobservable,causingittostopemittingvalues.

Page 86: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

CustomObservablesRxprovidesmanymorefactorymethodstocreateObservables(seehttps://github.com/ReactiveX/RxJava/wiki/Creating-Observables),butitisbeyondthescopeofthisbooktocoverthemall.

Nevertheless,sometimes,noneofthebuilt-infactoriesiswhatyouwant.Forsuchcases,Rxprovidesthecreatemethod.Wecanuseittocreateacustomobservablefromscratch.

Asanexample,we’llcreateourownversionofthejustobservableweusedearlierinthischapter:

(defnjust-obs[v]

(rx/observable*

(fn[observer]

(rx/on-nextobserverv)

(rx/on-completedobserver))))

(rx/subscribe(just-obs20)prn)

First,wecreateafunction,just-obs,whichimplementsourobservablebycallingtheobservable*function.

Whencreatinganobservablethisway,thefunctionpassedtoobservable*willgetcalledwithanobserverassoonasonesubscribestous.Whenthishappens,wearefreetodowhatevercomputation—andevenI/O—weneedinordertoproducevaluesandpushthemtotheobserver.

Weshouldremembertocalltheobserver’sonCompletedmethodwheneverwe’redoneproducingvalues.Theprecedingsnippetwillprint20totheREPL.

TipWhilecreatingcustomobservablesisfairlystraightforward,weshouldmakesureweexhaustthebuilt-infactoryfunctionsfirst,onlythenresortingtocreatingourown.

Page 87: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 88: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ManipulatingObservablesNowthatweknowhowtocreateobservables,weshouldlookatwhatkindsofinterestingthingswecandowiththem.Inthissection,wewillseewhatitmeanstotreatObservablesassequences.

We’llstartwithsomethingsimple.Let’sprintthesumofthefirstfivepositiveevenintegersfromanobservableofallintegers:

(rx/subscribe(->>(Observable/interval1TimeUnit/MICROSECONDS)

(rx/filtereven?)

(rx/take5)

(rx/reduce+))

prn-to-repl)

Thisisstartingtolookawfullyfamiliartous.Wecreateanintervalthatwillemitallpositiveintegersstartingatzeroevery1microsecond.Then,wefilterallevennumbersinthisobservable.Obviously,thisistoobigalisttohandle,sowesimplytakethefirstfiveelementsfromit.Finally,wereducethevalueusing+.Theresultis20.

Todrivehomethepointthatprogrammingwithobservablesreallyisjustlikeoperatingonsequences,wewilllookatonemoreexamplewherewewillcombinetwodifferentObservablesequences.OnecontainsthenamesofmusiciansI’mafanofandtheotherthenamesoftheirrespectivebands:

(defnmusicians[]

(rx/seq->o["JamesHetfield""DaveMustaine""KerryKing"]))

(defnbands[]

(rx/seq->o["Metallica""Megadeth""Slayer"]))

WewouldliketoprinttotheREPLastringoftheformatMusicianname–from:bandname.Anaddedrequirementisthatthebandnamesshouldbeprintedinuppercaseforimpact.

We’llstartbycreatinganotherobservablethatcontainstheuppercasedbandnames:

(defnuppercased-obs[]

(rx/map(fn[s](.toUpperCases))(bands)))

Whilenotstrictlynecessary,thismakesareusablepieceofcodethatcanbehandyinseveralplacesoftheprogram,thusavoidingduplication.Subscribersinterestedintheoriginalbandnamescankeepsubscribingtothebandsobservable.

Withthetwoobservablesinhand,wecanproceedtocombinethem:

(->(rx/mapvector

(musicians)

(uppercased-obs))

(rx/subscribe(fn[[musicianband]]

(prn-to-repl(strmusician"-from:"band)))))

Oncemore,thisexampleshouldfeelfamiliar.Thesolutionwewereafterwasawaytozip

Page 89: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

thetwoobservablestogether.RxClojureprovideszipbehaviorthroughmap,muchlikeClojure’scoremapfunctiondoes.Wecallitwiththreearguments:thetwoobservablestozipandafunctionthatwillbecalledwithbothelements,onefromeachobservable,andshouldreturnanappropriaterepresentation.Inthiscase,wesimplyturnthemintoavector.

Next,inoursubscriber,wesimplydestructurethevectorinordertoaccessthemusicianandbandnames.WecanfinallyprintthefinalresulttotheREPL:

"JamesHetfield-from:METALLICA"

"DaveMustaine-from:MEGADETH"

"KerryKing-from:SLAYER"

Page 90: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 91: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

FlatmapandfriendsIntheprevioussection,welearnedhowtotransformandcombineobservableswithoperationssuchasmap,reduce,andzip.However,thetwoobservablesabove—musiciansandbands—wereperfectlycapableofproducingvaluesontheirown.Theydidnotneedanyextrainput.

Inthissection,weexamineadifferentscenario:we’lllearnhowwecancombineobservables,wheretheoutputofoneistheinputofanother.WeencounteredflatmapbeforeinChapter1,WhatisReactiveProgramming?Ifyouhavebeenwonderingwhatitsroleis,thissectionaddressesexactlythat.

Here’swhatwearegoingtodo:givenanobservablerepresentingalistofallpositiveintegers,we’llcalculatethefactorialforallevennumbersinthatlist.Sincethelististoobig,we’lltakefiveitemsfromit.Theendresultshouldbethefactorialsof0,2,4,6,and8,respectively.

Thefirstthingweneedisafunctiontocalculatethefactorialofanumbern,aswellasourobservable:

(defnfactorial[n]

(reduce*(range1(incn))))

(defnall-positive-integers[]

(Observable/interval1TimeUnit/MICROSECONDS))

Usingsometypeofvisualaidwillbehelpfulinthissection,sowe’llstartwithamarblediagramrepresentingthepreviousobservable:

Themiddlearrowrepresentstimeanditflowsfromlefttoright.ThisdiagramrepresentsaninfiniteObservablesequence,asindicatedbytheuseofellipsisattheendofit.

Sincewe’recombiningalltheobservablesnow,we’llcreateonethat,givenanumber,emitsitsfactorialusingthehelperfunctiondefinedearlier.We’lluseRx’screatemethodforthispurpose:

(defnfact-obs[n]

(rx/observable*

(fn[observer]

(rx/on-nextobserver(factorialn))

Page 92: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

(rx/on-completedobserver))))

Thisisverysimilartothejust-obsobservablewecreatedearlierinthischapter,exceptthatitcalculatesthefactorialofitsargumentandemitstheresult/factorialinstead,endingthesequenceimmediatelythereafter.Thefollowingdiagramillustrateshowitworks:

Wefeedthenumber5totheobservable,whichinturnemitsitsfactorial,120.Theverticalbarattheendofthetimelineindicatesthesequenceterminatesthen.

Runningthecodeconfirmsthatourfunctioniscorrect:

(rx/subscribe(fact-obs5)prn-to-repl)

;;120

Sofarsogood.Now,weneedtocombinebothobservablesinordertoachieveourgoal.ThisiswhereflatmapofRxcomesin.We’llfirstseeitinactionandthengetintotheexplanation:

(rx/subscribe(->>(all-positive-integers)

(rx/filtereven?)

(rx/flatmapfact-obs)

(rx/take5))

prn-to-repl)

Ifweruntheprecedingcode,itwillprintthefactorialsfor0,2,4,6,and8,justlikewewanted:

1

2

24

720

40320

Mostoftheprecedingcodesnippetshouldlookfamiliar.Thefirstthingwedoisfilterallevennumbersfromall-positive-numbers.Thisleavesuswiththefollowingobservablesequence:

Page 93: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Muchlikeall-positive-integers,this,too,isaninfiniteobservable.

However,thenextlineofourcodelooksalittleodd.Wecallflatmapandgiveitthefact-obsfunction.Afunctionweknowitselfreturnsanotherobservable.flatmapwillcallfact-obswitheachvalueitemits.fact-obswill,inturn,returnasingle-valueobservableforeachnumber.However,oursubscriberdoesn’tknowhowtodealwithobservables!It’ssimplyinterestedinthefactorials!

Thisiswhy,aftercallingfact-obstoobtainanobservable,flatmapflattensallofthemintoasingleobservablewecansubscribeto.Thisisquiteamouthful,solet’svisualizewhatthismeans:

Page 94: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Asyoucanseeintheprecedingdiagram,throughouttheexecutionofflatmap,weendupwithalistofobservables.However,wedon’tcareabouteachobservablebutratheraboutthevaluestheyemit.Flatmap,then,istheperfecttoolasitcombines—flattens—allofthemintotheobservablesequenceshownatthebottomofthefigure.

Youcanthinkofflatmapasmapcatforobservablesequences.

Therestofthecodeisstraightforward.Wesimplytakethefirstfiveelementsfromthisobservableandsubscribetoit,aswehavebeendoingsofar.

Page 95: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

OnemoreflatmapfortheroadYoumightbewonderingwhatwouldhappeniftheobservablesequencewe’reflatmappingemittedmorethanonevalue.Whatthen?

We’llseeonelastexamplebeforewebeginthenextsectioninordertoillustratethebehaviorofflatMapinsuchcases.

Here’sanobservablethatemitsitsargumenttwice:

(defnrepeat-obs[n]

(rx/seq->o(repeat2n)))

Usingitisstraightforward:

(->(repeat-obs5)

(rx/subscribeprn-to-repl))

;;5

;;5

Aspreviously,we’llnowcombinethisobservablewiththeonewecreatedearlier,all-positive-integers.Beforereadingon,thinkaboutwhatyouexpecttheoutputtobefor,say,thefirstthreepositiveintegers.

Thecodeisasfollows:

(rx/subscribe(->>(all-positive-integers)

(rx/flatmaprepeat-obs)

(rx/take6))

prn-to-repl)

Andtheoutputisasfollows:

0

0

1

1

2

2

Theresultmightbeunexpectedforsomereaders.Let’shavealookatthemarblediagramforthisexampleandmakesureweunderstandhowitworks:

Page 96: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Eachtimerepeat-obsgetscalled,itemitstwovaluesandterminates.flatmapthencombinesthemallinasingleobservable,makingthepreviousoutputclearer.

Onelastthingworthmentioningaboutflatmap—andthetitleofthissection—isthatits“friends”refertotheseveralnamesbywhichflatmapisknown.

Forinstance,Rx.NETcallsitselectMany.RxJavaandScalacallitflatMap—thoughRxJavahasanaliasforitcalledmapMany.TheHaskellcommunitycallsitbind.Thoughtheyhavedifferentnames,thesefunctionssemanticsarethesameandarepartofahigher-orderabstractioncalledaMonad.Wedon’tneedtoknowanythingaboutMonadstoproceed.

Theimportantthingtokeepinmindisthatwhenyou’resittingatthebartalkingtoyourfriendsaboutCompositionalEventSystems,allthesenamesmeanthesamething.

Page 97: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 98: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ErrorhandlingAveryimportantaspectofbuildingreliableapplicationsisknowingwhattodowhenthingsgowrong.Itisnaivetoassumethatthenetworkisreliable,thathardwarewon’tfail,orthatwe,asdevelopers,won’tmakemistakes.

RxJavaembracesthisfactandprovidesarichsetofcombinatorstodealwithfailure,afewofwhichweexaminehere.

Page 99: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

OnErrorLet’sgetstartedbycreatingabadlybehavedobservablethatalwaysthrowsanexception:

(defnexceptional-obs[]

(rx/observable*

(fn[observer]

(rx/on-nextobserver(throw(Exception."Oops.Somethingwent

wrong")))

(rx/on-completedobserver))))

Nowlet’swatchwhathappensifwesubscribetoit:

(rx/subscribe(->>(exceptional-obs)

(rx/mapinc))

(fn[v](prn-to-repl"resultis"v)))

;;ExceptionOops.Somethingwentwrongrx-playground.core/exceptional-

obs/fn--1505

Theexceptionthrownbyexceptional-obsisn’tcaughtanywheresoitsimplybubblesuptotheREPL.IfthiswasawebapplicationouruserswouldbepresentedwithawebservererrorsuchastheHTTPcode500–InternalServerError.Thoseuserswouldprobablynotuseoursystemagain.

Ideally,wewouldliketogetachancetohandlethisexceptiongracefully,possiblyrenderingafriendlyerrormessagethatwillletoursusersknowwecareaboutthem.

Aswehaveseenearlierinthechapter,thesubscribefunctioncantakeupto3functionsasarguments:

Thefirst,ortheonNexthandler,iscalledwhentheobservableemitsanewvalueThesecond,oronError,iscalledwhenevertheobservablethrowsanexceptionThethirdandlastfunction,oronComplete,iscalledwhentheobservablehascompletedandwillnotemitanynewitems

ForourpurposesweareinterestedintheonErrorhandler,andusingitisstraightforward:

(rx/subscribe(->>(exceptional-obs)

(rx/mapinc))

(fn[v](prn-to-repl"resultis"v))

(fn[e](prn-to-repl"erroris"e)))

;;"erroris"#<Exceptionjava.lang.Exception:Oops.Somethingwentwrong>

Thistime,insteadofthrowingtheexception,ourerrorhandlergetscalledwithit.Thisgivesustheopportunitytodisplayanappropriatemessagetoourusers.

Page 100: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

CatchTheuseofonErrorgivesusamuchbetterexperienceoverallbutitisn’tveryflexible.

Let’simagineadifferentscenariowherewehaveanobservableretrievingdatafromthenetwork.Whatif,whenthisobserverfails,wewouldliketopresenttheuserwithacachedvalueinsteadofanerrormessage?

Thisiswherethecatchcombinatorcomesin.Itallowsustospecifyafunctiontobeinvokedwhentheobservablethrowsanexception,muchlikeOnErrordoes.

DifferentlyfromOnError,however,catchhastoreturnanewObservablethatwillbethenewsourceofitemsfromthemomenttheexceptionwasthrown:

(rx/subscribe(->>(exceptional-obs)

(rx/catchExceptione

(rx/return10))

(rx/mapinc))

(fn[v](prn-to-repl"resultis"v)))

;;"resultis"11

Inthepreviousexample,weareessentiallyspecifyingthat,wheneverexceptional-obsthrows,weshouldreturnthevalue10.Wearenotlimitedtosinglevalues,however.Infact,wecanuseanyObservablewelikeasthenewsource:

(rx/subscribe(->>(exceptional-obs)

(rx/catchExceptione

(rx/seq->o(range5)))

(rx/mapinc))

(fn[v](prn-to-repl"resultis"v)))

;;"resultis"1

;;"resultis"2

;;"resultis"3

;;"resultis"4

;;"resultis"5

Page 101: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

RetryThelasterrorhandlingcombinatorwe’llexamineisretry.ThiscombinatorisusefulwhenweknowanerrororexceptionisonlytransientsoweshouldprobablygiveitanothershotbyresubscribingtotheObservable.

First,we’llcreateanobservablethatfailswhenitissubscribedtoforthefirsttime.However,thenexttimeitissubscribedto,itsucceedsandemitsanewitem:

(defnretry-obs[]

(let[errored(atomfalse)]

(rx/observable*

(fn[observer]

(if@errored

(rx/on-nextobserver20)

(do(reset!erroredtrue)

(throw(Exception."Oops.Somethingwentwrong"))))))))

Let’sseewhathappensifwesimplysubscribetoit:

(rx/subscribe(retry-obs)

(fn[v](prn-to-repl"resultis"v)))

;;ExceptionOops.Somethingwentwrongrx-playground.core/retry-obs/fn-

-1476

Asexpected,theexceptionsimplybubblesupasinourfirstexample.Howeverweknow—forthepurposesofthisexample—thatthisisatransientfailure.Let’sseewhatchangesifweuseretry:

(rx/subscribe(->>(retry-obs)

(.retry))

(fn[v](prn-to-repl"resultis"v)))

;;"resultis"20

Now,ourcodeisresponsibleforretryingtheObservableandasexpectedwegetthecorrectoutput.

It’simportanttonotethatretrywillattempttoresubscribeindefinitelyuntilitsucceeds.ThismightnotbewhatyouwantsoRxprovidesavariation,calledretryWith,whichallowsustospecifyapredicatefunctionthatcontrolswhenandifretryingshouldstop.

Alltheseoperatorsgiveusthetoolsweneedtobuildreliablereactiveapplicationsandweshouldalwayskeeptheminmindastheyare,withoutadoubt,agreatadditiontoourtoolbox.TheRxJavawikionthesubjectshouldbereferredtoformoreinformation:https://github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators.

Page 102: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 103: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

BackpressureAnotherissuewemightbefacedwithistheoneofobservablesthatproduceitemsfasterthanwecanconsume.Theproblemthatarisesinthisscenarioiswhattodowiththisever-growingbacklogofitems.

Asanexample,thinkaboutzippingtwoobservablestogether.Thezipoperator(ormapinRxClojure)willonlyemitanewvaluewhenallobservableshaveemittedanitem.

Soifoneoftheseobservablesisalotfasteratproducingitemsthantheothers,mapwillneedtobuffertheseitemsandwaitfortheothers,whichwillmostlikelycauseanerror,asshownhere:

(defnfast-producing-obs[]

(rx/mapinc(Observable/interval1TimeUnit/MILLISECONDS)))

(defnslow-producing-obs[]

(rx/mapinc(Observable/interval500TimeUnit/MILLISECONDS)))

(rx/subscribe(->>(rx/mapvector

(fast-producing-obs)

(slow-producing-obs))

(rx/map(fn[[xy]]

(+xy)))

(rx/take10))

prn-to-repl

(fn[e](prn-to-repl"erroris"e)))

;;"erroris"#<MissingBackpressureException

rx.exceptions.MissingBackpressureException>

Asseenintheprecedingcode,wehaveafastproducingobservablethatemitsitems500timesfasterthantheslowerObservable.Clearly,wecan’tkeepupwithitandsurelyenough,RxthrowsMissingBackpressureException.

Whatthisexceptionistellingusisthatthefastproducingobservabledoesn’tsupportanytypeofbackpressure—whatRxcallsReactivepullbackpressure—thatis,consumerscan’ttellittogoslower.ThankfullyRxprovidesuswithcombinatorsthatarehelpfulinthesescenarios.

Page 104: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

SampleOnesuchcombinatorissample,whichallowsustosampleanobservableatagiveninterval,thusthrottlingthesourceobservable’soutput.Let’sapplyittoourpreviousexample:

(rx/subscribe(->>(rx/mapvector

(.sample(fast-producing-obs)200

TimeUnit/MILLISECONDS)

(slow-producing-obs))

(rx/map(fn[[xy]]

(+xy)))

(rx/take10))

prn-to-repl

(fn[e](prn-to-repl"erroris"e)))

;;204

;;404

;;604

;;807

;;1010

;;1206

;;1407

;;1613

;;1813

;;2012

TheonlychangeisthatwecallsampleonourfastproducingObservablebeforecallingmap.Wewillsampleitevery200milliseconds.

Byignoringallotheritemsemittedinthistimeslice,wehavemitigatedourinitialproblem,eventhoughtheoriginalObservabledoesn’tsupportanyformofbackpressure.

Thesamplecombinatorisonlyoneofthecombinatorsusefulinsuchcases.OthersincludethrottleFirst,debounce,buffer,andwindow.Onedrawbackofthisapproach,however,isthatalotoftheitemsgeneratedendupbeingignored.

Dependingonthetypeofapplicationwearebuilding,thismightbeanacceptablecompromise.Butwhatifweareinterestedinallitems?

Page 105: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

BackpressurestrategiesIfanObservabledoesn’tsupportbackpressurebutwearestillinterestedinallitemsitemits,wecanuseoneofthebuilt-inbackpressurecombinatorsprovidedbyRx.

Asanexamplewewilllookatonesuchcombinator,onBackpressureBuffer:

(rx/subscribe(->>(rx/mapvector

(.onBackpressureBuffer(fast-producing-obs))

(slow-producing-obs))

(rx/map(fn[[xy]]

(+xy)))

(rx/take10))

prn-to-repl

(fn[e](prn-to-repl"erroris"e)))

;;2

;;4

;;6

;;8

;;10

;;12

;;14

;;16

;;18

;;20

Theexampleisverysimilartotheonewhereweusedsample,buttheoutputisfairlydifferent.Thistimewegetallitemsemittedbybothobservables.

TheonBackpressureBufferstrategyimplementsastrategythatsimplybuffersallitemsemittedbytheslowerObservable,emittingthemwhenevertheconsumerisready.Inourcase,thathappensevery500milliseconds.

OtherstrategiesincludeonBackpressureDropandonBackpressureBlock.

It’sworthnotingthatReactivepullbackpressureisstillworkinprogressandthebestwaytokeepuptodatewithprogressisontheRxJavawikionthesubject:https://github.com/ReactiveX/RxJava/wiki/Backpressure.

Page 106: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 107: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

SummaryInthischapter,wetookadeepdiveintoRxJava,aportformMicrosoft’sReactiveExtensionsfrom.NET.Welearnedaboutitsmainabstraction,theobservable,andhowitrelatestoiterables.

Wealsolearnedhowtocreate,manipulate,andcombineobservablesinseveralways.Theexamplesshownherewerecontrivedtokeepthingssimple.Nevertheless,allconceptspresentedareextremelyusefulinrealapplicationsandwillcomeinhandyforournextchapter,whereweputthemtouseinamoresubstantialexample.

Finally,wefinishedbylookingaterrorhandlingandbackpressure,bothofwhichareimportantcharacteristicsofreliableapplicationsthatshouldalwaysbekeptinmind.

Page 108: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 109: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Chapter3.AsynchronousProgrammingandNetworkingSeveralbusinessapplicationsneedtoreacttoexternalstimuli—suchasnetworktraffic—asynchronously.Anexampleofsuchsoftwaremightbeadesktopapplicationthatallowsustotrackacompany’ssharepricesinthestockmarket.

Wewillbuildthisapplicationfirstusingamoretraditionalapproach.Indoingso,wewill:

BeabletoidentifyandunderstandthedrawbacksofthefirstdesignLearnhowtouseRxClojuretodealwithstatefulcomputationssuchasrollingaveragesRewritetheexampleinadeclarativefashionusingobservablesequences,thusreducingthecomplexityfoundinourfirstapproach

Page 110: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

BuildingastockmarketmonitoringapplicationOurstockmarketprogramwillconsistofthreemaincomponents:

Afunctionsimulatinganexternalservicefromwhichwecanquerythecurrentprice—thiswouldlikelybeanetworkcallinarealsettingAschedulerthatpollstheprecedingfunctionatapredefinedintervalAdisplayfunctionresponsibleforupdatingthescreen

We’llstartbycreatinganewleiningenproject,wherethesourcecodeforourapplicationwilllive.Typethefollowingonthecommandlineandthenswitchintothenewlycreateddirectory:

leinnewstock-market-monitor

cdstock-market-monitor

Aswe’llbebuildingaGUIforthisapplication,goaheadandaddadependencyonSeesawtothedependenciessectionofyourproject.clj:

[seesaw"1.4.4"]

Next,createasrc/stock_market_monitor/core.cljfileinyourfavoriteeditor.Let’screateandconfigureourapplication’sUIcomponents:

(nsstock-market-monitor.core

(:require[seesaw.core:refer:all])

(:import(java.util.concurrentScheduledThreadPoolExecutor

TimeUnit)))

(native!)

(defmain-frame(frame:title"Stockpricemonitor"

:width200:height100

:on-close:exit))

(defprice-label(label"Price:-"))

(config!main-frame:contentprice-label)

Asyoucansee,theUIisfairlysimple.Itconsistsofasinglelabelthatwilldisplayacompany’sshareprice.WealsoimportedtwoJavaclasses,ScheduledThreadPoolExecutorandTimeUnit,whichwewilluseshortly.

Thenextthingweneedisourpollingmachinerysothatwecaninvokethepricingserviceonagivenschedule.We’llimplementthisviaathreadpoolsoasnottoblockthemainthread:

TipUserinterfaceSDKssuchasswinghavetheconceptofamain—orUI—thread.ThisisthethreadusedbytheSDKtorendertheUIcomponentstothescreen.Assuch,ifwe

Page 111: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

haveblocking—orevensimplyslowrunning—operationsexecuteinthisthread,theuserexperiencewillbeseverelyaffected,hencetheuseofathreadpooltooffloadexpensivefunctioncalls.

(defpool(atomnil))

(defninit-scheduler[num-threads]

(reset!pool(ScheduledThreadPoolExecutor.num-threads)))

(defnrun-every[poolmillisf]

(.scheduleWithFixedDelaypool

f

0millisTimeUnit/MILLISECONDS))

(defnshutdown[pool]

(println"Shuttingdownscheduler…")

(.shutdownpool))

Theinit-schedulerfunctioncreatesScheduledThreadPoolExecutorwiththegivennumberofthreads.That’sthethreadpoolinwhichourperiodicfunctionwillrun.Therun-everyfunctionschedulesafunctionfinthegivenpooltorunattheintervalspecifiedbymillis.Finally,shutdownisafunctionthatwillbecalledonprogramterminationandshutdownthethreadpoolgracefully.

Therestoftheprogramputsallthesepartstogether:

(defnshare-price[company-code]

(Thread/sleep200)

(rand-int1000))

(defn-main[&args]

(show!main-frame)

(.addShutdownHook(Runtime/getRuntime)

(Thread.#(shutdown@pool)))

(init-scheduler1)

(run-every@pool500

#(->>(str"Price:"(share-price"XYZ"))

(text!price-label)

invoke-now)))

Theshare-pricefunctionsleepsfor200millisecondstosimulatenetworklatencyandreturnsarandomintegerbetween0and1,000representingthestock’sprice.

Thefirstlineofour-mainfunctionaddsashutdownhooktotheruntime.Thisallowsourprogramtointercepttermination—suchaspressingCtrl+Cinaterminalwindow—andgivesustheopportunitytoshutdownthethreadpool.

TipTheScheduledThreadPoolExecutorpoolcreatesnon-daemonthreadsbydefault.Aprogramcannotterminateifthereareanynon-daemonthreadsaliveinadditiontotheprogram’smainthread.Thisiswhytheshutdownhookisnecessary.

Next,weinitializetheschedulerwithasinglethreadandscheduleafunctiontobe

Page 112: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

executedevery500milliseconds.Thisfunctionaskstheshare-pricefunctionforXYZ’scurrentpriceandupdatesthelabel.

TipDesktopapplicationsrequireallrenderingtobedoneintheUIthread.However,ourperiodicfunctionrunsonaseparatethreadandneedstoupdatethepricelabel.Thisiswhyweuseinvoke-now,whichisaSeesawfunctionthatschedulesitsbodytobeexecutedintheUIthreadassoonaspossible.

Let’sruntheprogrambytypingthefollowingcommandintheproject’srootdirectory:

leintrampolinerun-mstock-market-monitor.core

TipTrampoliningtellsleiningennottonestourprogram’sJVMwithinitsown,thusfreeingustohandleusesofCtrl+Courselvesthroughshutdownhooks.

Awindowliketheoneshowninthefollowingscreenshotwillbedisplayed,withthevaluesonitbeingupdatedasperthescheduleimplementedearlier:

Thisisafinesolution.Thecodeisrelativelystraightforwardandsatisfiesouroriginalrequirements.However,ifwelookatthebigpicture,thereisafairbitofnoiseinourprogram.Mostofitslinesofcodearedealingwithcreatingandmanagingathreadpool,which,whilenecessary,isn’tcentraltotheproblemwe’resolving—it’sanimplementationdetail.

We’llkeepthingsastheyareforthemomentandaddanewrequirement:rollingaverages.

Page 113: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 114: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

RollingaveragesNowthatwecanseetheup-to-datestockpriceforagivencompany,itmakessensetodisplayarollingaverageofthepast,say,fivestockprices.Inarealscenario,thiswouldprovideanobjectiveviewofacompany’ssharetrendinthestockmarket.

Let’sextendourprogramtoaccommodatethisnewrequirement.

First,we’llneedtomodifyournamespacedefinition:

(nsstock-market-monitor.core

(:require[seesaw.core:refer:all])

(:import(java.util.concurrentScheduledThreadPoolExecutor

TimeUnit)

(clojure.langPersistentQueue)))

Theonlychangeisanewimportclause,forClojure’sPersistentQueueclass.Wewillbeusingthatlater.

We’llalsoneedanewlabeltodisplaythecurrentrunningaverage:

(defrunning-avg-label(label"Runningaverage:-"))

(config!main-frame:content

(border-panel

:northprice-label

:centerrunning-avg-label

:border5))

Next,weneedafunctiontocalculaterollingaverages.Arolling—ormoving—averageisacalculationinstatistics,whereyoutaketheaverageofasubsetofitemsinadataset.Thissubsethasafixedsizeanditshiftsforwardasdatacomesin.Thiswillbecomeclearwithanexample.

Supposeyouhavealistwithnumbersfrom1to10,inclusive.Ifweuse3asthesubsetsize,therollingaveragesareasfollows:

[12345678910]=>2.0

[12345678910]=>3.0

[12345678910]=>4.0

Thehighlightedpartsintheprecedingcodeshowthecurrentwindowbeingusedtocalculatethesubsetaverage.

Nowthatweknowwhatrollingaveragesare,wecanmoveontoimplementitinourprogram:

(defnroll-buffer[buffernumbuffer-size]

(let[buffer(conjbuffernum)]

(if(>(countbuffer)buffer-size)

(popbuffer)

buffer)))

(defnavg[numbers]

(float(/(reduce+numbers)

(countnumbers))))

Page 115: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

(defnmake-running-avg[buffer-size]

(let[buffer(atomclojure.lang.PersistentQueue/EMPTY)]

(fn[n]

(swap!bufferroll-buffernbuffer-size)

(avg@buffer))))

(defrunning-avg(running-avg5))

Theroll-bufferfunctionisautilityfunctionthattakesaqueue,anumber,andabuffersizeasarguments.Itaddsthatnumbertothequeue,poppingtheoldestelementifthequeuegoesoverthebufferlimit,thuscausingitscontentstorollover.

Next,wehaveafunctionforcalculatingtheaverageofacollectionofnumbers.Wecasttheresulttofloatifthere’sanunevendivision.

Finally,thehigher-ordermake-running-avgfunctionreturnsastateful,singleargumentfunctionthatclosesoveranemptypersistentqueue.Thisqueueisusedtokeeptrackofthecurrentsubsetofdata.

Wethencreateaninstanceofthisfunctionbycallingitwithabuffersizeof5andsaveittotherunning-avgvar.Eachtimewecallthisnewfunctionwithanumber,itwilladdittothequeueusingtheroll-bufferfunctionandthenfinallyreturntheaverageoftheitemsinthequeue.

Thecodewehavewrittentomanagethethreadpoolwillbereusedasissoallthatislefttodoisupdateourperiodicfunction:

(defnworker[]

(let[price(share-price"XYZ")]

(->>(str"Price:"price)(text!price-label))

(->>(str"Runningaverage:"(running-avgprice))

(text!running-avg-label))))

(defn-main[&args]

(show!main-frame)

(.addShutdownHook(Runtime/getRuntime)

(Thread.#(shutdown@pool)))

(init-scheduler1)

(run-every@pool500

#(invoke-now(worker))))

Sinceourfunctionisn’taone-lineranymore,weabstractitawayinitsownfunctioncalledworker.Asbefore,itupdatesthepricelabel,butwehavealsoextendedittousetherunning-avgfunctioncreatedearlier.

We’rereadytoruntheprogramoncemore:

leintrampolinerun-mstock-market-monitor.core

Youshouldseeawindowliketheoneshowninthefollowingscreenshot:

Page 116: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

YoushouldseethatinadditiontodisplayingthecurrentsharepriceforXYZ,theprogramalsokeepstrackandrefreshestherunningaverageofthestreamofprices.

Page 117: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 118: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

IdentifyingproblemswithourcurrentapproachAsidefromthelinesofcoderesponsibleforbuildingtheuserinterface,ourprogramisroughly48lineslong.

Thecoreoftheprogramresidesintheshare-priceandavgfunctions,whichareresponsibleforqueryingthepriceserviceandcalculatingtheaverageofalistofnnumbers,respectively.Theyrepresentonlysixlinesofcode.Thereisalotofincidentalcomplexityinthissmallprogram.

Incidentalcomplexityiscomplexitycausedbycodethatisnotessentialtotheproblemathand.Inthisexample,wehavetwosourcesofsuchcomplexity—wearedisregardingUIspecificcodeforthisdiscussion:thethreadpoolandtherollingbufferfunction.Theyaddagreatdealofcognitiveloadtosomeonereadingandmaintainingthecode.

Thethreadpoolisexternaltoourproblem.Itisonlyconcernedwiththesemanticsofhowtoruntasksasynchronously.Therollingbufferfunctionspecifiesadetailedimplementationofaqueueandhowtouseittorepresenttheconcept.

Ideally,weshouldbeabletoabstractoverthesedetailsandfocusonthecoreofourproblem;CompositionalEventSystems(CES)allowsustodojustthat.

Page 119: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 120: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

RemovingincidentalcomplexitywithRxClojureInChapter2,ALookatReactiveExtensions,welearnedaboutthebasicbuildingblocksofRxClojure,anopen-sourceCESframework.Inthissection,we’llusethisknowledgeinordertoremovetheincidentalcomplexityfromourprogram.Thiswillgiveusaclear,declarativewaytodisplaybothpricesandrollingaverages.

TheUIcodewe’vewrittensofarremainsunchanged,butweneedtomakesureRxClojureisdeclaredinthedependenciessectionofourproject.cljfile:

[io.reactivex/rxclojure"1.0.0"]

Then,ensurewerequirethefollowinglibrary:

(nsstock-market-monitor.core

(:require[rx.lang.clojure.core:asrx]

[seesaw.core:refer:all])

(:import(java.util.concurrentTimeUnit)

(rxObservable)))

Thewayweapproachtheproblemthistimeisalsodifferent.Let’stakealookatthefirstrequirement:itrequireswedisplaythecurrentpriceofacompany’sshareinthestockmarket.

Everytimewequerythepriceservice,wegeta—possiblydifferent—priceforthecompanyinquestion.AswesawinChapter2,ALookatReactiveExtensions,modelingthisasanobservablesequenceiseasy,sowe’llstartwiththat.We’llcreateafunctionthatgivesusbackastockpriceobservableforthegivencompany:

(defnmake-price-obs[company-code]

(rx/return(share-pricecompany-code)))

Thisisanobservablethatyieldsasinglevalueandterminates.It’sequivalenttothefollowingmarblediagram:

Partofthefirstrequirementisthatwequerytheserviceonapredefinedtimeinterval—every500millisecondsinthiscase.Thishintsatanobservablewehaveencounteredbefore,aptlynamedinterval.Inordertogetthepollingbehaviorwewant,weneedto

Page 121: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

combinetheintervalandthepriceobservables.

Asyouprobablyrecall,flatmapisthetoolforthejobhere:

(rx/flatmap(fn[_](make-price-obs"XYZ"))

(Observable/interval500

TimeUnit/MILLISECONDS))

TheprecedingsnippetcreatesanobservablethatwillyieldthelateststockpriceforXYZevery500millisecondsindefinitely.Itcorrespondstothefollowingdiagram:

Infact,wecansimplysubscribetothisnewobservableandtestitout.Modifyyourmainfunctiontothefollowingsnippetandruntheprogram:

(defn-main[&args]

(show!main-frame)

(let[price-obs(rx/flatmap(fn[_](make-price-obs"XYZ"))

(Observable/interval500

TimeUnit/MILLISECONDS))]

(rx/subscribeprice-obs

(fn[price]

(text!price-label(str"Price:"price))))))

Thisisverycool!Wereplicatedthebehaviorofourfirstprogramwithonlyafewlinesofcode.Thebestpartisthatwedidnothavetoworryaboutthreadpoolsorschedulingactions.Bythinkingabouttheproblemintermsofobservablesequences,aswellascombiningexistingandnewobservables,wewereabletodeclarativelyexpresswhatwe

Page 122: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

wanttheprogramtodo.

Thisalreadyprovidesgreatbenefitsinmaintainabilityandreadability.However,wearestillmissingtheotherhalfofourprogram:rollingaverages.

Page 123: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ObservablerollingaveragesItmightnotbeimmediatelyobvioushowwecanmodelrollingaveragesasobservables.Whatweneedtokeepinmindisthatprettymuchanythingwecanthinkofasasequenceofvalues,wecanprobablymodelasanobservablesequence.

Rollingaveragesarenodifferent.Let’sforgetforamomentthatthepricesarecomingfromanetworkcallwrappedinanobservable.Let’simaginewehaveallvalueswecareaboutinaClojurevector:

(defvalues(range10))

Whatweneedisawaytoprocessthesevaluesinpartitions—orbuffers—ofsize5insuchawaythatonlyasinglevalueisdroppedateachinteraction.InClojure,wecanusethepartitionfunctionforthispurpose:

(doseq[buffer(partition51values)]

(prnbuffer))

(01234)

(12345)

(23456)

(34567)

(45678)

...

Thesecondargumenttothepartitionfunctioniscalledastepanditistheoffsetofhowmanyitemsshouldbeskippedbeforestartinganewpartition.Here,wesetitto1inordertocreatetheslidingwindoweffectweneed.

Thebigquestionthenis:canwesomehowleveragepartitionwhenworkingwithobservablesequences?

ItturnsoutthatRxJavahasatransformercalledbufferjustforthispurpose.Thepreviousexamplecanberewrittenasfollows:

(->(rx/seq->o(vec(range10)))

(.buffer51)

(rx/subscribe

(fn[price]

(prn(str"Value:"price)))))

TipAsmentionedpreviously,notallRxJava’sAPIisexposedthroughRxClojure,sohereweneedtouseinteroptoaccessthebuffermethodfromtheobservablesequence.

Asbefore,thesecondargumenttobufferistheoffset,butit’scalledskipintheRxJavadocumentation.IfyourunthisattheREPLyou’llseethefollowingoutput:

"Value:[0,1,2,3,4]"

"Value:[1,2,3,4,5]"

"Value:[2,3,4,5,6]"

"Value:[3,4,5,6,7]"

Page 124: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

"Value:[4,5,6,7,8]"

...

Thisisexactlywhatwewant.Theonlydifferenceisthatthebuffermethodwaitsuntilithasenoughelements—fiveinthiscase—beforeproceeding.

Now,wecangobacktoourprogramandincorporatethisideawithourmainfunction.Hereiswhatitlookslike:

(defn-main[&args]

(show!main-frame)

(let[price-obs(->(rx/flatmapmake-price-obs

(Observable/interval500

TimeUnit/MILLISECONDS))

(.publish))

sliding-buffer-obs(.bufferprice-obs51)]

(rx/subscribeprice-obs

(fn[price]

(text!price-label(str"Price:"price))))

(rx/subscribesliding-buffer-obs

(fn[buffer]

(text!running-avg-label(str"Runningaverage:"(avg

buffer)))))

(.connectprice-obs)))

Theprecedingsnippetworksbycreatingtwoobservables.Thefirstone,price-obs,wehadcreatedbefore.Thenewslidingbufferobservableiscreatedusingthebuffertransformeronprice-obs.

Wecan,then,independentlysubscribetoeachoneinordertoupdatethepriceandrollingaveragelabels.Runningtheprogramwilldisplaythesamescreenwe’veseenpreviously:

Youmighthavenoticedtwomethodcallswehadn’tseenbefore:publishandconnect.

Thepublishmethodreturnsaconnectableobservable.Thismeansthattheobservablewon’tstartemittingvaluesuntilitsconnectmethodhasbeencalled.Wedothisherebecausewewanttomakesurethatallthesubscribersreceiveallthevaluesemittedbytheoriginalobservable.

Inconclusion,withoutmuchadditionalcode,weimplementedallrequirementsinaconcise,declarativemannerthatiseasytomaintainandfollow.Wehavealsomadethepreviousroll-bufferfunctioncompletelyunnecessary.

ThefullsourcecodefortheCESversionoftheprogramisgivenhereforreference:

(nsstock-market-monitor.05frp-price-monitor-rolling-avg

Page 125: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

(:require[rx.lang.clojure.core:asrx]

[seesaw.core:refer:all])

(:import(java.util.concurrentTimeUnit)

(rxObservable)))

(native!)

(defmain-frame(frame:title"Stockpricemonitor"

:width200:height100

:on-close:exit))

(defprice-label(label"Price:-"))

(defrunning-avg-label(label"Runningaverage:-"))

(config!main-frame:content

(border-panel

:northprice-label

:centerrunning-avg-label

:border5))

(defnshare-price[company-code]

(Thread/sleep200)

(rand-int1000))

(defnavg[numbers]

(float(/(reduce+numbers)

(countnumbers))))

(defnmake-price-obs[_]

(rx/return(share-price"XYZ")))

(defn-main[&args]

(show!main-frame)

(let[price-obs(->(rx/flatmapmake-price-obs

(Observable/interval500

TimeUnit/MILLISECONDS))

(.publish))

sliding-buffer-obs(.bufferprice-obs51)]

(rx/subscribeprice-obs

(fn[price]

(text!price-label(str"Price:"price))))

(rx/subscribesliding-buffer-obs

(fn[buffer]

(text!running-avg-label(str"Runningaverage:"(avg

buffer)))))

(.connectprice-obs)))

Notehowinthisversionoftheprogram,wedidn’thavetouseashutdownhook.ThisisbecauseRxClojurecreatesdaemonthreads,whichareautomaticallyterminatedoncetheapplicationexits.

Page 126: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 127: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

SummaryInthischapter,wesimulatedareal-worldapplicationwithourstockmarketprogram.We’vewrittenitinasomewhattraditionalwayusingthreadpoolsandacustomqueueimplementation.WethenrefactoredittoaCESstyleusingRxClojure’sobservablesequences.

Theresultingprogramisshorter,simpler,andeasiertoreadonceyougetfamiliarwiththecoreconceptsofRxClojureandRxJava.

InthenextChapterwewillbeintroducedtocore.asyncinpreparationforimplementingourownbasicCESframework.

Page 128: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 129: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Chapter4.Introductiontocore.asyncLonggonearethedayswhenprogramswererequiredtodoonlyonethingatatime.Beingabletoperformseveraltasksconcurrentlyisatthecoreofthevastmajorityofmodernbusinessapplications.Thisiswhereasynchronousprogrammingcomesin.

Asynchronousprogramming—and,moregenerally,concurrency—isaboutdoingmorewithyourhardwareresourcesthanyoupreviouslycould.Itmeansfetchingdatafromthenetworkoradatabaseconnectionwithouthavingtowaitfortheresult.Or,perhaps,readinganExcelspreadsheetintomemorywhiletheusercanstilloperatethegraphicalinterface.Ingeneral,itimprovesasystem’sresponsiveness.

Inthischapter,wewilllookathowdifferentplatformshandlethisstyleofprogramming.Morespecifically,wewill:

Beintroducedtocore.async’sbackgroundandAPISolidifyourunderstandingofcore.asyncbyre-implementingthestockmarketapplicationintermsofitsabstractionsUnderstandhowcore.asyncdealswitherrorhandlingandbackpressureTakeabrieftourontransducers

Page 130: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

AsynchronousprogrammingandconcurrencyDifferentplatformshavedifferentprogrammingmodels.Forinstance,JavaScriptapplicationsaresingle-threadedandhaveaneventloop.Whenmakinganetworkcall,itiscommontoregisteracallbackthatwillbeinvokedatalaterstage,whenthatnetworkcallcompleteseithersuccessfullyorwithanerror.

Incontrast,whenwe’reontheJVM,wecantakefulladvantageofmultithreadingtoachieveconcurrency.ItissimpletospawnnewthreadsviaoneofthemanyconcurrencyprimitivesprovidedbyClojure,suchasfutures.

However,asynchronousprogrammingbecomescumbersome.Clojurefuturesdon’tprovideanativewayforustobenotifiedoftheircompletionatalaterstage.Inaddition,retrievingvaluesfromanot-yet-completedfutureisablockingoperation.Thiscanbeseenclearlyinthefollowingsnippet:

(defndo-something-important[]

(let[f(future(do(prn"Calculating…")

(Thread/sleep10000)))]

(prn"Perhapsthefuturehasdoneitsjob?")

(prn@f)

(prn"Youwillonlyseethisinabout10seconds…")))

(do-something-important)

Thesecondcalltoprintdereferencesthefuture,causingthemainthreadtoblocksinceithasn’tfinishedyet.Thisiswhyyouonlyseethelastprintafterthethreadinwhichthefutureisrunninghasfinished.Callbackscan,ofcourse,besimulatedbyspawningaseparatethreadtomonitorthefirstone,butthissolutionisclunkyatbest.

AnexceptiontothelackofcallbacksisGUIprogramminginClojure.MuchlikeJavaScript,ClojureSwingapplicationsalsopossessaneventloopandcanrespondtouserinputandinvokelisteners(callbacks)tohandlethem.

Anotheroptionisrewritingthepreviousexamplewithacustomcallbackthatispassedintothefuture:

(defndo-something-important[callback]

(let[f(future(let[answer42]

(Thread/sleep10000)

(callbackanswer)))]

(prn"Perhapsthefuturehasdoneitsjob?")

(prn"Youshouldseethisalmostimmediatelyandthenin10secs…")

f))

(do-something-important(fn[answer]

(prn"Futureisdone.Answeris"answer)))

Thistimetheorderoftheoutputsshouldmakemoresense.However,ifwereturnthefuturefromthisfunction,wehavenowaytogiveitanothercallback.Wehavelostthe

Page 131: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

abilitytoperformanactionwhenthefutureendsandarebacktohavingtodereferenceit,thusblockingthemainthreadagain—exactlywhatwewantedtoavoid.

TipJava8introducesanewclass,CompletableFuture,thatallowsregisteringacallbacktobeinvokedoncethefuturecompletes.Ifthat’sanoptionforyou,youcanuseinteroptomakeClojureleveragethenewclass.

Asyoumighthaverealized,CESiscloselyrelatedtoasynchronousprogramming:thestockmarketapplicationwebuiltinthepreviouschapterisanexampleofsuchaprogram.Themain—orUI—threadisneverblockedbytheObservablesfetchingdatafromthenetwork.Additionally,wewerealsoabletoregistercallbackswhensubscribingtothem.

Inmanyasynchronousapplications,however,callbacksarenotthebestwaytogo.Heavyuseofcallbackscanleadtowhatisknownascallbackhell.Clojureprovidesamorepowerfulandelegantsolution.

Inthenextfewsections,wewillexplorecore.async,aClojurelibraryforasynchronousprogramming,andhowitrelatestoReactiveProgramming.

Page 132: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 133: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

core.asyncIfyou’veeverdoneanyamountofJavaScriptprogramming,youhaveprobablyexperiencedcallbackhell.Ifyouhaven’t,thefollowingcodeshouldgiveyouagoodidea:

http.get('api/users/find?name='+name,function(user){

http.get('api/orders?userId='+user.id,function(orders){

orders.forEach(function(order){

container.append(order);

});

});

});

Thisstyleofprogrammingcaneasilygetoutofhand—insteadofwritingmorenatural,sequentialstepstoachievingatask,thatlogicisinsteadscatteredacrossmultiplecallbacks,increasingthedeveloper’scognitiveload.

Inresponsetothisissue,theJavaScriptcommunityreleasedseveralpromiseslibrariesthataremeanttosolvetheissue.Wecanthinkofpromisesasemptyboxeswecanpassintoandreturnfromourfunctions.Atsomepointinthefuture,anotherprocessmightputavalueinsidethisbox.

Asanexample,theprecedingsnippetcanbewrittenwithpromiseslikethefollowing:

http.get('api/users/find?name='+name)

.then(function(user){

returnhttp.get('api/orders?userId='+user.id);

})

.then(function(orders){

orders.forEach(function(order){

container.append(order);

});

});

Theprecedingsnippetshowshowusingpromisescanflattenyourcallbackpyramid,buttheydon’teliminatecallbacks.ThethenfunctionisapublicfunctionofthepromisesAPI.Itisdefinitelyastepintherightdirectionasthecodeiscomposableandeasiertoread.

Aswetendtothinkinsequencesofsteps,however,wewouldliketowritethefollowing:

user=http.get('api/users/find?name='+name);

orders=http.get('api/orders?userId='+user.id);

orders.forEach(function(order){

container.append(order);

});

Eventhoughthecodelookssynchronous,thebehaviorshouldbenodifferentfromthepreviousexamples.Thisisexactlywhatcore.asyncletsusdoinbothClojureandClojureScript.

Page 134: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

CommunicatingsequentialprocessesThecore.asynclibraryisbuiltonanoldidea.ThefoundationuponwhichitlieswasfirstdescribedbyTonyHoare—ofQuicksortfame—inhis1978paperCommunicatingSequentialProcesses(CSP;seehttp://www.cs.ucf.edu/courses/cop4020/sum2009/CSP-hoare.pdf).CSPhassincebeenextendedandimplementedinseverallanguages,thelatestofwhichbeingGoogle’sGoprogramminglanguage.

Itisbeyondthescopeofthisbooktogointothedetailsofthisseminalpaper,sowhatfollowsisasimplifieddescriptionofthemainideas.

InCSP,workismodeledusingtwomainabstractions:channelsandprocesses.CSPisalsomessage-drivenand,assuch,itcompletelydecouplestheproducerfromtheconsumerofthemessage.Itisusefultothinkofchannelsasblockingqueues.

Asimplisticapproachdemonstratingthesebasicabstractionsisasfollows:

(import'java.util.concurrent.ArrayBlockingQueue)

(defnproducer[c]

(prn"Takinganap")

(Thread/sleep5000)

(prn"Nowputtinganameinqueue…")

(.putc"Leo"))

(defnconsumer[c]

(prn"Attemptingtotakevaluefromqueuenow…")

(prn(str"Gotit.Hello"(.takec)"!")))

(defchan(ArrayBlockingQueue.10))

(future(consumerchan))

(future(producerchan))

RunningthiscodeintheREPLshouldshowusoutputsimilartothefollowing:

"Attemptingtotakevaluefromqueuenow…"

"Takinganap"

;;then5secondslater

"Nowputtinganameinquequeue…"

"Gotit.HelloLeo!"

Inordernottoblockourprogram,westartboththeconsumerandtheproducerintheirownthreadsusingafuture.Sincetheconsumerwasstartedfirst,wemostlikelywillseeitsoutputimmediately.However,assoonasitattemptstotakeavaluefromthechannel—orqueue—itwillblock.Itwillwaitforavaluetobecomeavailableandwillonlyproceedaftertheproducerisdonetakingitsnap—clearlyaveryimportanttask.

Now,let’scompareitwithasolutionusingcore.async.First,createanewleiningenprojectandaddadependencyonit:

[org.clojure/core.async"0.1.278.0-76b25b-alpha"]

Now,typethisintheREPLorinyourcorenamespace:

Page 135: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

(nscore-async-playground.core

(:require[clojure.core.async:refer[gochan<!>!timeout]]))

(defnprn-with-thread-id[s]

(prn(strs"-Threadid:"(.getId(Thread/currentThread)))))

(defnproducer[c]

(go(prn-with-thread-id"Takinganap")

(<!(timeout5000))

(prn-with-thread-id"Nowputtinganameinquequeue…")

(>!c"Leo")))

(defnconsumer[c]

(go(prn-with-thread-id"Attemptingtotakevaluefromqueuenow…")

(prn-with-thread-id(str"Gotit.Hello"(<!c)"!"))))

(defc(chan))

(consumerc)

(producerc)

Thistimeweareusingahelperfunction,prn-with-thread-id,whichappendsthecurrentthreadIDtotheoutputstring.Iwillexplainwhyshortly,butapartfromthat,theoutputwillhavebeenequivalenttothepreviousone:

"Attemptingtotakevaluefromqueuenow…-Threadid:43"

"Takinganap-Threadid:44"

"Nowputtinganameinquequeue…-Threadid:48"

"Gotit.HelloLeo!-Threadid:48"

Structurally,bothsolutionslookfairlysimilar,butsinceweareusingquiteafewnewfunctionshere,let’sbreakitdown:

chanisafunctionthatcreatesacore.asyncchannel.Asmentionedpreviously,itcanbethoughtofasaconcurrentblockingqueueandisthemainabstractioninthelibrary.Bydefaultchancreatesanunboundedchannel,butcore.asyncprovidesmanymoreusefulchannelconstructors,afewofwhichwe’llbeusinglater.timeoutisanothersuchchannelconstructor.Itgivesusachannelthatwillwaitforagivenamountoftimebeforereturningniltothetakingprocess,closingitselfimmediatelyafterward.Thisisthecore.asyncequivalentofThread/sleep.Thefunctions>!and<!areusedtoputandtakevaluesfromachannel,respectively.Thecaveatisthattheyhavetobeusedinsideagoblock,aswewillexplainlater.goisamacrothattakesabodyofexpressions—whichformagoblock—andcreateslightweightprocesses.Thisiswherethemagichappens.Insideagoblock,anycallsto>!and<!thatwouldordinarilyblockwaitingforvaluestobeavailableinchannelsareinsteadparked.Parkingisaspecialtypeofblockingusedinternallyinthestatemachineofcore.async.TheblogpostbyHueyPetersencoversthisstatemachineindepth(seehttp://hueypetersen.com/posts/2013/08/02/the-state-machines-of-core-async/).

GoblocksaretheveryreasonforwhichIchosetoprintthethreadIDsinourexample.Ifwelookclosely,we’llrealizethatthelasttwostatementswereexecutedinthesamethread

Page 136: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

—thisisn’ttrue100percentofthetimeasconcurrencyisinherentlynon-deterministic.Thisisafundamentaldifferencebetweencore.asyncandsolutionsusingthreads/futures.

Threadscanbeexpensive.OntheJVM,theirdefaultstacksizeis512kilobytes—configurableviathe-XssJVMstartupoption.Whendevelopingahighlyconcurrentsystem,creatingthousandsofthreadscanquicklydraintheresourcesofthemachinetheapplicationisrunningon.

core.asyncacknowledgesthislimitationandgivesuslightweightprocesses.Internally,theydoshareathreadpool,butinsteadofwastefullycreatingathreadpergoblock,threadsarerecycledandreusedwhenaput/takeoperationiswaitingforavaluetobecomeavailable.

TipAtthetimeofwriting,thethreadpoolusedbycore.asyncdefaultstothenumberofavailableprocessorsx2,+42.So,amachinewitheightprocessorswillhaveapoolwith58threads.

Therefore,itiscommonforcore.asyncapplicationstohavedozensofthousandsoflightweightprocesses.Theyareextremelycheaptocreate.

SincethisisabookonReactiveProgramming,thequestionthatmightbeinyourheadnowis:canwebuildreactiveapplicationsusingcore.async?Theshortanswerisyes,wecan!Toproveit,wewillrevisitourstockmarketapplicationandrewriteitusingcore.async.

Page 137: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 138: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Rewritingthestockmarketapplicationwithcore.asyncByusinganexamplewearefamiliarwith,weareabletofocusonthedifferencesbetweenallapproachesdiscussedsofar,withoutgettingsidetrackedwithnew,specificdomainrules.

Beforewediveintotheimplementation,let’squicklydoanoverviewofhowoursolutionshouldwork.

Justlikeinourpreviousimplementations,wehaveaservicefromwhichwecanqueryshareprices.Whereourapproachdiffers,however,isadirectconsequenceofhowcore.asyncchannelswork.

Onagivenschedule,wewouldliketowritethecurrentpricetoacore.asyncchannel.Thismightlooklikeso:

Thisprocesswillcontinuouslyputpricesintheoutchannel.Weneedtodotwothingswitheachprice:displayitanddisplaythecalculatedslidingwindow.Sincewelikeourfunctionsdecoupled,wewillusetwogoblocks,oneforeachtask:

Holdon.Thereseemstobesomethingoffwithourapproach.Oncewetakeapricefrom

Page 139: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

theoutputchannel,itisnotavailableanylongertobetakenbyothergoblocks,so,insteadofcalculatingtheslidingwindowstartingwith10,ourfunctionendsupgettingthesecondvalue,20.Withthisapproach,wewillendupwithaslidingwindowthatcalculatesaslidingwindowwithroughlyeveryotheritem,dependingonhowconsistenttheinterleavingbetweenthegoblocksis.

Clearly,thisisnotwhatwewant,butithelpsusthinkabouttheproblemalittlemore.Thesemanticsofcore.asyncpreventusfromreadingavaluefromachannelmorethanonce.Mostofthetime,thisbehaviorisjustfine—especiallyifyouthinkofthemasqueues.Sohowcanweprovidethesamevaluetobothfunctions?

Tosolvethisproblem,wewilltakeadvantageofanotherchannelconstructorprovidedbycore.asynccalledbroadcast.Asthenameimplies,broadcastreturnsachannel,which,whenwrittento,writesitsvalueintothechannelspassedtoitasarguments.Effectively,thischangesourhigh-levelpicturetosomethinglikethefollowing:

Insummary,wewillhaveagoloopwritingpricestothisbroadcastchannel,whichwillthenforwarditsvaluestothetwochannelsfromwhichwewillbeoperating:pricesandtheslidingwindow.

Withthegeneralideainplace,wearereadytodiveintothecode.

Page 140: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ImplementingtheapplicationcodeWealreadyhaveaprojectdependingoncore.asyncthatwecreatedintheprevioussection,sowe’llbeworkingoffthat.Let’sstartbyaddinganextradependencyonseesawtoyourproject.cljfile:

:dependencies[[org.clojure/clojure"1.5.1"]

[org.clojure/core.async"0.1.278.0-76b25b-alpha"]

[seesaw"1.4.4"]]

Next,createafilecalledstock_market.cljinthesrcdirectoryandaddthisnamespacedeclaration:

(nscore-async-playground.stock-market

(:require[clojure.core.async

:refer[gochan<!>!timeoutgo-loopmap>]:asasync])

(:require[clojure.core.async.lab:refer[broadcast]])

(:use[seesaw.core]))

ThismightbeagoodpointtorestartyourREPLifyouhaven’tdoneso.Don’tworryaboutanyfunctionswehaven’tseenyet.We’llgetafeelfortheminthissection.

TheGUIcoderemainslargelyunchanged,sonoexplanationshouldbenecessaryforthenextsnippet:

(native!)

(defmain-frame(frame:title"Stockpricemonitor"

:width200:height100

:on-close:exit))

(defprice-label(label"Price:-"))

(defrunning-avg-label(label"Runningaverage:-"))

(config!main-frame:content

(border-panel

:northprice-label

:centerrunning-avg-label

:border5))

(defnshare-price[company-code]

(Thread/sleep200)

(rand-int1000))

(defnavg[numbers]

(float(/(reduce+numbers)

(countnumbers))))

(defnroll-buffer[buffervalbuffer-size]

(let[buffer(conjbufferval)]

(if(>(countbuffer)buffer-size)

(popbuffer)

buffer)))

(defnmake-sliding-buffer[buffer-size]

Page 141: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

(let[buffer(atomclojure.lang.PersistentQueue/EMPTY)]

(fn[n]

(swap!bufferroll-buffernbuffer-size))))

(defsliding-buffer(make-sliding-buffer5))

Theonlydifferenceisthatnowwehaveasliding-bufferfunctionthatreturnsawindowofdata.Thisisincontrastwithouroriginalapplication,wheretherolling-avgfunctionwasresponsibleforbothcreatingthewindowandcalculatingtheaverage.Thisnewdesignismoregeneralasitmakesthisfunctioneasiertoreuse.Theslidinglogicisthesame,however.

Next,wehaveourmainapplicationlogicusingcore.async:

(defnbroadcast-at-interval[msecstask&ports]

(go-loop[out(applybroadcastports)]

(<!(timeoutmsecs))

(>!out(task))

(recurout)))

(defn-main[&args]

(show!main-frame)

(let[prices-ch(chan)

sliding-buffer-ch(map>sliding-buffer(chan))]

(broadcast-at-interval500#(share-price"XYZ")prices-chsliding-

buffer-ch)

(go-loop[]

(when-let[price(<!prices-ch)]

(text!price-label(str"Price:"price))

(recur)))

(go-loop[]

(when-let[buffer(<!sliding-buffer-ch)]

(text!running-avg-label(str"Runningaverage:"(avgbuffer)))

(recur)))))

Let’swalkthroughthecode.

Thefirstfunction,broadcast-at-interval,isresponsibleforcreatingthebroadcastingchannel.Itreceivesavariablenumberofarguments:anumberofmillisecondsdescribingtheinterval,thefunctionrepresentingthetasktobeexecuted,andasequenceofoneofmoreoutputchannels.Thesechannelsareusedtocreatethebroadcastingchanneltowhichthegoloopwillbewritingprices.

Next,wehaveourmainfunction.Theletblockiswheretheinterestingbitsare.Aswediscussedinourhigh-leveldiagrams,weneedtwooutputchannels:oneforpricesandonefortheslidingwindow.Theyarebothcreatedinthefollowing:

...

(let[prices-ch(chan)

sliding-buffer-ch(map>sliding-buffer(chan))]

...

prices-chshouldbeself-explanatory;however,sliding-buffer-chisusingafunctionwehaven’tencounteredbefore:map>.Thisisyetanotherusefulchannelconstructorin

Page 142: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

core.async.Ittakestwoarguments:afunctionandatargetchannel.Itreturnsachannelthatappliesthisfunctiontoeachvaluebeforewritingittothetargetchannel.Anexamplewillhelpillustratehowitworks:

(defc(map>sliding-buffer(chan10)))

(go(doseq[n(range10)]

(>!cn)))

(go(doseq[n(range10)]

(prn(vec(<!c)))))

;;[0]

;;[01]

;;[012]

;;[0123]

;;[01234]

;;[12345]

;;[23456]

;;[34567]

;;[45678]

;;[56789]

Thatis,wewriteapricetothechannelandgetaslidingwindowontheotherend.Finally,wecreatethetwogoblockscontainingthesideeffects.Theyloopindefinitely,gettingvaluesfrombothchannelsandupdatingtheuserinterface.

Youcanseeitinactionbyrunningtheprogramfromtheterminal:

$leinrun-mcore-async-playground.stock-market

Page 143: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 144: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ErrorhandlingBackinChapter2,ALookatReactiveExtensions,welearnedhowReactiveExtensionstreatserrorsandexceptions.Itprovidesarichsetofcombinatorstodealwithexceptionalcasesandarestraightforwardtouse.

Despitebeingapleasuretoworkwith,core.asyncdoesn’tshipwithmuchsupportforexceptionhandling.Infact,ifwewriteourcodewithonlythehappypathinmindwedon’tevenknowanerroroccurred!

Let’shavealookatanexample:

(defnget-data[]

(throw(Exception."Badthingshappen!")))

(defnprocess[]

(let[result(chan)]

;;dosomeprocessing…

(go(>!result(get-data)))

result))

Intheprecedingsnippet,weintroducedtwofunctions:

get-datasimulatesafunctionthatfetchesdatafromthenetworkoranin-memorycache.Inthiscaseitsimplythrowsanexception.processisafunctionthatdependsonget-datatodosomethinginterestingandputstheresultintoachannel,whichisreturnedattheend.

Let’swatchwhathappenswhenweputthistogether:

(go(let[result(<!(->>(process"data")

(map>#(*%%))

(map>#(prn%))))]

(prn"resultis:"result)))

Nothinghappens.Zero,zip,zilch,nada.

Thisispreciselytheproblemwitherrorhandlingincore.async:bydefault,ourexceptionsareswallowedbythegoblockasitrunsonaseparatethread.Weareleftinthisstatewherewedon’treallyknowwhathappened.

Notallislost,however.DavidNolenoutlinedonhisblogapatternfordealingwithsuchasynchronousexceptions.Itonlyrequiresafewextralinesofcode.

Westartbydefiningahelperfunctionandmacro—thiswouldprobablyliveinautilitynamespacewerequireanywhereweusecore.async:

(defnthrow-err[e]

(when(instance?Throwablee)(throwe))

e)

(defmacro<?[ch]

`(throw-err(async/<!~ch)))

Page 145: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Thethrow-errfunctionreceivesavalueand,ifit’sasubclassofThrowable,itisthrown.Otherwise,itissimplyreturned.

Themacro<?isessentiallyadrop-inreplacementfor<!.Infact,ituses<!togetthevalueoutofthechannelbutpassesittothrow-errfirst.

Withtheseutilitiesinplace,weneedtomakeacoupleofchanges,firsttoourprocessfunction:

(defnprocess[]

(let[result(chan)]

;;dosomeprocessing…

(go(>!result(try(get-data)

(catchExceptione

e))))

result))

Theonlychangeisthatwewrappedget-datainatry/catchblock.Lookcloselyatthecatchblock:itsimplyreturnstheexception.

Thisisimportantasweneedtoensuretheexceptiongetsputintothechannel.

Next,weupdateourconsumercode:

(go(try(let[result(<?(->>(process"data")

(map>#(*%%))

(map>#(prn%))))]

(prn"resultis:"result))

(catchExceptione

(prn"Oops,anerrorhappened!Webetterdosomethingaboutit

here!"))))

;;"Oops,anerrorhappened!Webetterdosomethingaboutithere!"

Thistimeweuse<?inplaceof<!.Thismakessenseasitwillrethrowanyexceptionsfoundinthechannel.Asaresultwecannowuseasimpletry/catchtoregaincontroloverourexceptions.

Page 146: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 147: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

BackpressureThemainmechanismbywhichcore.asyncallowsforcoordinatingbackpressureisbuffering.core.asyncdoesn’tallowunboundedbuffersasthiscanbeasourceofbugsandaresourcehog.

Instead,wearerequiredtothinkhardaboutourapplication’suniqueneedsandchooseanappropriatebufferingstrategy.

Page 148: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

FixedbufferThisisthesimplestformofbuffering.Itisfixedtoachosennumbern,allowingproducerstoputitemsinthechannelwithouthavingtowaitforconsumers:

(defresult(chan(buffer5)))

(go-loop[]

(<!(async/timeout1000))

(when-let[x(<!result)]

(prn"Gotvalue:"x)

(recur)))

(go(doseq[n(range5)]

(>!resultn))

(prn"Doneputtingvalues!")

(close!result))

;;"Doneputtingvalues!"

;;"Gotvalue:"0

;;"Gotvalue:"1

;;"Gotvalue:"2

;;"Gotvalue:"3

;;"Gotvalue:"4

Intheprecedingexample,wecreatedabufferofsize5andstartedagolooptoconsumevaluesfromit.Thegoloopusesatimeoutchanneltodelayitsstart.

Then,westartanothergoblockthatputsnumbersfrom0to4intotheresultchannelandprintstotheconsoleonceit’sdone.

Bythen,thefirsttimeoutwillhaveexpiredandwewillseethevaluesprintedtotheREPL.

Nowlet’swatchwhathappensifthebufferisn’tlargeenough:

(defresult(chan(buffer2)))

(go-loop[]

(<!(async/timeout1000))

(when-let[x(<!result)]

(prn"Gotvalue:"x)

(recur)))

(go(doseq[n(range5)]

(>!resultn))

(prn"Doneputtingvalues!")

(close!Result))

;;"Gotvalue:"0

;;"Gotvalue:"1

;;"Gotvalue:"2

;;"Doneputtingvalues!"

;;"Gotvalue:"3

;;"Gotvalue:"4

Thistimeourbuffersizeis2buteverythingelseisthesame.Asyoucanseethegoloopfinishesmuchlaterasitattemptedtoputanothervalueintheresultchannelandwas

Page 149: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

blocked/parkedsinceitsbufferwasfull.

Aswithmostthings,thismightbeOKbutifwearenotwillingtoblockafastproducerjustbecausewecan’tconsumeitsitemsfastenough,wemustlookforanotheroption.

Page 150: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

DroppingbufferAdroppingbufferalsohasafixedsize.However,insteadofblockingproducerswhenitisfull,itsimplyignoresanynewitemsasshownhere:

(defresult(chan(dropping-buffer2)))

(go-loop[]

(<!(async/timeout1000))

(when-let[x(<!result)]

(prn"Gotvalue:"x)

(recur)))

(go(doseq[n(range5)]

(>!resultn))

(prn"Doneputtingvalues!")

(close!result))

;;"Doneputtingvalues!"

;;"Gotvalue:"0

;;"Gotvalue:"1

Asbefore,westillhaveabufferofsizetwo,butthistimetheproducerendsquicklywithoutevergettingblocked.Thedropping-buffersimplyignoredallitemsoveritslimit.

Page 151: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

SlidingbufferAdrawbackofdroppingbuffersisthatwemightnotbeprocessingthelatestitemsatagiventime.Forthetimeswhereprocessingthelatestinformationisamust,wecanuseaslidingbuffer:

(defresult(chan(sliding-buffer2)))

(go-loop[]

(<!(async/timeout1000))

(when-let[x(<!result)]

(prn"Gotvalue:"x)

(recur)))

(go(doseq[n(range5)]

(>!resultn))

(prn"Doneputtingvalues!")

(close!result))

;;"Doneputtingvalues!"

;;"Gotvalue:"3

;;"Gotvalue:"4

Asbefore,weonlygettwovaluesbuttheyarethelatestonesproducedbythegoloop.

Whenthelimitoftheslidingbufferisoverrun,core.asyncdropstheoldestitemstomakeroomforthenewestones.Iendupusingthisbufferingstrategymostofthetime.

Page 152: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 153: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

TransducersBeforewefinishupwithourcore.asyncportionofthebook,itwouldbeunwiseofmenottomentionwhatiscomingupinClojure1.7aswellashowthisaffectscore.async.

Atthetimeofthiswriting,Clojure’slatestreleaseis1.7.0-alpha5—andeventhoughitisanalpharelease,alotofpeople—myselfincluded—arealreadyusingitinproduction.

Assuch,afinalversioncouldbejustaroundthecornerandperhapsbythetimeyoureadthis,1.7finalwillbeoutalready.

Oneofthebigchangesinthisupcomingreleaseistheintroductionoftransducers.Wewillnotcoverthenutsandboltsofitherebutratherfocusonwhatitmeansatahigh-levelwithexamplesusingbothClojuresequencesandcore.asyncchannels.

IfyouwouldliketoknowmoreIrecommendCarinMeier’sGreenEggsandTransducersblogpost(http://gigasquidsoftware.com/blog/2014/09/06/green-eggs-and-transducers/).It’sagreatplacetostart.

Additionally,theofficialClojuredocumentationsiteonthesubjectisanotherusefulresource(http://clojure.org/transducers).

Let’sgetstartedbycreatinganewleiningenproject:

$leinnewcore-async-transducers

Now,openyourproject.cljfileandmakesureyouhavetherightdependencies:

...

:dependencies[[org.clojure/clojure"1.7.0-alpha5"]

[org.clojure/core.async"0.1.346.0-17112a-alpha"]]

...

Next,fireupaREPLsessionintheprojectrootandrequirecore.async,whichwewillbeusingshortly:

$leinrepl

user>(require'[clojure.core.async:refer[gochanmap<filter<into>!<!

go-loopclose!pipe]])

Wewillstartwithafamiliarexample:

(->>(range10)

(mapinc);;createsanewsequence

(filtereven?);;createsanewsequence

(prn"resultis"))

;;"resultis"(246810)

TheprecedingsnippetisstraightforwardandhighlightsaninterestingpropertyofwhathappenswhenweapplycombinatorstoClojuresequences:eachcombinatorcreatesanintermediatesequence.

Inthepreviousexample,weendedupwiththreeintotal:theonecreatedbyrange,theonecreatedbymap,andfinallytheonecreatedbyfilter.Mostofthetime,thiswon’t

Page 154: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

reallybeanissuebutforlargesequencesthismeansalotofunnecessaryallocation.

StartinginClojure1.7,thepreviousexamplecanbewrittenlikeso:

(defxform

(comp(mapinc)

(filtereven?)));;nointermediatesequencecreated

(->>(range10)

(sequencexform)

(prn"resultis"))

;;"resultis"(246810)

TheClojuredocumentationdescribestransducersascomposablealgorithmictransformations.Let’sseewhythatis.

Inthenewversion,awholerangeofthecoresequencecombinators,suchasmapandfilter,havegainedanextraarity:ifyoudon’tpassitacollection,itinsteadreturnsatransducer.

Inthepreviousexample,(mapinc)returnsatransducerthatknowshowtoapplythefunctioninctoelementsofasequence.Similarly,(filtereven?)returnsatransducerthatwilleventuallyfilterelementsofasequence.Neitherofthemdoanythingyet,theysimplyreturnfunctions.

Thisisinterestingbecausetransducersarecomposable.Webuildlargerandmorecomplextransducersbyusingsimplefunctioncomposition:

(defxform

(comp(mapinc)

(filtereven?)))

Oncewehaveourtransducerready,wecanapplyittoacollectioninafewdifferentways.Forthisexample,wechosesequenceasitwillreturnalazysequenceoftheapplicationsofthegiventransducertotheinputsequence:

(->>(range10)

(sequencexform)

(prn"resultis"))

;;"resultis"(246810)

Aspreviouslyhighlighted,thiscodedoesnotcreateintermediatesequences;transducersextracttheverycoreofthealgorithmictransformationathandandabstractsitawayfromhavingtodealwithsequencesdirectly.

Page 155: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Transducersandcore.asyncWemightnowbeaskingourselves“Whatdotransducershavetodowithcore.async?”

Itturnsoutthatoncewe’reabletoextractthecoreofthesetransformationsandputthemtogetherusingsimplefunctioncomposition,thereisnothingstoppingusfromusingtransducerswithdatastructuresotherthansequences!

Let’srevisitourfirstexampleusingstandardcore.asyncfunctions:

(defresult(chan10))

(deftransformed

(->>result

(map<inc);;createsanewchannel

(filter<even?);;createsanewchannel

(into[])))

(go

(prn"resultis"(<!transformed)))

(go

(doseq[n(range10)]

(>!resultn))

(close!result))

;;"resultis"[246810]

Thiscodeshouldlookfamiliarbynow:it’sthecore.asyncequivalentofthesequence-onlyversionshownearlier.Asbefore,wehaveunnecessaryallocationshereaswell,exceptthatthistimewe’reallocatingchannels.

Withthenewsupportfortransducers,core.asynccantakeadvantageofthesametransformationdefinedearlier:

(defresult(chan10))

(defxform

(comp(mapinc)

(filtereven?)));;nointermediatechannelscreated

(deftransformed(->>(piperesult(chan10xform))

(into[])))

(go

(prn"resultis"(<!transformed)))

(go

(doseq[n(range10)]

(>!resultn))

(close!result))

;;"resultis"[246810]

Page 156: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Thecoderemainslargelyunchangedexceptwenowusethesamexformtransformationdefinedearlierwhencreatinganewchannel.It’simportanttonotethatwedidnothavetousecore.asynccombinators—infactalotofthesecombinatorshavebeendeprecatedandwillberemovedinfutureversionsofcore.async.

Thefunctionsmapandfilterusedtodefinexformarethesameonesweusedpreviously,thatis,theyarecoreClojurefunctions.

Thisisthenextbigadvantageofusingtransducers:byremovingtheunderlyingdatastructurefromtheequationviatransducers,librariessuchascore.asynccanreuseClojure’scorecombinatorstopreventunnecessaryallocationandcodeduplication.

It’snottoofarfetchedtoimagineotherframeworkslikeRxClojurecouldtakeadvantageoftransducersaswell.Allofthemwouldbeabletousethesamecorefunctionacrosssubstantiallydifferentdatastructuresandcontexts:sequences,channels,andObervables.

TipTheconceptofextractingtheessenceofcomputationsdisregardingtheirunderlyingdatastructuresisanexcitingtopicandhasbeenseenbeforeintheHaskellcommunity,althoughtheydealwithlistsspecifically.

TwopapersworthmentioningonthesubjectareStreamFusion[11]byDuncanCoutts,RomanLeshchinskiyandDonStewartandTransformingprogramstoeliminatetrees[12]byPhilipWadler.Therearesomeoverlapssothereadermightfindtheseinteresting.

Page 157: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 158: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

SummaryBynow,Ihopetohaveprovedthatyoucanwritereactiveapplicationsusingcore.async.It’sanextremelypowerfulandflexibleconcurrencymodelwitharichAPI.Ifyoucandesignyoursolutionintermsofqueues,mostlikelycore.asyncisthetoolyouwanttoreachfor.

ThisversionofthestockmarketapplicationisshorterandsimplerthantheversionusingonlythestandardJavaAPIwedevelopedearlierinthisbook—forinstance,wedidn’thavetoworryaboutthreadpools.Ontheotherhand,itfeelslikeitisalittlemorecomplexthantheversionimplementedusingReactiveExtensionsinChapter3,AsynchronousProgrammingandNetworking.

Thisisbecausecore.asyncoperatesatalowerlevelofabstractionwhencomparedtootherframeworks.Thisbecomesespeciallyobviousinourapplicationaswehadtoworryaboutcreatingbroadcastingchannels,goloops,andsoon—allofwhichcanbeconsideredincidentalcomplexity,notdirectlyrelatedtotheproblemathand.

core.asyncdoes,however,provideanexcellentfoundationforbuildingourownCESabstractions.Thisiswhatwewillbeexploringnext.

Page 159: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 160: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Chapter5.CreatingYourOwnCESFrameworkwithcore.asyncInthepreviouschapter,itwasalludedtothatcore.asyncoperatesatalowerlevelofabstractionwhencomparedtootherframeworkssuchasRxClojureorRxJava.

Thisisbecausemostofthetimewehavetothinkcarefullyaboutthechannelswearecreatingaswellaswhattypesandsizesofbufferstouse,whetherweneedpub/subfunctionality,andsoon.

Notallapplicationsrequiresuchlevelofcontrol,however.Nowthatwearefamiliarwiththemotivationsandmainabstractionsofcore.asyncwecanembarkintowritingaminimalCESframeworkusingcore.asyncastheunderlyingfoundation.

Bydoingso,weavoidhavingtothinkaboutthreadpoolmanagementastheframeworktakescareofthatforus.

Inthischapter,wewillcoverthefollowingtopics:

BuildingaCESframeworkusingcore.asyncasitsunderlyingconcurrencystrategyBuildinganapplicationthatusesourCESframeworkUnderstandingthetrade-offsofthedifferentapproachespresentedsofar

Page 161: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

AminimalCESframeworkBeforewegetstartonthedetails,weshoulddefineatahighlevelwhatminimalmeans.

Let’sstartwiththetwomainabstractionsourframeworkwillprovide:behaviorsandeventstreams.

IfyoucanrecallfromChapter1,WhatisReactiveProgramming?,behaviorsrepresentcontinuous,time-varyingvaluessuchastimeoramousepositionbehavior.Eventstreams,ontheotherhand,representdiscreteoccurrencesatapointintimeT,suchaskeypress.

Next,weshouldthinkaboutwhatkindsofoperationswewouldliketosupport.Behaviorsarefairlysimplesoattheveryminimumweneedto:

CreatenewbehaviorsRetrievethecurrentvalueofabehaviorConvertabehaviorintoaneventstream

Eventstreamshavemoreinterestinglogicinplayandweshouldatleastsupporttheseoperations:

Push/deliveravaluedownthestreamCreateastreamfromagivenintervalTransformthestreamwiththemapandfilteroperationsCombinestreamswithflatmapSubscribetoastream

ThisisasmallsubsetbutbigenoughtodemonstratetheoverallarchitectureofaCESframework.Oncewe’redone,we’lluseittobuildasimpleexample.

Page 162: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ClojureorClojureScript?Herewe’llshiftgearsandaddanotherrequirementtoourlittlelibrary:itshouldworkbothinClojureandClojureScript.Atfirst,thismightsoundlikeatoughrequirement.However,rememberthatcore.async—thefoundationofourframework—worksbothontheJVMandinJavaScript.Thismeanswehavealotlessworktodotomakeithappen.

Itdoesmean,however,thatweneedtobecapableofproducingtwoartifactsfromourlibrary:ajarfileandaJavaScriptfile.Luckily,thereareleiningenplugins,whichhelpusdothatandwewillbeusingacoupleofthem:

lein-cljsbuild(seehttps://github.com/emezeske/lein-cljsbuild):LeiningenplugintomakebuildingClojureScripteasycljx(seehttps://github.com/lynaghk/cljx):ApreprocessorusedtowriteportableClojurecodebases,thatis,writeasinglefileandoutputboth.cljand.cljsfiles

Youdon’tneedtounderstandtheselibrariesingreatdetail.Weareonlyusingtheirbasicfunctionalityandwillbeexplainingthebitsandpiecesasweencounterthem.

Let’sgetstartedbycreatinganewleiningenproject.We’llcallourframeworkrespondent—oneofthemanysynonymsforthewordreactive:

$leinnewrespondent

Weneedtomakeafewchangestotheproject.cljfiletoincludethedependenciesandconfigurationswe’llbeusing.First,makesuretheprojectdependencieslooklikethefollowing:

:dependencies[[org.clojure/clojure"1.5.1"]

[org.clojure/core.async"0.1.303.0-886421-alpha"]

[org.clojure/clojurescript"0.0-2202"]]

Thereshouldbenosurpriseshere.Stillintheprojectfile,addthenecessaryplugins:

:plugins[[com.keminglabs/cljx"0.3.2"]

[lein-cljsbuild"1.0.3"]]

Thesearethepluginsthatwe’vementionedpreviously.Bythemselvestheydon’tdomuch,however,andneedtobeconfigured.

Forcljx,addthefollowingtotheprojectfile:

:cljx{:builds[{:source-paths["src/cljx"]

:output-path"target/classes"

:rules:clj}

{:source-paths["src/cljx"]

:output-path"target/classes"

:rules:cljs}]}

:hooks[cljx.hooks]

Theprevioussnippetdeservessomeexplanation.cljxallowsustowritecodethatisportablebetweenClojureandClojureScriptbyplacingannotationsitspreprocessorcanunderstand.Wewillseelaterwhattheseannotationslooklike,butthischunkof

Page 163: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

configurationtellscljxwheretofindtheannotatedfilesandwheretooutputthemoncethey’reprocessed.

Forexample,basedontheprecedingrules,ifwehaveafilecalledsrc/cljx/core.cljxandwerunthepreprocessorwewillendupwiththetarget/classes/core.cljandtarget/classes/core.cljsoutputfiles.Thehooks,ontheotherhand,aresimplyaconvenientwaytoautomaticallyruncljxwheneverwestartaREPLsession.

Thenextpartoftheconfigurationisforcljsbuild:

:cljsbuild

{:builds[{:source-paths["target/classes"]

:compiler{:output-to"target/main.js"}}]}

cljsbuildprovidesleiningentaskstocompileClojuresriptsourcecodeintoJavaScript.Weknowfromourprecedingcljxconfigurationthatthesource.cljsfileswillbeundertarget/classes,soherewe’resimplytellingcljsbuildtocompileallClojureScriptfilesinthatdirectoryandspitthecontentstotarget/main.js.Thisisthelastpieceneededfortheprojectfile.

Goaheadanddeletethesefilescreatedbytheleiningentemplateaswewon’tbeusingthem:

$rmsrc/respondent/core.clj

$rmtest/respondent/core_test.clj

Then,createanewcore.cljxfileundersrc/cljx/respondent/andaddthefollowingnamespacedeclaration:

(nsrespondent.core

(:refer-clojure:exclude[filtermapdeliver])

#+clj

(:import[clojure.langIDeref])

#+clj

(:require[clojure.core.async:asasync

:refer[gogo-loopchan<!>!timeout

map>filter>close!multtapuntap]])

#+cljs

(:require[cljs.core.async:asasync

:refer[chan<!>!timeoutmap>filter>

close!multtapuntap]])

#+cljs

(:require-macros[respondent.core:refer[behavior]]

[cljs.core.async.macros:refer[gogo-loop]]))

Here,westartseeingcljxannotations.cljxissimplyatextpreprocessor,sowhenitisprocessingafileusingcljrules—asseenintheconfiguration—itwillkeepthes-expressionsprecededbytheannotation#+cljintheoutputfile,whileremovingtheonesprefixedby#+cljs.Thereverseprocesshappenswhenusingcljsrules.

ThisisnecessarybecausemacrosneedtobecompiledontheJVM,sotheyhavetobe

Page 164: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

includedseparatelyusingthe:require-macrosnamespaceoptionwhenusingClojureScript.Don’tworryaboutthecore.asyncfunctionswehaven’tencounteredbefore;theywillbeexplainedasweusethemtobuildourframework.

Also,notehowweareexcludingfunctionsfromtheClojurestandardAPIaswewishtousethesamenamesanddon’twantanyundesirednamingcollisions.

Thissectionsetusupwithanewprojectandthepluginsandconfigurationsneededforourframework.We’rereadytostartimplementingit.

Page 165: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

DesigningthepublicAPIOneoftherequirementsforbehaviorsweagreedonistheabilitytoturnagivenbehaviorintoaneventstream.Acommonwayofdoingthisisbysamplingabehaviorataspecificinterval.Ifwetakethemousepositionbehaviorasanexample,bysamplingiteveryxsecondswegetaneventstream,whichwillemitthecurrentmousepositionatdiscretepointsintime.

Thisleadstothefollowingprotocol:

(defprotocolIBehavior

(sample[binterval]

"TurnsthisBehaviorintoanEventStreamfromthesampledvaluesatthe

giveninterval"))

Ithasasinglefunction,sample,whichwedescribedintheprecedingcode.Therearemorethingsweneedtodowithabehavior,butfornowthiswillsuffice.

OurnextmainabstractionisEventStream,which—basedontherequirementsseenpreviously—leadstothefollowingprotocol:

(defprotocolIEventStream

(map[sf]

"Returnsanewstreamcontainingtheresultofapplyingf

tothevaluesins")

(filter[spred]

"Returnsanewstreamcontainingtheitemsfroms

forwhichpredreturnstrue")

(flatmap[sf]

"TakesafunctionffromvaluesinstoanewEventStream.

ReturnsanEventStreamcontainingvaluesfromallunderlyingstreams

combined.")

(deliver[svalue]

"Deliversavaluetothestreams")

(completed?[s]

"Returnstrueifthisstreamhasstoppedemittingvalues.False

otherwise."))

Thisgivesusafewbasicfunctionstotransformandqueryaneventstream.Itdoesleaveouttheabilitytosubscribetoastream.Don’tworry,Ididn’tforgetit!

Although,itiscommontosubscribetoaneventstream,theprotocolitselfdoesn’tmandateitandthisisbecausetheoperationfitsbestinitsownprotocol:

(defprotocolIObservable

(subscribe[obsf]"Registeracallbacktobeinvokedwhentheunderlying

sourcechanges.

Returnsatokenthesubscribercanusetocancelthesubscription."))

Asfarassubscriptionsgo,itisusefultohaveawayofunsubscribingfromastream.Thiscanbeimplementedinacoupleofways,butdocstringoftheprecedingfunctionhintsataspecificone:atokenthatcanbeusedtounsubscribefromastream.Thisleadstoourlastprotocol:

Page 166: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

(defprotocolIToken

(dispose[tk]

"Calledwhenthesubscriberisn'tinterestedinreceivingmoreitems"))

Page 167: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ImplementingtokensThetokentypeisthesimplestinthewholeframeworkasithasgotasinglefunctionwithastraightforwardimplementation:

(deftypeToken[ch]

IToken

(dispose[_]

(close!ch)))

Itsimplycloseswhateverchannelitisgiven,stoppingeventsfromflowingthroughsubscriptions.

Page 168: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ImplementingeventstreamsTheeventstreamimplementation,ontheotherhand,isthemostcomplexinourframework.We’lltackleitgradually,implementingandexperimentingaswego.

First,let’slookatourmainconstructorfunction,event-stream:

(defnevent-stream

"Createsandreturnsaneweventstream.Youcanoptionallyprovidean

existing

core.asyncchannelasthesourceforthenewstream"

([]

(event-stream(chan)))

([ch]

(let[multiple(multch)

completed(atomfalse)]

(EventStream.chmultiplecompleted))))

ThedocstringshouldbesufficienttounderstandthepublicAPI.Whatmightnotbeclear,however,iswhatalltheconstructorargumentsmean.Fromlefttoright,theargumentstoEventStreamare:

ch:Thisisthecore.asyncchannelbackingthisstream.multiple:Thisisawaytobroadcastinformationfromonechanneltomanyotherchannels.It’sacore.asyncconceptwewillbeexplainingshortly.completed:ABooleanflagindicatingwhetherthiseventstreamhascompletedandwillnotemitanynewvalues.

Fromtheimplementation,youcanseethatthemultipleiscreatedfromthechannelbackingthestream.multipleworkskindoflikeabroadcast.Considerthefollowingexample:

(defin(chan))

(defmultiple(multin))

(defout-1(chan))

(tapmultipleout-1)

(defout-2(chan))

(tapmultipleout-2)

(go(>!in"Singleput!"))

(go(prn"Gotfromout-1"(<!out-1)))

(go(prn"Gotfromout-2"(<!out-2)))

Intheprevioussnippet,wecreateaninputchannel,in,andmultofitcalledmultiple.Then,wecreatetwooutputchannels,out-1andout-2,whicharebothfollowedbyacalltotap.Thisessentiallymeansthatwhatevervaluesarewrittentoinwillbetakenbymultipleandwrittentoanychannelstappedintoitasthefollowingoutputshows:

"Gotfromout-1""Singleput!"

"Gotfromout-2""Singleput!"

ThiswillmakeunderstandingtheEventStreamimplementationeasier.

Page 169: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Next,let’shavealookatwhataminimalimplementationoftheEventStreamlookslikethefollowing—makesuretheimplementationgoesbeforetheconstructorfunctiondescribedearlier:

(declareevent-stream)

(deftypeEventStream[channelmultiplecompleted]

IEventStream

(map[_f]

(let[out(map>f(chan))]

(tapmultipleout)

(event-streamout)))

(deliver[_value]

(if(=value::complete)

(do(reset!completedtrue)

(go(>!channelvalue)

(close!channel)))

(go(>!channelvalue))))

IObservable

(subscribe[thisf]

(let[out(chan)]

(tapmultipleout)

(go-loop[]

(let[value(<!out)]

(when(andvalue(not=value::complete))

(fvalue)

(recur))))

(Token.out))))

Fornow,wehavechosentoimplementonlythemapanddeliverfunctionsfromtheIEventStreamprotocol.Thisallowsustodelivervaluestothestreamaswellastransformthosevalues.

However,thiswouldnotbeveryusefulifwecouldnotretrievethevaluesdelivered.ThisiswhywealsoimplementthesubscribefunctionfromtheIObservableprotocol.

Inanutshell,mapneedstotakeavaluefromtheinputstream,applyafunctiontoit,andsendittothenewlycreatedstream.Wedothisbycreatinganoutputchannelthattapsoncurrentmultiple.Wethenusethischanneltobacktheneweventstream.

Thedeliverfunctionsimplyputsthevalueintothebackingchannel.Ifthevalueisthenamespacedkeyword::complete,weupdatethecompletedatomandclosethebackingchannel.Thisensuresthestreamwillnotemitanyothervalues.

Finally,wehavethesubscribefunction.Thewaysubscribersarenotifiedisbyusinganoutputchanneltappedtobackingmultiple.Weloopindefinitelycallingthesubscribingfunctionwheneveranewvalueisemitted.

Wefinishbyreturningatoken,whichwillclosetheoutputchanneloncedisposed,causingthego-looptostop.

Page 170: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Let’smakesurethatallthismakessensebyexperimentingwithacoupleofexamplesintheREPL:

(defes1(event-stream))

(subscribees1#(prn"firsteventstreamemitted:"%))

(deliveres110)

;;"firsteventstreamemitted:"10

(defes2(mapes1#(*2%)))

(subscribees2#(prn"secondeventstreamemitted:"%))

(deliveres120)

;;"firsteventstreamemitted:"20

;;"secondeventstreamemitted:"40

Excellent!Wehaveaminimal,workingimplementationofourIEventStreamprotocol!

Thenextfunctionwe’llimplementisfilteranditisverysimilartomap:

(filter[_pred]

(let[out(filter>pred(chan))]

(tapmultipleout)

(event-streamout)))

Theonlydifferenceisthatweusethefilter>functionandpredshouldbeaBooleanfunction:

(defes1(event-stream))

(defes2(filteres1even?))

(subscribees1#(prn"firsteventstreamemitted:"%))

(subscribees2#(prn"secondeventstreamemitted:"%))

(deliveres12)

(deliveres13)

(deliveres14)

;;"firsteventstreamemitted:"2

;;"secondeventstreamemitted:"2

;;"firsteventstreamemitted:"3

;;"firsteventstreamemitted:"4

;;"secondeventstreamemitted:"4

Aswewitness,es2onlyemitsanewvalueifandonlyifthatvalueisanevennumber.

TipIfyouarefollowingalong,typingtheexamplesstepbystep,youwillneedtorestartyourREPLwheneverweaddnewfunctionstoanydeftypedefinition.ThisisbecausedeftypegeneratesandcompilesaJavaclasswhenevaluated.Assuch,simplyreloadingthenamespacewon’tbeenough.

Alternatively,youcanuseatoolsuchastools.namespace(seehttps://github.com/clojure/tools.namespace)thataddressessomeoftheseREPLreloadinglimitations.

Page 171: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Movingdownourlist,wenowhaveflatmap:

(flatmap[_f]

(let[es(event-stream)

out(chan)]

(tapmultipleout)

(go-loop[]

(when-let[a(<!out)]

(let[mb(fa)]

(subscribemb(fn[b]

(deliveresb)))

(recur))))

es))

We’veencounteredthisoperatorbeforewhensurveyingReactiveExtensions.Thisiswhatourdocstringsaysaboutit:

TakesafunctionffromvaluesinstoanewEventStream.

ReturnsanEventStreamcontainingvaluesfromallunderlyingstreamscombined.

Thismeansflatmapneedstocombineallthepossibleeventstreamsintoasingleoutputeventstream.Asbefore,wetapanewchanneltothemultiplestream,butthenweloopovertheoutputchannel,applyingftoeachoutputvalue.

However,aswesaw,fitselfreturnsaneweventstream,sowesimplysubscribetoit.Wheneverthefunctionregisteredinthesubscriptiongetscalled,wedeliverthatvaluetotheoutputeventstream,effectivelycombiningallstreamsintoasingleone.

Considerthefollowingexample:

(defnrange-es[n]

(let[es(event-stream(chann))]

(doseq[n(rangen)]

(deliveresn))

es))

(defes1(event-stream))

(defes2(flatmapes1range-es))

(subscribees1#(prn"firsteventstreamemitted:"%))

(subscribees2#(prn"secondeventstreamemitted:"%))

(deliveres12)

;;"firsteventstreamemitted:"2

;;"secondeventstreamemitted:"0

;;"secondeventstreamemitted:"1

(deliveres13)

;;"firsteventstreamemitted:"3

;;"secondeventstreamemitted:"0

;;"secondeventstreamemitted:"1

;;"secondeventstreamemitted:"2

Wehaveafunction,range-es,thatreceivesanumbernandreturnsaneventstreamthatemitsnumbersfrom0ton.Asbefore,wehaveastartingstream,es1,andatransformedstreamcreatedwithflatmap,es2.

Page 172: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Wecanseefromtheprecedingoutputthatthestreamcreatedbyrange-esgetsflattenedintoes2allowingustoreceiveallvaluesbysimplysubscribingtoitonce.

ThisleavesuswithsinglefunctionfromIEventStreamlefttoimplement:

(completed?[_]@completed)

completed?simplyreturnsthecurrentvalueofthecompletedatom.Wearenowreadytoimplementbehaviors.

Page 173: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ImplementingbehaviorsIfyourecall,theIBehaviorprotocolhasasinglefunctioncalledsamplewhosedocstringstates:TurnsthisBehaviorintoanEventStreamfromthesampledvaluesatthegiveninterval.

Inordertoimplementsample,wewillfirstcreateausefulhelperfunctionthatwewillcallfrom-interval:

(defnfrom-interval

"Createsandreturnsaneweventstreamwhichemitsvaluesatthegiven

interval.

Ifnootherargumentsaregiven,thevaluesstartat0andincrementby

oneateachdelivery.

Ifgivenseedandsuccitemitsseedandappliessucctoseedtoget

thenextvalue.Itthenappliessucctothepreviousresultandsoon."

([msecs]

(from-intervalmsecs0inc))

([msecsseedsucc]

(let[es(event-stream)]

(go-loop[timeout-ch(timeoutmsecs)

valueseed]

(when-not(completed?es)

(<!timeout-ch)

(deliveresvalue)

(recur(timeoutmsecs)(succvalue))))

es)))

Thedocstringfunctionshouldbeclearenoughatthisstage,butwewouldliketoensureweunderstanditsbehaviorcorrectlybytryingitattheREPL:

(defes1(from-interval500))

(defes1-token(subscribees1#(prn"Got:"%)))

;;"Got:"0

;;"Got:"1

;;"Got:"2

;;"Got:"3

;;...

(disposees1-token)

Asexpected,es1emitsintegersstartingatzeroat500-millisecondintervals.Bydefault,itwouldemitnumbersindefinitely;therefore,wekeepareferencetothetokenreturnedbycallingsubscribe.

Thiswaywecandisposeitwheneverwe’redone,causinges-1tocompleteandstopemittingitems.

Next,wecanfinallyimplementtheBehaviortype:

(deftypeBehavior[f]

IBehavior

(sample[_interval]

(from-intervalinterval(f)(fn[&args](f))))

IDeref

Page 174: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

(#+cljderef#+cljs-deref[_]

(f)))

(defmacrobehavior[&body]

`(Behavior.#(do~@body)))

Abehavioriscreatedbypassingitafunction.Youcanthinkofthisfunctionasageneratorresponsibleforgeneratingthenextvalueinthiseventstream.

Thisgeneratorfunctionwillbecalledwheneverwe(1)dereftheBehavioror(2)attheintervalgiventosample.

ThebehaviormacroisthereforconvenienceandallowsustocreateanewBehaviorwithoutwrappingthebodyinafunctionourselves:

(deftime-behavior(behavior(System/nanoTime)))

@time-behavior

;;201003153977194

@time-behavior

;;201005133457949

Intheprecedingexample,wedefinedtime-behaviorthatalwayscontainsthecurrentsystemtime.Wecanthenturnthisbehaviorintoastreamofdiscreteeventsbyusingthesamplefunction:

(deftime-stream(sampletime-behavior1500))

(deftoken(subscribetime-stream#(prn"Timeis"%)))

;;"Timeis"201668521217402

;;"Timeis"201670030219351

;;...

(disposetoken)

TipAlwaysremembertokeepareferencetothesubscriptiontokenwhendealingwithinfinitestreamssuchastheonescreatedbysampleandfrom-interval,orelseyoumightincurundesiredmemoryleaks.

Congratulations!Wehaveaworking,minimalCESframeworkusingcore.async!

Wedidn’tproveitworkswithClojureScript,however,whichwasoneofthemainrequirementsearlyon.That’sokay.WewillbetacklingthatsoonbydevelopingasimpleClojureScriptapplication,whichmakesuseofournewframework.

Inordertodothis,weneedtodeploytheframeworktoourlocalMavenrepository.Fromtheprojectroot,typethefollowingleincommand:

$leininstall

Rewritingsrc/cljxtotarget/classes(clj)withfeatures#{clj}and0

transformations.

Rewritingsrc/cljxtotarget/classes(cljs)withfeatures#{cljs}and1

transformations.

Page 175: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Createdrespondent/target/respondent-0.1.0-SNAPSHOT.jar

Wroterespondent/pom.xml

Page 176: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 177: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ExercisesThefollowingsectionshaveafewexercisesforyou.

Page 178: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Exercise5.1ExtendourcurrentEventStreamimplementationtoincludeafunctioncalledtake.ItworksmuchlikeClojure’scoretakefunctionforsequences:itwilltakenitemsfromtheunderlyingeventstreamafterwhichitwillstopemittingitems.

Asampleinteraction,whichtakesthefirstfiveitemsemittedfromtheoriginaleventstream,isshownhere:

(defes1(from-interval500))

(deftake-es(takees15))

(subscribetake-es#(prn"Takevalues:"%))

;;"Takevalues:"0

;;"Takevalues:"1

;;"Takevalues:"2

;;"Takevalues:"3

;;"Takevalues:"4

TipKeepingsomestatemightbeusefulhere.Atomscanhelp.Additionally,trytothinkofawaytodisposeofanyunwantedsubscriptionsrequiredbythesolution.

Page 179: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Exercise5.2Inthisexercise,wewilladdafunctioncalledzipthatzipstogetheritemsemittedfromtwodifferenteventstreamsintoavector.

Asampleinteractionwiththezipfunctionisasfollows:

(defes1(from-interval500))

(defes2(map(from-interval500)#(*%2)))

(defzipped(zipes1es2))

(deftoken(subscribezipped#(prn"Zippedvalues:"%)))

;;"Zippedvalues:"[00]

;;"Zippedvalues:"[12]

;;"Zippedvalues:"[24]

;;"Zippedvalues:"[36]

;;"Zippedvalues:"[48]

(disposetoken)

TipForthisexercise,weneedawaytoknowwhenwehaveenoughitemstoemitfrombotheventstreams.Managingthisinternalstatecanbetrickyatfirst.Clojure’sreftypesand,inparticular,dosync,canbeofuse.

Page 180: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 181: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ArespondentapplicationThischapterwouldnotbecompleteifwedidn’tgothroughthewholedevelopmentlifecycleofdeployingandusingthenewframeworkinanewapplication.Thisisthepurposeofthissection.

Theapplicationwewillbuildisextremelysimple.Allitdoesistrackthepositionofthemouseusingthereactiveprimitiveswebuiltintorespondent.

Tothatend,wewillbeusingtheexcellentleintemplatecljs-start(seehttps://github.com/magomimmo/cljs-start),createdbyMimmoCosenzatohelpdevelopersgetstartedwithClojureScript.

Let’sgetstarted:

leinnewcljs-startrespondent-app

Next,let’smodifytheprojectfiletoincludethefollowingdependencies:

[clojure-reactive-programming/respondent"0.1.0-SNAPSHOT"]

[prismatic/dommy"0.1.2"]

Thefirstdependencyisself-explanatory.It’ssimplyourownframework.dommyisaDOMmanipulationlibraryforClojureScript.We’llbrieflyuseitwhenbuildingourwebpage.

Next,editthedev-resources/public/index.htmlfiletomatchthefollowing:

<!doctypehtml>

<htmllang="en">

<head>

<metacharset="utf-8">

<title>Example:trackingmouseposition</title>

<!--[ifltIE9]>

<scriptsrc="http://html5shiv.googlecode.com/svn/trunk/html5.js">

</script>

<![endif]-->

</head>

<body>

<divid="test">

<h1>Mouse(x,y)coordinates:</h1>

</div>

<divid="mouse-xy">

(0,0)

</div>

<scriptsrc="js/respondent_app.js"></script>

</body>

</html>

Intheprecedingsnippet,wecreatedanewdivelement,whichwillcontainthemouseposition.Itdefaultsto(0,0).

Thelastpieceofthepuzzleismodifyingsrc/cljs/respondent_app/core.cljstomatchthefollowing:

Page 182: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

(nsrespondent-app.core

(:require[respondent.core:asr]

[dommy.core:asdommy])

(:use-macros

[dommy.macros:only[sel1]]))

(defmouse-pos-stream(r/event-stream))

(set!(.-onmousemovejs/document)

(fn[e]

(r/delivermouse-pos-stream[(.-pageXe)(.-pageYe)])))

(r/subscribemouse-pos-stream

(fn[[xy]]

(dommy/set-text!(sel1:#mouse-xy)

(str"("x","y")"))))

Thisisourmainapplicationlogic.Itcreatesaneventstreamtowhichwedeliverthecurrentmousepositionfromtheonmousemoveeventofthebrowserwindow.

Later,wesimplysubscribetoitandusedommytoselectandsetthetextofthedivelementweaddedpreviously.

Wearenowreadytousetheapp!Let’sstartbycompilingClojureScript:

$leincompile

Thisshouldtakeafewseconds.Ifalliswell,thenextthingtodoistostartaREPLsessionandstartuptheserver:

$leinrepl

user=>(run)

Now,pointyourbrowsertohttp://localhost:3000/anddragthemousearoundtoseeitscurrentposition.

Congratulationsonsuccessfullydeveloping,deploying,andusingyourownCESframework!

Page 183: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 184: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

CESversuscore.asyncAtthisstage,youmightbewonderingwhenyoushouldchooseoneapproachovertheother.Afterall,asdemonstratedatthebeginningofthischapter,wecouldusecore.asynctodoeverythingwehavedoneusingrespondent.

Itallcomesdowntousingtherightlevelofabstractionforthetaskathand.

core.asyncgivesusmanylowlevelprimitivesthatareextremelyusefulwhenworkingwithprocesses,whichneedtotalktoeachother.Thecore.asyncchannelsworkasconcurrentblockingqueuesandareanexcellentsynchronizationmechanisminthesescenarios.

However,itmakesothersolutionshardertoimplement:forinstance,channelsaresingle-takebydefault,soifwehavemultipleconsumersinterestedinthevaluesputinsideachannel,wehavetoimplementthedistributionourselvesusingtoolssuchasmultandtap.

CESframeworks,ontheotherhand,operateatahigherlevelofabstractionandworkwithmultiplesubscribersbydefault.

Additionally,core.asyncreliesonsideeffects,ascanbeseenbytheuseoffunctionssuchas>!insidegoblocks.FrameworkssuchasRxClojurepromotestreamtransformationsbytheuseofpurefunctions.

Thisisnottosaytherearen’tsideeffectsinCESframeworks.Theremostdefinitelyare.However,asaconsumerofthelibrary,thisismostlyhiddenfromoureyes,allowingustothinkofmostofourcodeassimplesequencetransformations.

Inconclusion,differentapplicationdomainswillbenefitmoreorlessfromeitherapproach—sometimestheycanbenefitfromboth.Weshouldthinkhardaboutourapplicationdomainandanalyzethetypesofsolutionsandidiomsdevelopersaremostlikelytodesign.Thiswillpointusinthedirectionofbetterabstractionforwhateverapplicationwearedevelopingatagiventime.

Page 185: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 186: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

SummaryInthischapter,wedevelopedourveryownCESframework.Bydevelopingourownframework,wehavesolidifiedourunderstandingofbothCESandhowtoeffectivelyusecore.async.

Theideathatcore.asynccouldbeusedasthefoundationofaCESframeworkisn’tmine,however.JamesReeves(seehttps://github.com/weavejester)—creatoroftheroutinglibraryCompojure(seehttps://github.com/weavejester/compojure)andmanyotherusefulClojurelibraries—alsosawthesamepotentialandsetofftowriteReagi(seehttps://github.com/weavejester/reagi),aCESlibrarybuiltontopofcore.async,similarinspirittotheonewedevelopedinthischapter.

Hehasputalotmoreeffortintoit,makingitamorerobustoptionforapureClojureframework.We’llbelookingatitinthenextchapter.

Page 187: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 188: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Chapter6.BuildingaSimpleClojureScriptGamewithReagiInthepreviouschapter,welearnedhowaframeworkforCompositionalEventSystems(CES)worksbybuildingourownframework,whichwecalledrespondent.Itgaveusagreatinsightintothemainabstractionsinvolvedinsuchapieceofsoftwareaswellasagoodoverviewofcore.async,Clojure’slibraryforasynchronousprogrammingandthefoundationofourframework.

Respondentisbutatoyframework,however.Wepaidlittleattentiontocross-cuttingconcernssuchasmemoryefficiencyandexceptionhandling.Thatisokayasweuseditasavehicleforlearningmoreabouthandlingandcomposingeventsystemswithcore.async.Additionally,itsdesignisintentionallysimilartoReagi’sdesign.

Inthischapter,wewill:

LearnaboutReagi,aCESframeworkbuiltontopofcore.asyncUseReagitobuildtherudimentsofaClojureScriptgamethatwillteachushowtohandleuserinputinacleanandmaintainablewayBrieflycompareReagitootherCESframeworksandgetafeelforwhentouseeachone

Page 189: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

SettinguptheprojectHaveyoueverplayedAsteroids?Ifyouhaven’t,AsteroidsisanarcadespaceshooterfirstreleasedbyAtariin1979.InAsteroids,youarethepilotofashipflyingthroughspace.Asyoudoso,yougetsurroundedbyasteroidsandflyingsaucersyouhavetoshootanddestroy.

Developingthewholegameinonechapteristooambitiousandwoulddistractusfromthesubjectofthisbook.Wewilllimitourselvestomakingsurewehaveashiponthescreenwecanflyaroundaswellasshootspacebulletsintothevoid.Bytheendofthischapter,wewillhavesomethingthatlookslikewhatisshowninthefollowingscreenshot:

Togetstarted,wewillcreateanewClojureScriptprojectusingthesameleiningentemplateweusedinthepreviouschapter,cljs-start(seehttps://github.com/magomimmo/cljs-start):

leinnewcljs-startreagi-game

Next,addthefollowingdependenciestoyourprojectfile:

[org.clojure/clojurescript"0.0-2138"]

[reagi"0.10.0"]

[rm-hull/monet"0.1.12"]

Thelastdependency,monet(seehttps://github.com/rm-hull/monet),isaClojureScriptlibraryyoucanusetoworkwithHTML5Canvas.Itisahigh-levelwrapperontopoftheCanvasAPIandmakesinteractingwithitalotsimpler.

Beforewecontinue,it’sprobablyagoodideatomakesureoursetupisworkingproperly.Changeintotheprojectdirectory,startaClojureREPL,andthenstarttheembeddedwebserver:

cdreagi-game/

Page 190: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

leinrepl

CompilingClojureScript.

Compiling"dev-resources/public/js/reagi_game.js"from("src/cljs"

"test/cljs""dev-resources/tools/repl")...

user=>(run)

2014-06-1419:21:40.381:INFO:oejs.Server:jetty-7.6.8.v20121106

2014-06-1419:21:40.403:INFO:oejs.AbstractConnector:Started

[email protected]:3000

#<Serverorg.eclipse.jetty.server.Server@51f6292b>

ThiswillcompiletheClojureScriptsourcefilestoJavaScriptandstartthesamplewebserver.Inyourbrowser,navigatetohttp://localhost:3000/.Ifyouseesomethinglikethefollowing,wearegoodtogo:

AswewillbeworkingwithHTML5Canvas,weneedanactualcanvastorenderto.Let’supdateourHTMLdocumenttoincludethat.It’slocatedunderdev-resources/public/index.html:

<!doctypehtml>

<htmllang="en">

<head>

<metacharset="utf-8">

<title>bREPLConnection</title>

<!--[ifltIE9]>

<scriptsrc="http://html5shiv.googlecode.com/svn/trunk/html5.js">

</script>

<![endif]-->

</head>

<body>

<canvasid="canvas"width="800"height="600"></canvas>

<scriptsrc="js/reagi_game.js"></script>

</body>

</html>

WehaveaddedacanvasDOMelementtoourdocument.Allrenderingwillhappeninthiscontext.

Page 191: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

GameentitiesOurgamewillhaveonlytwoentities:onerepresentingthespaceshipandtheotherrepresentingbullets.Tobetterorganizethecode,wewillputallentity-relatedcodeinitsownfile,src/cljs/reagi_game/entities.cljs.Thisfilewillalsocontainsomeoftherenderinglogic,sowe’llneedtorequiremonet:

(nsreagi-game.entities

(:require[monet.canvas:ascanvas]

[monet.geometry:asgeom]))

Next,we’lladdafewhelperfunctionstoavoidrepeatingourselvestoomuch:

(defnshape-x[shape]

(->shape:posderef:x))

(defnshape-y[shape]

(->shape:posderef:y))

(defnshape-angle[shape]

@(:angleshape))

(defnshape-data[xyangle]

{:pos(atom{:xx:yy})

:angle(atomangle)})

Thefirstthreefunctionsaresimplyashorterwayofgettingdataoutofourshapedatastructure.Theshape-datafunctioncreatesastructure.Notethatweareusingatoms,oneofClojure’sreferencetypes,torepresentashape’spositionandangle.

Thisway,wecansafelypassourshapedataintomonet’srenderingfunctionsandstillbeabletoupdateitinaconsistentway.

Nextupisourshipconstructorfunction.Thisiswherethebulkoftheinteractionwithmonethappens:

(defnship-entity[ship]

(canvas/entity{:x(shape-xship)

:y(shape-yship)

:angle(shape-angleship)}

(fn[value]

(->value

(assoc:x(shape-xship))

(assoc:y(shape-yship))

(assoc:angle(shape-angleship))))

(fn[ctxval]

(->ctx

canvas/save

(canvas/translate(:xval)(:yval))

(canvas/rotate(:angleval))

(canvas/begin-path)

(canvas/move-to500)

(canvas/line-to0-15)

(canvas/line-to015)

Page 192: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

(canvas/fill)

canvas/restore))))

Thereisquiteabitgoingon,solet’sbreakitdown.

canvas/entityisamonetconstructorandexpectsyoutoprovidethreeargumentsthatdescribeourship:itsinitialx,ycoordinatesandangle,anupdatefunctionthatgetscalledinthedrawloop,andadrawfunctionthatisresponsibleforactuallydrawingtheshapeontothescreenaftereachupdate.

Theupdatefunctionisfairlystraightforward:

(fn[value]

(->value

(assoc:x(shape-xship))

(assoc:y(shape-yship))

(assoc:angle(shape-angleship))))

Wesimplyupdateitsattributestothecurrentvaluesfromtheship’satoms.

Thenextfunction,responsiblefordrawing,interactswithmonet’sAPImoreheavily:

(fn[ctxval]

(->ctx

canvas/save

(canvas/translate(:xval)(:yval))

(canvas/rotate(:angleval))

(canvas/begin-path)

(canvas/move-to500)

(canvas/line-to0-15)

(canvas/line-to015)

(canvas/fill)

canvas/restore))

Westartbysavingthecurrentcontextsothatwecanrestorethingssuchasdrawingstyleandcanvaspositioninglater.Next,wetranslatethecanvastotheship’sx,ycoordinatesandrotateitaccordingtoitsangle.Wethenstartdrawingourshape,atriangle,andfinishbyrestoringoursavedcontext.

Thenextfunctionalsocreatesanentity,ourbullet:

(declaremove-forward!)

(defnmake-bullet-entity[monet-canvaskeyshape]

(canvas/entity{:x(shape-xshape)

:y(shape-yshape)

:angle(shape-angleshape)}

(fn[value]

(when(not

(geom/contained?

{:x0:y0

:w(.-width(:canvasmonet-canvas))

:h(.-height(:canvasmonet-canvas))}

{:x(shape-xshape)

:y(shape-yshape)

:r5}))

Page 193: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

(canvas/remove-entitymonet-canvaskey))

(move-forward!shape)

(->value

(assoc:x(shape-xshape))

(assoc:y(shape-yshape))

(assoc:angle(shape-angleshape))))

(fn[ctxval]

(->ctx

canvas/save

(canvas/translate(:xval)(:yval))

(canvas/rotate(:angleval))

(canvas/fill-style"red")

(canvas/circle{:x10:y0:r5})

canvas/restore))))

Asbefore,let’sinspecttheupdateanddrawingfunctions.We’llstartwithupdate:

(fn[value]

(when(not

(geom/contained?

{:x0:y0

:w(.-width(:canvasmonet-canvas))

:h(.-height(:canvasmonet-canvas))}

{:x(shape-xshape)

:y(shape-yshape)

:r5}))

(canvas/remove-entitymonet-canvaskey))

(move-forward!shape)

(->value

(assoc:x(shape-xshape))

(assoc:y(shape-yshape))

(assoc:angle(shape-angleshape))))

Bulletshavealittlemorelogicintheirupdatefunction.Asyoufirethemfromtheship,wemightcreatehundredsoftheseentities,soit’sagoodpracticetogetridofthemassoonastheygooffthevisiblecanvasarea.That’sthefirstthingthefunctiondoes:itusesgeom/contained?tocheckwhethertheentityiswithinthedimensionsofthecanvas,removingitwhenitisn’t.

Differentfromtheship,however,bulletsdon’tneeduserinputinordertomove.Oncefired,theymoveontheirown.That’swhythenextthingwedoiscallmove-forward!Wehaven’timplementedthisfunctionyet,sowehadtodeclareitbeforehand.We’llgettoit.

Oncethebullet’scoordinatesandanglehavebeenupdated,wesimplyreturnthenewentity.

Thedrawfunctionisabitsimplerthantheship’sversionmostlyduetoitsshapebeingsimpler;it’sjustaredcircle:

(fn[ctxval]

(->ctx

canvas/save

(canvas/translate(:xval)(:yval))

(canvas/rotate(:angleval))

(canvas/fill-style"red")

Page 194: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

(canvas/circle{:x10:y0:r5})

canvas/restore))

Now,we’llmoveontothefunctionsresponsibleforupdatingourshape’scoordinatesandangle,startingwithmove!:

(defspeed200)

(defncalculate-x[angle]

(*speed(/(*(Math/cosangle)

Math/PI)

180)))

(defncalculate-y[angle]

(*speed(/(*(Math/sinangle)

Math/PI)

180)))

(defnmove![shapef]

(let[pos(:posshape)]

(swap!pos(fn[xy]

(->xy

(update-in[:x]

#(f%(calculate-x

(shape-angleshape))))

(update-in[:y]

#(f%(calculate-y

(shape-angleshape)))))))))

Tokeepthingssimple,boththeshipandbulletsusethesamespeedvaluetocalculatetheirpositioning,heredefinedas200.

move!takestwoarguments:theshapemapandafunctionf.Thisfunctionwilleitherbethe+(plus)orthe-(minus)function,dependingonwhetherwe’removingforwardorbackward,respectively.Next,itupdatestheshape’sx,ycoordinatesusingsomebasictrigonometry.

Ifyou’rewonderingwhywearepassingtheplusandminusfunctionsasarguments,it’sallaboutnotrepeatingourselves,asthenexttwofunctionsshow:

(defnmove-forward![shape]

(move!shape+))

(defnmove-backward![shape]

(move!shape-))

Withmovementtakencareof,thenextstepistowritetherotationfunctions:

(defnrotate![shapef]

(swap!(:angleshape)#(f%(/(/Math/PI3)20))))

(defnrotate-right![shape]

(rotate!shape+))

(defnrotate-left![shape]

(rotate!shape-))

Page 195: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Sofar,we’vegotshipmovementcovered!Butwhatgoodisourshipifwecan’tfirebullets?Let’smakesurewehavethatcoveredaswell:

(defnfire![monet-canvasship]

(let[entity-key(keyword(gensym"bullet"))

data(shape-data(shape-xship)

(shape-yship)

(shape-angleship))

bullet(make-bullet-entitymonet-canvas

entity-key

data)]

(canvas/add-entitymonet-canvasentity-keybullet)))

Thefire!functiontakestwoarguments:areferencetothegamecanvasandtheship.Itthencreatesanewbulletbycallingmake-bullet-entityandaddsittothecanvas.

NotehowweuseClojure’sgensymfunctiontocreateauniquekeyforthenewentity.Weusethiskeytoremoveanentityfromthegame.

Thisconcludesthecodefortheentitiesnamespace.

Tipgensymisquiteheavilyusedinwritinghygienicmacrosasyoucanbesurethatthegeneratedsymbolswillnotclashwithanylocalbindingsbelongingtothecodeusingthemacro.Macrosarebeyondthescopeofthisbook,butyoumightfindthisseriesofmacroexercisesusefulinthelearningprocess,athttps://github.com/leonardoborges/clojure-macros-workshop.

Page 196: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

PuttingitalltogetherWe’renowreadytoassembleourgame.Goaheadandopenthecorenamespacefile,src/cljs/reagi_game/core.cljs,andaddthefollowing:

(nsreagi-game.core

(:require[monet.canvas:ascanvas]

[reagi.core:asr]

[clojure.set:asset]

[reagi-game.entities:asentities

:refer[move-forward!move-backward!rotate-left!rotate-

right!fire!]]))

Thefollowingsnippetsetsupvariousdatastructuresandreferenceswe’llneedinordertodevelopthegame:

(defcanvas-dom(.getElementByIdjs/document"canvas"))

(defmonet-canvas(canvas/initcanvas-dom"2d"))

(defship

(entities/shape-data(/(.-width(:canvasmonet-canvas))2)

(/(.-height(:canvasmonet-canvas))2)

0))

(defship-entity(entities/ship-entityship))

(canvas/add-entitymonet-canvas:ship-entityship-entity)

(canvas/draw-loopmonet-canvas)

Westartbycreatingmonet-canvasfromareferencetoourcanvasDOMelement.Wethencreateourshipdata,placingitatthecenterofthecanvas,andaddtheentitytomonet-canvas.Finally,westartadraw-loop,whichwillhandleouranimationsusingthebrowser’snativecapabilities—internallyitcallswindow.requestAnimationFrame(),ifavailable,butitfallsbacktowindow.setTimemout()otherwise.

Ifyouweretotrytheapplicationnow,thiswouldbeenoughtodrawtheshiponthemiddleofthescreen,butnothingelsewouldhappenaswehaven’tstartedhandlinguserinputyet.

Asfarasuserinputgoes,we’reconcernedwithafewactions:

Shipmovement:rotation,forward,andbackwardFiringtheship’sgunPausingthegame

Toaccountfortheseactions,we’lldefinesomeconstantsthatrepresenttheASCIIcodesofthekeysinvolved:

(defUP38)

(defRIGHT39)

(defDOWN40)

(defLEFT37)

(defFIRE32);;space

Page 197: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

(defPAUSE80);;lower-caseP

Thisshouldlooksensibleasweareusingthekeystraditionallyusedforthesetypesofactions.

Page 198: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ModelinguserinputaseventstreamsOneofthethingsdiscussedintheearlierchaptersisthatifyoucanthinkofeventsasalistofthingsthathaven’thappenedyet;youcanprobablymodelitasaneventstream.Inourcase,thislistiscomposedbythekeystheplayerpressesduringthegameandcanbevisualizedlikeso:

Thereisacatchthough.Mostgamesneedtohandlesimultaneouslypressedkeys.

Sayyou’reflyingthespaceshipforwards.Youdon’twanttohavetostopitinordertorotateittotheleftandthencontinuemovingforwards.Whatyouwantistopressleftatthesametimeyou’repressingupandhavetheshiprespondaccordingly.

Thishintsatthefactthatweneedtobeabletotellwhethertheplayeriscurrentlypressingmultiplekeys.TraditionallythisisdoneinJavaScriptbykeepingtrackofwhichkeysarebeinghelddowninamap-likeobject,usingflags.Somethingsimilartothefollowingsnippet:

varkeysPressed={};

document.addEventListener('keydown',function(e){

keysPressed[e.keyCode]=true;

},false);

document.addEventListener('keyup',function(e){

keysPressed[e.keyCode]=false;

},false);

Then,laterinthegameloop,youwouldcheckwhethertherearemultiplekeysbeingpressed:

functiongameLoop(){

if(keyPressed[UP]&&keyPressed[LEFT]){

//updateshipposition

}

//...

}

Whilethiscodeworks,itreliesonmutatingthekeysPressedobjectwhichisn’tideal.

Additionally,withasetupsimilartotheprecedingone,thekeysPressedobjectisglobaltotheapplicationasitisneededbothinthekeyup/keydowneventhandlersaswellasinthegameloopitself.

Infunctionalprogramming,westrivetoeliminateorreducetheamountofglobalmutable

Page 199: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

stateinordertowritereadable,maintainablecodethatislesserror-prone.Wewillapplytheseprincipleshere.

AsseenintheprecedingJavaScriptexample,wecanregistercallbackstobenotifiedwheneverakeyuporkeydowneventhappens.Thisisusefulaswecaneasilyturnthemintoeventstreams:

(defnkeydown-stream[]

(let[out(r/events)]

(set!(.-onkeydownjs/document)

#(r/deliverout[::down(.-keyCode%)]))

out))

(defnkeyup-stream[]

(let[out(r/events)]

(set!(.-onkeyupjs/document)

#(r/deliverout[::up(.-keyCode%)]))

out))

Bothkeydown-streamandkeyup-streamreturnanewstreamtowhichtheydelivereventswhenevertheyhappen.Eacheventistaggedwithakeyword,sowecaneasilyidentifyitstype.

Wewouldliketohandlebothtypesofeventssimultaneouslyandassuchweneedawaytocombinethesetwostreamsintoasingleone.

Therearemanywaysinwhichwecancombinestreams,forexample,usingoperatorssuchaszipandflatmap.Forthisinstance,however,weareinterestedinthemergeoperator.mergecreatesanewstreamthatemitsvaluesfrombothstreamsastheyarrive:

Page 200: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Thisgivesusenoughtostartcreatingourstreamofactivekeys.Basedonwhatwehavediscussedsofar,ourstreamlookssomethinglikethefollowingatthemoment:

(defactive-keys-stream

(->>(r/merge(keydown-stream)(keyup-stream))

...

))

Tokeeptrackofwhichkeysarecurrentlypressed,wewilluseaClojureScriptset.Thiswaywedon’thavetoworryaboutsettingflagstotrueorfalse—wecansimplyperformstandardsetoperationsandadd/removekeysfromthedatastructure.

Thenextthingweneedisawaytoaccumulatethepressedkeysintothissetasneweventsareemittedfromthemergedstream.

Infunctionalprogramming,wheneverwewishtoaccumulateoraggregatesometypeofdataoverasequenceofvalues,weusereduce.

Most—ifnotall—CESframeworkshavethisfunctionbuilt-in.RxJavacallsitscan.Reagi,ontheotherhand,callsitreduce,makingitintuitivetofunctionalprogrammersingeneral.

Thatisthefunctionwewillusetofinishtheimplementationofactive-keys-stream:

(defactive-keys-stream

(->>(r/merge(keydown-stream)(keyup-stream))

(r/reduce(fn[acc[event-typekey-code]]

Page 201: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

(condp=event-type

::down(conjacckey-code)

::up(disjacckey-code)

acc))

#{})

(r/sample25)))

r/reducetakesthreearguments:areducingfunction,anoptionalinitial/seedvalue,andthestreamtoreduceover.

Ourseedvalueisanemptysetasinitiallytheuserhasn’tyetpressedanykeys.Then,ourreducingfunctioncheckstheeventtype,removingoraddingthekeyfrom/tothesetasappropriate.

Asaresult,whatwehaveisastreamliketheonerepresentedasfollows:

Page 202: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

WorkingwiththeactivekeysstreamThegroundworkwe’vedonesofarwillmakesurewecaneasilyhandlegameeventsinacleanandmaintainableway.Themainideabehindhavingastreamrepresentingthegamekeysisthatnowwecanpartitionitmuchlikewewouldanormallist.

Forinstance,ifwe’reinterestedinalleventswherethekeypressedisUP,wewouldrunthefollowingcode:

(->>active-keys-stream

(r/filter(partialsome#{UP}))

(r/map(fn[_](.logjs/console"Pressedup…"))))

Similarly,foreventsinvolvingtheFIREkey,wecoulddothefollowing:

(->>active-keys-stream

(r/filter(partialsome#{FIRE}))

(r/map(fn[_](.logjs/console"Pressedfire…"))))

ThisworksbecauseinClojure,setscanbeusedaspredicates.WecanquicklyverifythisattheREPL:

user>(defnumbers#{121314})

#'user/numbers

user>(some#{12}numbers)

12

user>(some#{15}numbers)

nil

Byrepresentingtheeventsasastream,wecaneasilyoperateonthemusingfamiliarsequencefunctionssuchasmapandfilter.

Writingcodelikethis,however,isalittlerepetitive.Thetwopreviousexamplesareprettymuchsayingsomethingalongtheselines:filteralleventsmatchingagivenpredicatepredandthenmaptheffunctionoverthem.Wecanabstractthispatterninafunctionwe’llcallfilter-map:

(defnfilter-map[predf&args]

(->>active-keys-stream

(r/filter(partialsomepred))

(r/map(fn[_](applyfargs)))))

Withthishelperfunctioninplace,itbecomeseasytohandleourgameactions:

(filter-map#{FIRE}fire!monet-canvasship)

(filter-map#{UP}move-forward!ship)

(filter-map#{DOWN}move-backward!ship)

(filter-map#{RIGHT}rotate-right!ship)

(filter-map#{LEFT}rotate-left!ship)

TheonlythingmissingnowistakingcareofpausingtheanimationswhentheplayerpressesthePAUSEkey.Wefollowthesamelogicasabove,butwithaslightchange:

(defnpause![_]

(if@(:updating?monet-canvas)

Page 203: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

(canvas/stop-updatingmonet-canvas)

(canvas/start-updatingmonet-canvas)))

(->>active-keys-stream

(r/filter(partialsome#{PAUSE}))

(r/throttle100)

(r/mappause!))

Monetmakesaflagavailablethattellsuswhetheritiscurrentlyupdatingtheanimationstate.Weusethatasacheapmechanismto“pause”thegame.

Notethatactive-keys-streampusheseventsastheyhappenso,ifauserisholdingabuttondownforanyamountoftime,wewillgetmultipleeventsforthatkey.Assuch,wewouldprobablygetmultipleoccurrencesofthePAUSEkeyinaveryshortamountoftime.Thiswouldcausethegametofranticallystop/start.Inordertopreventthisfromhappening,wethrottlethefilteredstreamandignoreallPAUSEeventsthathappeninawindowshorterthan100milliseconds.

Tomakesurewedidn’tmissanything,thisiswhatoursrc/cljs/reagi_game/core.cljsfileshouldlooklike,infull:

(nsreagi-game.core

(:require[monet.canvas:ascanvas]

[reagi.core:asr]

[clojure.set:asset]

[reagi-game.entities:asentities

:refer[move-forward!move-backward!rotate-left!rotate-

right!fire!]]))

(defcanvas-dom(.getElementByIdjs/document"canvas"))

(defmonet-canvas(canvas/initcanvas-dom"2d"))

(defship(entities/shape-data(/(.-width(:canvasmonet-canvas))2)

(/(.-height(:canvasmonet-canvas))2)

0))

(defship-entity(entities/ship-entityship))

(canvas/add-entitymonet-canvas:ship-entityship-entity)

(canvas/draw-loopmonet-canvas)

(defUP38)

(defRIGHT39)

(defDOWN40)

(defLEFT37)

(defFIRE32);;space

(defPAUSE80);;lower-caseP

(defnkeydown-stream[]

(let[out(r/events)]

(set!(.-onkeydownjs/document)#(r/deliverout[::down(.-keyCode

%)]))

out))

Page 204: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

(defnkeyup-stream[]

(let[out(r/events)]

(set!(.-onkeyupjs/document)#(r/deliverout[::up(.-keyCode%)]))

out))

(defactive-keys-stream

(->>(r/merge(keydown-stream)(keyup-stream))

(r/reduce(fn[acc[event-typekey-code]]

(condp=event-type

::down(conjacckey-code)

::up(disjacckey-code)

acc))

#{})

(r/sample25)))

(defnfilter-map[predf&args]

(->>active-keys-stream

(r/filter(partialsomepred))

(r/map(fn[_](applyfargs)))))

(filter-map#{FIRE}fire!monet-canvasship)

(filter-map#{UP}move-forward!ship)

(filter-map#{DOWN}move-backward!ship)

(filter-map#{RIGHT}rotate-right!ship)

(filter-map#{LEFT}rotate-left!ship)

(defnpause![_]

(if@(:updating?monet-canvas)

(canvas/stop-updatingmonet-canvas)

(canvas/start-updatingmonet-canvas)))

(->>active-keys-stream

(r/filter(partialsome#{PAUSE}))

(r/throttle100)

(r/mappause!))

Thiscompletesthecodeandwe’renowreadytohavealookattheresults.

Ifyoustillhavetheserverrunningfromearlierinthischapter,simplyexittheREPL,startitagain,andstarttheembeddedwebserver:

leinrepl

CompilingClojureScript.

Compiling"dev-resources/public/js/reagi_game.js"from("src/cljs"

"test/cljs""dev-resources/tools/repl")...

user=>(run)

2014-06-1419:21:40.381:INFO:oejs.Server:jetty-7.6.8.v20121106

2014-06-1419:21:40.403:INFO:oejs.AbstractConnector:Started

[email protected]:3000

#<Serverorg.eclipse.jetty.server.Server@51f6292b>

ThiswillcompilethelatestversionofourClojureScriptsourcetoJavaScript.

Alternatively,youcanleavetheREPLrunningandsimplyaskcljsbuildtoauto-compilethesourcecodefromanotherterminalwindow:

leincljsbuildauto

Page 205: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Compiling"dev-resources/public/js/reagi_game.js"from("src/cljs"

"test/cljs""dev-resources/tools/repl")...

Successfullycompiled"dev-resources/public/js/reagi_game.js"in13.23869

seconds.

Nowyoucanpointyourbrowsertohttp://localhost:3000/andflyaroundyourspaceship!Don’tforgettoshootsomebulletsaswell!

Page 206: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 207: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ReagiandotherCESframeworksBackinChapter4,Introductiontocore.async,wehadanoverviewofthemaindifferencesbetweencore.asyncandCES.Anotherquestionthatmighthaveariseninthischapteristhis:howdowedecidewhichCESframeworktouse?

Theanswerislessclearthanbeforeandoftendependsonthespecificsofthetoolbeinglookedat.Wehavelearnedabouttwosuchtoolssofar:ReactiveExtensions(encompassingRxJS,RxJava,andRxClojure)andReagi.

ReactiveExtensions(Rx)isamuchmorematureframework.Itsfirstversionforthe.NETplatformwasreleasedin2011andtheideasinithavesinceevolvedsubstantially.

Additionally,portsforotherplatformssuchasRxJavaarebeingheavilyusedinproductionbybignamessuchasNetflix.

AdrawbackofRxisthatifyouwouldliketouseitbothinthebrowserandontheserver,youhavetousetwoseparateframeworks,RxJSandRxJava,respectively.WhiletheydosharethesameAPI,theyaredifferentcodebases,whichcanincurbugsthatmighthavebeensolvedinoneportbutnotyetinanother.

ForClojuredevelopers,italsomeansrelyingmoreoninteroperabilitytointeractwiththefullAPIofRx.

Reagi,ontheotherhand,isanewplayerinthisspacebutbuildsonthesolidfoundationlaidoutbycore.async.ItisfullydevelopedinClojureandsolvesthein-browser/on-serverissuebycompilingtobothClojureandClojureScript.

Reagialsoallowsseamlessintegrationwithcore.asyncviafunctionssuchasportandsubscribe,whichallowchannelstobecreatedfromeventstreams.

Moreover,theuseofcore.asyncinClojureScriptapplicationsisbecomingubiquitous,sochancesareyoualreadyhaveitasadependency.ThismakesReagianattractiveoptionforthetimeswhenweneedahigherlevelofabstractionthantheoneprovidedbycore.async.

Page 208: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 209: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

SummaryInthischapter,welearnedhowwecanusethetechniquesfromreactiveprogrammingwehavelearnedsofarinordertowritecodethatiscleanerandeasiertomaintain.Todoso,weinsistedonthinkingaboutasynchronouseventssimplyaslistsandsawhowthatwayofthinkinglendsitselfquiteeasilytobeingmodeledasaneventstream.Allourgamehastodo,then,isoperateonthesestreamsusingfamiliarsequenceprocessingfunctions.

WealsolearnedthebasicsofReagi,aframeworkforCESsimilartotheonewecreatedinChapter4,Introductiontocore.async,butthatismorefeaturerichandrobust.

Inthenextchapter,wewilltakeabreakfromCESandseehowamoretraditionalreactiveapproachbasedondataflowscanbeuseful.

Page 210: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 211: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Chapter7.TheUIasaFunctionSofarwehavetakenajourneythroughmanagingcomplexitybyefficientlyhandlingandmodelingasynchronousworkflowsintermsofstreamsofdata.Inparticular,Chapter4,Introductiontocore.asyncandChapter5,CreatingYourOwnCESFrameworkwithcore.asyncexploredwhat’sinvolvedinlibrariesthatprovideprimitivesandcombinatorsforCompositionalEventSystems.WealsobuiltasimpleClojureScriptapplicationthatmadeuseofourframework.

Onethingyoumighthavenoticedisthatnoneoftheexamplessofarhavedealtwithwhathappenstothedataoncewearereadytopresentittoourusers.It’sstillanopenquestionthatwe,asapplicationdevelopers,needtoanswer.

Inthischapter,wewilllookatonewaytohandleReactiveUserInterfacesinwebapplicationsusingReact(seehttp://facebook.github.io/react/),amodernJavaScriptframeworkdevelopedbyFacebook,aswellas:

LearnhowReactrendersuserinterfacesefficientlyBeintroducedtoOm,aClojureScriptinterfacetoReactLearnhowOmleveragespersistentdatastructuresforperformanceDeveloptwofullyworkingClojureScriptapplicationswithOm,includingtheuseofcore.asyncforintercomponentcommunication

Page 212: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

TheproblemwithcomplexwebUIsWiththeriseofsingle-pagewebapplications,itbecameamusttobeabletomanagethegrowthandcomplexityofaJavaScriptcodebase.ThesameappliestoClojureScript.

Inanefforttomanagethiscomplexity,aplethoraofJavaScriptMVCframeworkshaveemergedsuchasAngularJS,Backbone.js,Ember.js,andKnockoutJStonameafew.

Theyareverydifferent,butshareafewcommonfeatures:

Givesingle-pageapplicationsmorestructurebyprovidingmodels,views,controllers,templates,andsoonProvideclient-sideroutingTwo-waydatabinding

Inthischapter,we’llbefocusingonthelastgoal.

Two-waydatabindingisabsolutelycrucialifwearetodevelopevenamoderatelycomplexsingle-pagewebapplication.Here’showitworks.

Supposewe’redevelopingaphonebookapplication.Morethanlikely,wewillhaveamodel—orentity,map,whathaveyou—thatrepresentsacontact.Thecontactmodelmighthaveattributessuchasname,phonenumber,ande-mailaddress.

Ofcourse,thisapplicationwouldnotbeallthatusefulifuserscouldn’tupdatecontactinformation,sowewillneedaformwhichdisplaysthecurrentdetailsforacontactandletsyouupdatethecontact’sinformation.

ThecontactmodelmighthavebeenloadedviaanAJAXrequestandthenmighthaveusedexplicitDOMmanipulationcodetodisplaytheform.Thiswouldlooksomethinglikethefollowingpseudo-code:

functioneditContact(contactId){

contactService.get(contactId,function(data){

contactForm.setName(data.name);

contactForm.setPhone(data.phone);

contactForm.setEmail(data.email);

})

}

Butwhathappenswhentheuserupdatessomeone’sinformation?Weneedtostoreitsomehow.Onclickingonsave,afunctionsuchasthefollowingwoulddothetrick,assumingyou’reusingjQuery:

$("save-button").click(function(){

contactService.update(contactForm.serialize(),function(){

flashMessage.set("ContactUpdated.")

})

Thisseeminglyharmlesscodeposesabigproblem.Thecontactmodelforthisparticularpersonisnowoutofdate.Ifwewerestilldevelopingwebapplicationstheoldway,wherewereloadthepageateveryupdate,thiswouldn’tbeaproblem.However,thewholepointofsingle-pagewebapplicationsistoberesponsive,soitkeepsalotofstateontheclient,

Page 213: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

anditisimportanttokeepourmodelssyncedwithourviews.

Thisiswheretwo-waydatabindingcomesin.AnexamplefromAngularJSwouldlooklikethefollowing:

//JS

//intheController

$scope.contact={

name:'LeonardoBorges',

phone'+61xxxxxxxxx',

email:'[email protected]'

}

<!--HTML-->

<!--intheView-->

<form>

<inputtype="text"name="contactName"ng-model="contact.name"/>

<inputtype="text"name="contactPhone"ng-model="contact.phone"/>

<inputtype="text"name="contactEmail"ng-model="contact.email"/>

</form>

Angularisn’tthetargetofthischapter,soIwon’tdigintothedetails.Allweneedtoknowfromthisexampleisthat$scopeishowwetellAngulartomakeourcontactmodelavailabletoourviews.Intheview,thecustomattributeng-modeltellsAngulartolookupthatpropertyinthescope.Thisestablishesatwo-wayrelationshipinsuchawaythatwhenyourmodeldatachangesinthescope,AngularrefreshestheUI.Similarly,iftheusereditstheform,Angularupdatesthemodel,keepingeverythinginsync.

Thereare,however,twomainproblemswiththisapproach:

Itcanbeslow.ThewayAngularandfriendsimplementtwo-waydatabindingis,roughlyspeaking,byattachingeventhandlersandwatcherstoviewbothcustom-attributesandmodelattributes.Forcomplexenoughuserinterfaces,youwillstartnoticingthattheUIbecomesslowertorender,diminishingtheuserexperience.Itreliesheavilyonmutation.Asfunctionalprogrammers,westrivetolimitsideeffectstoaminimum.

Theslownessthatcomeswiththisandsimilarapproachesistwo-fold:firstly,AngularJSandfriendshaveto“watch”allpropertiesofeverymodelinthescopeinordertotrackupdates.Oncetheframeworkdeterminesthatdatahaschangedinthemodel,itthenlooksuppartsoftheUI,whichdependonthatinformation—suchasthefragmentsusingng-modelabove—andthenitre-rendersthem.

Secondly,theDOMistheslowestpartofmostsingle-pagewebapplications.Ifwethinkaboutitforamoment,theseframeworksaretriggeringdozensorperhapshundredsofDOMeventhandlersinordertokeepthedatainsync,eachofwhichendsupupdatinganode—orseveral—intheDOM.

Don’ttakemywordforitthough.IranasimplebenchmarktocompareapurecalculationversuslocatingaDOMelementandupdatingitsvaluetotheresultofthesaidcalculation.Herearetheresults—I’veusedJSPerftorunthebenchmark,andtheseresultsarefor

Page 214: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Chrome37.0.2062.94onMacOSXMavericks(seehttp://jsperf.com/purefunctions-vs-dom):

document.getElementsByName("sum")[0].value=1+2

//Operationspersecond:2,090,202

1+2

//Operationspersecond:780,538,120

UpdatingtheDOMisordersofmagnitudeslowerthanperformingasimplecalculation.Itseemslogicalthatwewouldwanttodothisinthemostefficientmannerpossible.

However,ifwedon’tkeepourdatainsync,we’rebackatsquareone.Thereshouldbeawaybywhichwecandrasticallyreducetheamountofrenderingbeingdone,whileretainingtheconvenienceoftwo-waydatabinding.Canwehaveourcakeandeatittoo?

Page 215: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 216: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

EnterReact.jsAswe’llseeinthischapter,theanswertothequestionposedintheprevioussectionisaresoundingyesand,asyoumighthaveguessed,itinvolvesReact.js.

Butwhatmakesitspecial?

It’swisetostartwithwhatReactisnot.ItisnotanMVCframeworkandassuchitisnotareplacementforthelikesofAngularJS,Backbone.js,andsoon.ReactfocusessolelyontheVinMVC,andpresentsarefreshinglydifferentwaytothinkaboutuserinterfaces.Wemusttakeaslightdetourinordertoexplorehowitdoesthat.

Page 217: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

LessonsfromfunctionalprogrammingAsfunctionalprogrammers,wedon’tneedtobeconvincedofthebenefitsofimmutability.Weboughtintothepremiselongago.However,shouldwenotbeabletouseimmutabilityefficiently,itwouldnothavebecomecommonplaceinfunctionalprogramminglanguages.

WeoweittothehugeamountofresearchthatwentintoPurelyFunctionalDataStructures—firstbyOkasakiinhisbookofthesametitle(seehttp://www.amazon.com/Purely-Functional-Structures-Chris-Okasaki/dp/0521663504/ref=sr_1_1?ie=UTF8&qid=1409550695&sr=8-1&keywords=purely+functional+data+structures)andthenimprovedbyothers.

Withoutit,ourprogramswouldbeballooning,bothinspaceandruntimecomplexity.

Thegeneralideaisthatgivenadatastructure,theonlywaytoupdateitisbycreatingacopyofitwiththedesireddeltaapplied:

(conj[123]4);;[1234]

Intheprecedingimage,wehaveasimplisticviewofhowconjoperates.Ontheleft,youhavetheunderlyingdatastructurerepresentingthevectorwewishtoupdate.Ontheright,wehavethenewlycreatedvector,which,aswecansee,sharessomestructurewiththepreviousvector,aswellascontainingournewitem.

TipInreality,theunderlyingdatastructureisatreeandtherepresentationwassimplifiedforthepurposesofthisbook.IhighlyrecommendreferringtoOkasaki’sbookshouldthereaderwantmoredetailsonhowpurelyfunctionaldatastructureswork.

Additionally,thesefunctionsareconsideredpure.Thatis,itrelateseveryinputtoasingleoutputanddoesnothingelse.Thisis,infact,remarkablysimilartohowReacthandlesuserinterfaces.

IfwethinkofaUIasavisualrepresentationofadatastructure,whichreflectsthecurrentstateofourapplication,wecan,withouttoomucheffort,thinkofUIupdatesasasimple

Page 218: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

functionwhoseinputistheapplicationstateandtheoutputisaDOMrepresentation.

You’llhavenoticedIdidn’tsaytheoutputisrenderingtotheDOM—thatwouldmakethefunctionimpureasrenderingisclearlyasideeffect.Itwouldalsomakeitjustasslowasthealternatives.

ThisDOMrepresentationisessentiallyatreeofDOMnodesthatmodelhowyourUIshouldlook,andnothingelse.

ReactcallsthisrepresentationaVirtualDOM,androughlyspeaking,insteadofwatchingindividualbitsandpiecesofapplicationstatethattriggeraDOMre-renderuponchange,ReactturnsyourUIintoafunctiontowhichyougivethewholeapplicationstate.

Whenyougivethisfunctionthenewupdatedstate,ReactrendersthatstatetotheVirtualDOM.RemembertheVirtualDOMissimplyadatastructure,sotherenderingisextremelyfast.Onceit’sdone,Reactdoesoneoftwothings:

ItcommitstheVirtualDOMtotheactualDOMifthisisthefirstrender.Otherwise,itcomparesthenewVirtualDOMwiththecurrentVirtualDOM,cachedfromthepreviousrenderoftheapplication.ItthenusesanefficientdiffalgorithmtocomputetheminimumsetofchangesrequiredtoupdatetherealDOM.Finally,itcommitsthisdeltatotheDOM.

WithoutdiggingintothenutsandboltsofReact,thisisessentiallyhowitisimplementedandthereasonitisfasterthanthealternatives.Conceptually,Reacthitsthe“refresh”buttonwheneveryourapplicationstatechanges.

AnothergreatbenefitisthatbythinkingofyourUIasafunctionfromapplicationstatetoaVirtualDOM,werecoversomeofthereasoningwe’reabletodowhenworkingwithimmutabledatastructuresinfunctionallanguages.

Intheupcomingsections,wewillunderstandwhythisisabigwinforusClojuredevelopers.

Page 219: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 220: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ClojureScriptandOmWhyhaveIspentsixpagestalkingaboutJavaScriptandReactinaClojurebook?IpromiseI’mnottryingtowasteyourprecioustime;wesimplyneededsomecontexttounderstandwhat’stocome.

OmisaClojureScriptinterfacetoReact.jsdevelopedbytheprolificandamazingindividualDavidNolen,fromCognitect.Yes,hehasalsodevelopedcore.logic,core.match,andtheClojureScriptcompiler.That’showprolific.ButIdigress.

WhenFacebookreleasedReact,Davidimmediatelysawthepotentialand,moreimportantly,howtotakeadvantageoftheassumptionsweareabletomakewhenprogramminginClojure,themostimportantofwhichisthatdatastructuresdon’tchange.

Reactprovidesseveralcomponentlife-cyclefunctionsthatallowdeveloperstocontrolvariouspropertiesandbehaviors.Oneinparticular,shouldComponentUpdate,isusedtodecidewhetheracomponentneedstobere-rendered.

Reacthasabigchallengehere.JavaScriptisinherentlymutable,soitisextremelyhard,whencomparingVirtualDOMTrees,toidentifywhichnodeshavechangedinanefficientway.ReactemploysafewheuristicsinordertoavoidO(n3)worst-caseperformanceandisabletodoitinO(n)mostofthetime.Sinceheuristicsaren’tperfect,wecanchoosetoprovideourownimplementationofshouldComponentUpdateandtakeadvantageoftheknowledgewepossesswhenrenderingacomponent.

ClojureScript,ontheotherhand,usesimmutabledatastructures.Assuch,OmprovidesthesimplestandmostefficientimplementationpossibleforshouldComponentUpdate:asimplereferenceequalitycheck.

Becausewe’realwaysdealingwithimmutabledatastructures,inordertoknowwhethertwotreesarethesame,allweneedtodoiscomparewhethertheirrootsarethesame.Iftheyare,we’redone.Otherwise,descendandrepeattheprocess.ThisisguaranteedtoyieldO(logn)runtimecomplexityandallowsOmtoalwaysrendertheUIfromtherootefficiently.

Ofcourse,performanceisn’ttheonlythingthat’sgoodaboutOm—wewillnowexplorewhatmakesanOmapplication.

Page 221: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 222: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

BuildingasimpleContactsapplicationwithOmThischapterhasbeenverytextheavysofar.It’stimewegetourhandsdirtyandbuildasimpleOmapplication.Sincewetalkedaboutcontactsbefore,that’swhatwewillstartwith.

ThemaindriverbehindReactandOmistheabilitytobuildhighlyreusable,self-containedcomponentsand,assuch,eveninasimpleContactsapplication,wewillhavemultiplecomponentsworkinginconcerttoachieveacommongoal.

Thisiswhatourusersshouldbeabletodointheapplication:

DisplayalistofcontactscurrentlyinstorageDisplaythedetailsofagivencontactEditthedetailsofaspecificcontact

Andoncewe’redone,itwilllooklikethefollowing:

Page 223: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

TheContactsapplicationstateAsmentionedpreviously,Om/ReactwilleventuallyrendertheDOMbasedonourapplicationstate.We’llbeusingdatathat’sinmemorytokeeptheexamplesimple.Here’swhatourapplicationstatewilllooklike:

(defapp-state

(atom{:contacts{1{:id1

:name"JamesHetfield"

:email"[email protected]"

:phone"+1XXXXXXXXX"}

2{:id2

:name"AdamDarski"

:email"[email protected]"

:phone"+48XXXXXXXXX"}}

:selected-contact-id[]

:editing[false]}))

ThereasonwekeepthestateinanatomisthatOmusesthattore-rendertheapplicationifweswap!orreset!it,forinstance,ifweloadsomedatafromtheserveraftertheapplicationhasbeenrenderedforthefirsttime.

Thedatainthestateitselfshouldbemostlyself-explanatory.Wehaveamapcontainingallcontacts,akeyrepresentingwhetherthereiscurrentlyacontactselected,andaflagthatindicateswhetherwearecurrentlyeditingtheselectedcontact.Whatmightlookoddisthatboth:selected-contact-idand:editingkeyspointtoavector.Justbearwithmeforamoment;thereasonforthiswillbecomeclearshortly.

Nowthatwehaveadraftofourapplicationstate,it’stimewethinkabouthowthestatewillflowthroughthedifferentcomponentsinourapp.Apictureisworthathousandwords,sothefollowingdiagramshowsthehigh-levelarchitecturethroughwhichourdatawillflow:

Page 224: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Intheprecedingimage,eachfunctioncorrespondstoanOmcomponent.Attheveryleast,theytakesomepieceofdataastheirinitialstate.Whatisinterestinginthisimageisthataswedescendintoourmorespecializedcomponents,theyrequestlessstatethanthemaincomponent,contacts-app.Forinstance,thecontacts-viewcomponentneedsallcontactsaswellastheIDoftheselectedcontact.Thedetails-panel-viewcomponent,ontheotherhand,onlyneedsthecurrentlyselectedcontact,andwhetherit’sbeingeditedornot.ThisisacommonpatterninOmandweusuallywanttoavoidover-sharingtheapplicationstate.

Witharoughunderstandingofourhigh-levelarchitecture,wearereadytostartbuildingourContactsapplication.

Page 225: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

SettinguptheContactsprojectOnceagain,wewillusealeiningentemplatetohelpusgetstarted.Thistimewe’llbeusingom-start(seehttps://github.com/magomimmo/om-start-template),alsobyMimmoCosenza(seehttps://github.com/magomimmo).Typethisintheterminaltocreateabaseprojectusingthistemplate:

leinnewom-startcontacts

cdcontacts

Next,let’sopentheproject.cljfileandmakesurewehavethesameversionsforthevariousdifferentdependenciesthetemplatepullsin.Thisisjustsothatwedon’thaveanysurpriseswithincompatibleversions:

...

:dependencies[[org.clojure/clojure"1.6.0"]

[org.clojure/clojurescript"0.0-2277"]

[org.clojure/core.async"0.1.338.0-5c5012-alpha"]

[om"0.7.1"]

[com.facebook/react"0.11.1"]]

...

Tovalidatethenewprojectskeleton,stillintheterminal,typethefollowingtoauto-compileyourClojureScriptsourcefiles:

leincljsbuildauto

CompilingClojureScript.

Compiling"dev-resources/public/js/contacts.js"from("src/cljs""dev-

resources/tools/repl")...

Successfullycompiled"dev-resources/public/js/contacts.js"in9.563

seconds.

Now,weshouldseethetemplatedefault“HelloWorld”pageifweopenthedev-resources/public/index.htmlfileinthebrowser.

Page 226: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ApplicationcomponentsThenextthingwe’lldoisopenthesrc/cljs/contacts/core.cljsfile,whichiswhereourapplicationcodewillgo,andmakesureitlookslikethefollowingsothatwehaveacleanslatewiththeappropriatenamespacedeclaration:

(nscontacts.core

(:require[om.core:asom:include-macrostrue]

[om.dom:asdom:include-macrostrue]))

(enable-console-print!)

(defapp-state

(atom{:contacts{1{:id1

:name"JamesHetfield"

:email"[email protected]"

:phone"+1XXXXXXXXX"}

2{:id2

:name"AdamDarski"

:email"[email protected]"

:phone"+48XXXXXXXXX"}}

:selected-contact-id[]

:editing[false]}))

(om/root

contacts-app

app-state

{:target(.js/document(getElementById"app"))})

EveryOmapplicationstartswitharootcomponentcreatedbytheom/rootfunction.Ittakesasargumentsafunctionrepresentingacomponent—contacts-app—theinitialstateoftheapplication—app-state—andamapofoptionsofwhichtheonlyonewecareaboutis:target,whichtellsOmwheretomountourrootcomponentontheDOM.

Inthisinstance,itwillmountonaDOMelementwhoseIDisapp.Thiselementwasgiventousbytheom-starttemplateandislocatedinthedev-resources/public/index.htmlfile.

Ofcourse,thiscodewon’tcompileyet,aswedon’thavethecontacts-apptemplate.Let’ssolvethatandcreateitabovetheprecedingdeclaration—we’reimplementingthecomponentsbottom-up:

(defncontacts-app[dataowner]

(reify

om/IRender

(render[this]

(let[[selected-id:asselected-id-cursor]

(:selected-contact-iddata)]

(dom/divnil

(om/buildcontacts-view

{:contacts(:contactsdata)

:selected-id-cursorselected-id-cursor})

(om/builddetails-panel-view

{:contact(get-indata[:contacts

Page 227: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

selected-id])

:editing-cursor(:editingdata)}))))))

Thissnippetintroducesanumberofnewfeaturesandterminology,soitdeservesafewparagraphs.

Whendescribingom/root,wesawthatitsfirstargumentmustbeanOmcomponent.Thecontact-appfunctioncreatesonebyreifyingtheom/IRenderprotocol.Thisprotocolcontainsasinglefunction—render—whichgetscalledwhentheapplicationstatechanges.

TipClojureusesreifytoimplementprotocolsorJavainterfacesonthefly,withouttheneedtocreateanewtype.YoucanreadmoreaboutthisonthedatatypespageoftheClojuredocumentationathttp://clojure.org/datatypes.

TherenderfunctionmustreturnanOm/ReactcomponentorsomethingReactknowshowtorender—suchasaDOMrepresentationofthecomponent.Theargumentstocontacts-apparestraightforward:dataisthecomponentstateandowneristhebackingReactcomponent.

Movingdownthesourcefile,intheimplementationofrender,wehavethefollowing:

(let[[selected-id:asselected-id-cursor]

(:selected-contact-iddata)]

...)

Ifwerecallfromourapplicationstate,thevalueof:selected-contact-idis,atthisstage,anemptyvector.Here,then,wearedestructuringthisvectorandgivingitaname.Whatyoumightbewonderingnowiswhyweboundthevectortoavariablenamedselected-id-cursor.Thisistoreflectthefactthatatthispointinthelifecycleofacomponent,selected-id-cursorisn’tavectoranylongerbutratheritisacursor.

Page 228: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

OmcursorsOnceom/rootcreatesourrootcomponent,sub-componentsdon’thavedirectaccesstothestateatomanylonger.Instead,componentsreceiveacursorcreatedfromtheapplicationstate.

Cursorsaredatastructuresthatrepresentaplaceintheoriginalstateatom.Youcanusecursorstoread,delete,update,orcreateavaluewithnoknowledgeoftheoriginaldatastructure.Let’staketheselected-id-cursorcursorasanexample:

Atthetop,wehaveouroriginalapplicationstate,whichOmturnsintoacursor.Whenwerequestthe:selected-contact-idkeyfromit,Omgivesusanothercursorrepresentingthatparticularplaceinthedatastructure.Itjustsohappensthatitsvalueistheemptyvector.

WhatisinterestingaboutthiscursoristhatifweupdateitsvalueusingoneofOm’sstatetransitionfunctionssuchasom/transact!andom/update!—wewillexplaintheseshortly—itknowshowtopropagatethechangeupthetreeandallthewaybacktotheapplicationstateatom.

Thisisimportantbecauseaswehavebrieflystatedbefore,itiscommonpracticetohaveourmorespecializedcomponentsdependonspecificpartsoftheapplicationstaterequiredforitscorrectoperation.

Page 229: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Byusingcursors,wecaneasilypropagatechangeswithoutknowingwhattheapplicationstatelookslike,thusavoidingtheneedtoaccesstheglobalstate.

TipYoucanthinkofcursorsaszippers.Conceptually,theyserveasimilarpurposebuthavedifferentAPIs.

Page 230: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

FillingintheblanksMovingdownthecontacts-appcomponent,wenowhavethefollowing:

(dom/divnil

(om/buildcontacts-view

{:contacts(:contactsdata)

:selected-id-cursorselected-id-cursor})

(om/builddetails-panel-view

{:contact(get-indata[:contacts

selected-id])

:editing-cursor(:editingdata)}))

ThedomnamespacecontainsthinwrappersaroundReact’sDOMclasses.It’sessentiallythedatastructurerepresentingwhattheapplicationwilllooklike.Next,weseetwoexamplesofhowwecancreateOmcomponentsinsideanotherOmcomponent.Weusetheom/buildfunctionforthatandcreatethecontacts-viewanddetails-panel-viewcomponents.Theom/buildfunctiontakesasargumentsthecomponentfunction,thecomponentstate,and,optionally,amapofoptionswhicharen’timportantforthisexample.

Atthispoint,wehavealreadystartedtolimitthestatewewillpassintothesub-componentsbycreatingsub-cursors.

Accordingtothesourcecode,thenextcomponentweshouldlookatiscontacts-view.Hereitisinfull:

(defncontacts-view[{:keys[contactsselected-id-cursor]}owner]

(reify

om/IRender

(render[_]

(dom/div#js{:style#js{:float"left"

:width"50%"}}

(applydom/ulnil

(om/build-allcontact-summary-view(valscontacts)

{:shared{:selected-id-cursorselected-

id-cursor}}))))))

Hopefully,thesourceofthiscomponentlooksalittlemorefamiliarnow.Asbefore,wereifyom/IRendertoprovideaDOMrepresentationofourcomponent.Itcomprisesasingledivelement.Thistimewegiveasthesecondargumenttodom/divahash-maprepresentingHTMLattributes.Weareusingsomeinlinestyles,butideallywewoulduseanexternalstylesheet.

TipIfyouarenotfamiliarwiththe#js{…}syntax,it’ssimplyareadermacrothatexpandsto(clj->js{…})inordertoconvertaClojureScripthash-mapintoaJavaScriptobject.Theonlythingtowatchforisthatitisnotrecursive,asevidencedbythenesteduseof#js.

Thethirdargumenttodom/divisslightlymorecomplexthanwhatwehaveseensofar:

(applydom/ulnil

Page 231: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

(om/build-allcontact-summary-view(valscontacts)

{:shared{:selected-id-cursorselected-

id-cursor}}))

Eachcontactwillberepresentedbyali(listitem)HTMLnode,sowestartbywrappingtheresultintoadom/ulelement.Then,weuseom/build-alltobuildalistofcontact-summary-viewcomponents.Omwill,inturn,callom/buildforeachcontactinvalscontacts.

Lastly,weusethethirdargumenttoom/build-all—theoptionsmap—todemonstratehowwecansharestatebetweencomponentswithouttheuseofglobalstate.We’llseehowthat’susedinthenextcomponent,contact-summary-view:

(defncontact-summary-view[{:keys[namephone]:ascontact}owner]

(reify

om/IRender

(render[_]

(dom/li#js{:onClick#(select-contact!@contact

(om/get-sharedowner

:selected-id-cursor))}

(dom/spannilname)

(dom/spannilphone)))))

Ifwethinkofourapplicationasatreeofcomponents,wehavenowreachedoneofitsleaves.Thiscomponentsimplyreturnsadom/linodewiththecontact’snameandphoneinit,wrappedindom/spannodes.

Italsoinstallsahandlertothedom/lionClickevent,whichwecanusetoupdatethestatecursor.

Weuseom/get-sharedtoaccessthesharedstateweinstalledearlierandpasstheresultingcursorintoselect-contact!Wealsopassthecurrentcontact,but,ifyoulookclosely,wehavetoderefitfirst:

@contact

ThereasonforthisisthatOmdoesn’tallowustomanipulatecursorsoutsideoftherenderphase.Byderefingthecursor,wehaveitsmostrecentunderlyingvalue.Nowselect-contact!hasallitneedstoperformtheupdate:

(defnselect-contact![contactselected-id-cursor]

(om/update!selected-id-cursor0(:idcontact)))

Wesimplyuseom/update!tosetthevalueoftheselected-id-cursorcursoratindex0totheidofthecontact.Asmentionedpreviously,thecursortakescareofpropagatingthechange.

TipYoucanthinkofom/update!asthecursorsversionofclojure.core/reset!usedinatoms.Conversely,thesameappliestoom/transact!andclojure.core/swap!,respectively.

Wearemovingatagoodpace.It’stimewelookatthenextcomponent,details-panel-

Page 232: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

view:

(defndetails-panel-view[dataowner]

(reify

om/IRender

(render[_]

(dom/div#js{:style#js{:float"right"

:width"50%"}}

(om/buildcontact-details-viewdata)

(om/buildcontact-details-form-viewdata)))))

Thiscomponentshouldnowlookfairlyfamiliar.Allitdoesisbuildtwoothercomponents,contact-details-viewandcontact-details-form-view:

(defncontact-details-view[{{:keys[namephoneemailid]:ascontact}

:contact

editing:editing-cursor}

owner]

(reify

om/IRender

(render[_]

(dom/div#js{:style#js{:display(if(getediting0)"none""")}}

(dom/h2nil"Contactdetails")

(ifcontact

(dom/divnil

(dom/h3#js{:style#js{:margin-bottom"0px"}}

(:namecontact))

(dom/spannil(:phonecontact))(dom/brnil)

(dom/spannil(:emailcontact))(dom/brnil)

(dom/button#js{:onClick#(om/update!editing0

true)}

"Edit"))

(dom/spannil"Nocontactselected"))))))

Thecontact-details-viewcomponentreceivestwopiecesofstate:thecontactandtheeditingflag.Ifwehaveacontact,wesimplyrenderthecomponent.However,weusetheeditingflagtohideit,ifweareeditingit.Thisissothatwecanshowtheeditforminthenextcomponent.WealsoinstallanonClickhandlertotheEditbuttonsothatwecanupdatetheeditingcursor.

Thecontact-details-form-viewcomponentreceivesthesameargumentsbutrendersthefollowingforminstead:

(defncontact-details-form-view[{{:keys[namephoneemailid]:ascontact}

:contact

editing:editing-cursor}

owner]

(reify

om/IRender

(render[_]

(dom/div#js{:style#js{:display(if(getediting0)"""none")}}

(dom/h2nil"Contactdetails")

(ifcontact

(dom/divnil

(dom/input#js{:type"text"

Page 233: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

:valuename

:onChange#(update-

contact!%contact:name)})

(dom/input#js{:type"text"

:valuephone

:onChange#(update-

contact!%contact:phone)})

(dom/input#js{:type"text"

:valueemail

:onChange#(update-

contact!%contact:email)})

(dom/button#js{:onClick#(om/update!editing0

false)}

"Save"))

(dom/divnil"Nocontactselected"))))))

Thisisthecomponentresponsibleforactuallyupdatingthecontactinformationbasedontheform.Itdoessobycallingupdate-contact!withtheJavaScriptevent,thecontactcursor,andthekeyrepresentingtheattributetobeupdated:

(defnupdate-contact![econtactkey]

(om/update!contactkey(..e-target-value)))

Asbefore,wesimplyuseom/update!insteadofom/transact!aswearesimplyreplacingthevalueofthecursorattributewiththecurrentvalueoftheformfieldwhichtriggeredtheevente.

NoteIfyou’renotfamiliarwiththe..syntax,it’ssimplyaconveniencemacroforJavaandJavaScriptinteroperability.Thepreviousexampleexpandsto:

(.(.e-target)-value)

ThisandotherinteroperabilityoperatorsaredescribedintheJavaInteroppageoftheClojurewebsite(seehttp://clojure.org/java_interop).

Thisisit.Makesureyourcodeisstillcompiling—orifyouhaven’tyet,starttheauto-compilationbytypingthefollowingintheterminal:

leincljsbuildauto

Then,openupdev-resources/public/index.htmlagaininyourbrowserandtakeourContactsappforaspin!Noteinparticularhowtheapplicationstateisalwaysinsyncwhileyoueditthecontactattributes.

Ifthereareanyissuesatthisstage,makesurethesrc/cljs/contacts/core.cljsfilematchesthecompanioncodeforthisbook.

Page 234: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 235: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

IntercomponentcommunicationInourpreviousexample,thecomponentswebuiltcommunicatedwitheachotherexclusivelythroughtheapplicationstate,bothforreadingandtransactingdata.Whilethisapproachworks,itisnotalwaysthebestexceptforverysimpleusecases.Inthissection,wewilllearnanalternatewayofperformingthiscommunicationusingcore.asyncchannels.

Theapplicationwewillbuildisasupersimplevirtualagileboard.Ifyou’veheardofit,it’ssimilartoTrello(seehttps://trello.com/).Ifyouhaven’t,fearnot,it’sessentiallyataskmanagementwebapplicationinwhichyouhavecardsthatrepresenttasksandyoumovethembetweencolumnssuchasBacklog,InProgress,andDone.

Bytheendofthissection,theapplicationwilllooklikethefollowing:

We’lllimitourselvestoasinglefeature:movingcardsbetweencolumnsbydragginganddroppingthem.Let’sgetstarted.

Page 236: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

CreatinganagileboardwithOmWe’realreadyfamiliarwiththeom-start(seehttps://github.com/magomimmo/om-start-template)leiningentemplate,andsincethereisnoreasontochangeit,that’swhatwewillusetocreateourproject—whichIcalledom-pmforOmProjectManagement:

leinnewom-startom-pm

cdom-pm

Asbefore,weshouldensurewehavetherightdependenciesinourproject.cljfile:

:dependencies[[org.clojure/clojure"1.6.0"]

[org.clojure/clojurescript"0.0-2511"]

[org.om/om"0.8.1"]

[org.clojure/core.async"0.1.346.0-17112a-alpha"]

[com.facebook/react"0.12.2"]]

Nowvalidatethatweareingoodshapebymakingsuretheprojectcompilesproperly:

leincljsbuildauto

CompilingClojureScript.

Compiling"dev-resources/public/js/om_pm.js"from("src/cljs""dev-

resources/tools/repl")...

Successfullycompiled"dev-resources/public/js/om_pm.js"in13.101seconds.

Next,openthesrc/cljs/om_pm/core.cljsfileandaddthenamespacesthatwewillbeusingtobuildtheapplication:

(nsom-pm.core

(:require[om.core:asom:include-macrostrue]

[om.dom:asdom:include-macrostrue]

[cljs.core.async:refer[put!chan<!]]

[om-pm.util:refer[set-transfer-data!get-transfer-data!move-

card!]])

(:require-macros[cljs.core.async.macros:refer[gogo-loop]]))

Themaindifferencethistimeisthatwearerequiringcore.asyncfunctionsandmacros.Wedon’tyethaveanom-pm.utilnamespace,butwe’llgettothatattheend.

Page 237: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

TheboardstateIt’stimewethinkwhatourapplicationstatewilllooklike.Ourmainentityinthisapplicationisthecard,whichrepresentsataskandhastheattributesid,title,anddescription.Wewillstartbydefiningacoupleofcards:

(defcards[{:id1

:title"Groceriesshopping"

:description"Almondmilk,mixednuts,eggs…"}

{:id2

:title"Expenses"

:description"Submitlastclient'sexpensereport"}])

Thisisn’tourapplicationstateyet,butratherapartofit.Anotherimportantpieceofstateisawaytotrackwhichcardsareonwhichcolumns.Tokeepthingssimple,wewillworkwithonlythreecolumns:Backlog,InProgress,andDone.Bydefault,allcardsstartoutinthebacklog:

(defapp-state

(atom{:cardscards

:columns[{:title"Backlog"

:cards(mapv:idcards)}

{:title"InProgress"

:cards[]}

{:title"Done"

:cards[]}]}))

Thisisallthestateweneed.Columnshavea:titleanda:cardsattribute,whichcontainstheIDsofallcardsinthatcolumn.

Additionally,wewillhaveahelperfunctiontomakefindingcardsmoreconvenient:

(defncard-by-id[id]

(first(filterv#(=id(:id%))cards)))

TipBewareoflazysequences

YoumighthavenoticedtheuseofmapvinsteadofmapforretrievingthecardsIDs.Thisisasubtlebutimportantdifference:mapislazybydefault,butOmcanonlycreatecursorsformapsandvectors.Usingmapvgivesusavectorback,avoidinglazinessaltogether.

Hadwenotdonethat,OmwouldconsiderthelistofIDsasanormalvalueandwewouldnotbeabletotransactit.

Page 238: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ComponentsoverviewTherearemanywaystosliceupanOmapplicationintocomponents,andinthissection,wewillpresentonewayaswewalkthrougheachcomponent’simplementation.

Theapproachwewillfollowissimilartoourpreviousapplicationinthatfromthispointon,wepresentthecomponentsbottom-up.

Beforeweseeourfirstcomponent,however,weshouldstartwithOm’sownrootcomponent:

(om/rootproject-viewapp-state

{:target(.js/document(getElementById"app"))})

Thisgivesusahintastowhatournextcomponentwillbe,project-view:

(defnproject-view[appowner]

(reify

om/IInitState

(init-state[_]

{:transfer-chan(chan)})

om/IWillMount

(will-mount[_]

(let[transfer-chan(om/get-stateowner:transfer-chan)]

(go-loop[]

(let[transfer-data(<!transfer-chan)]

(om/transact!app:columns

#(move-card!%transfer-data))

(recur)))))

om/IRenderState

(render-state[thisstate]

(dom/divnil

(applydom/ulnil

(om/build-allcolumn-view(:columnsapp)

{:shared{:cards(:cardsapp)}

:init-statestate}))))))

Page 239: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

LifecycleandcomponentlocalstateThepreviouscomponentisfairlydifferentfromtheoneswehaveseensofar.Morespecifically,itimplementstwonewprotocols:om/IInitStateandom/IWillMount.Additionally,wedroppedom/IRenderaltogetherinfavorofom/IRenderState.Beforeweexplainwhatthesenewprotocolsaregoodfor,weneedtodiscussourhigh-leveldesign.

Theproject-viewcomponentisourapplication’smainentrypointandreceivesthewholeapplicationstateasitsfirstargument.AsinourearlierContactsapplication,ittheninstantiatestheremainingcomponentswiththedatatheyneed.

DifferentfromtheContactsexample,however,itcreatesacore.asyncchannel—transfer-chan—whichworksasamessagebus.Theideaisthatwhenwedragacardfromonecolumnanddropitonanother,oneofourcomponentswillputatransfereventinthischannelandletsomeoneelse—mostlikelyagoblock—performtheactualmoveoperation.

Thisisdoneinthefollowingsnippettakenfromthecomponentshownearlier:

om/IInitState

(init-state[_]

{:transfer-chan(chan)})

ThiscreateswhatOmcallsthecomponentlocalstate.Itusesadifferentlifecycleprotocol,om/IInitState,whichisguaranteedtobecalledonlyonce.Afterall,weneedasinglechannelforthiscomponent.init-stateshouldreturnamaprepresentingthelocalstate.

Nowthatwehavethechannel,weneedtoinstallago-looptohandlemessagessenttoit.Forthispurpose,weuseadifferentprotocol:

om/IWillMount

(will-mount[_]

(let[transfer-chan(om/get-stateowner:transfer-chan)]

(go-loop[]

(let[transfer-data(<!transfer-chan)]

(om/transact!app:columns#(move-card!%transfer-data))

(recur)))))

Likethepreviousprotocol,om/IWillMountisalsoguaranteedtobecalledonceinthecomponentlifecycle.ItiscalledwhenitisabouttobemountedintotheDOMandistheperfectplacetoinstallthego-loopintoourchannel.

TipWhencreatingcore.asyncchannelsinOmapplications,itisimportanttoavoidcreatingtheminsidelife-cyclefunctionsthatarecalledmultipletimes.Besidesnon-deterministicbehavior,thisisasourceofmemoryleaks.

Wegetholdofitfromthecomponentlocalstateusingtheom/get-statefunction.Oncewegetamessage,wetransactthestate.Wewillseewhattransfer-datalookslikeveryshortly.

Wecompletethecomponentbyimplementingitsrenderfunction:

Page 240: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

...

om/IRenderState

(render-state[thisstate]

(dom/divnil

(applydom/ulnil

(om/build-allcolumn-view(:columnsapp)

{:shared{:cards(:cardsapp)}

:init-statestate}))))

...

Theom/IRenderStatefunctionservesthesamepurposeofom/IRender,thatis,itshouldreturntheDOMrepresentationofwhatthecomponentshouldlooklike.However,itdefinesadifferentfunction,render-state,whichreceivesthecomponentlocalstateasitssecondargument.Thisstatecontainsthemapwecreatedduringtheinit-statephase.

Page 241: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

RemainingcomponentsNext,wewillbuildmultiplecolumn-viewcomponents,onepercolumn.Eachofthemreceivesthelistofcardsfromtheapplicationstateastheirsharedstate.WewillusethattoretrievethecarddetailsfromtheIDswestoreineachcolumn.

Wealsousethe:init-statekeytoinitializethelocalstateofeachcolumnviewwithourchannel,sinceallcolumnsneedareferencetoit.Here’swhatthecomponentlookslike:

(defncolumn-view[{:keys[titlecards]}owner]

(reify

om/IRenderState

(render-state[this{:keys[transfer-chan]}]

(dom/div#js{:style#js{:border"1pxsolidblack"

:float"left"

:height"100%"

:width"320px"

:padding"10px"}

:onDragOver#(.preventDefault%)

:onDrop#(handle-drop%transfer-chantitle)}

(dom/h2niltitle)

(applydom/ul#js{:style#js{:list-style-type"none"

:padding"0px"}}

(om/build-all(partialcard-viewtitle)

(mapvcard-by-idcards)))))))

Thecodeshouldlookfairlyfamiliaratthispoint.WeusedinlineCSSintheexampletokeepitsimple,butinarealapplication,wewouldprobablyhaveusedanexternalstylesheet.

Weimplementrender-stateoncemoretoretrievethetransferchannel,whichwillbeusedwhenhandlingtheonDropJavaScriptevent.ThiseventisfiredbythebrowserwhenauserdropsadraggableDOMelementontothiscomponent.handle-droptakescareofthatlikeso:

(defnhandle-drop[etransfer-chancolumn-title]

(.preventDefaulte)

(let[data{:card-id

(js/parseInt(get-transfer-data!e"cardId"))

:source-column

(get-transfer-data!e"sourceColumn")

:destination-column

column-title}]

(put!transfer-chandata)))

Thisfunctioncreatesthetransferdata—amapwiththekeys:card-id,:source-column,and:destination-column—whichiseverythingweneedtomovethecardsbetweencolumns.Finally,weput!itintothetransferchannel.

Next,webuildanumberorcard-viewcomponents.Asmentionedpreviously,Omcan’tcreatecursorsfromlazysequences,soweusefiltervtogiveeachcard-viewavectorcontainingtheirrespectivecards.Let’sseeitssource:

Page 242: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

(defncard-view[column{:keys[idtitledescription]:ascard}owner]

(reify

om/IRender

(render[this]

(dom/li#js{:style#js{:border"1pxsolidblack"}

:draggabletrue

:onDragStart(fn[e]

(set-transfer-data!e"cardId"id)

(set-transfer-data!e"sourceColumn"

column))}

(dom/spanniltitle)

(dom/pnildescription)))))

Asthiscomponentdoesn’tneedanylocalstate,wegobacktousingtheIRenderprotocol.Additionally,wemakeitdraggableandinstallaneventhandlerontheonDragStartevent,whichwillbetriggeredwhentheuserstartsdraggingthecard.

Thiseventhandlersetsthetransferdata,whichweusefromhandle-drop.

Wehaveglossedoverthefactthatthesecomponentsuseafewutilityfunctions.That’sOK,aswewillnowdefinetheminanewnamespace.

Page 243: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

UtilityfunctionsGoaheadandcreateanewfileundersrc/cljs/om_pm/calledutil.cljsandaddthefollowingnamespacedeclaration:

(nsom-pm.util)

Forconsistency,wewilllookatthefunctionsbottom-up,startingwithmove-card!:

(defncolumn-idx[titlecolumns]

(first(keep-indexed(fn[idxcolumn]

(when(=title(:titlecolumn))

idx))

columns)))

(defnmove-card![columns{:keys[card-idsource-columndestination-

column]}]

(let[from(column-idxsource-columncolumns)

to(column-idxdestination-columncolumns)]

(->columns

(update-in[from:cards](fn[cards]

(remove#{card-id}cards)))

(update-in[to:cards](fn[cards]

(conjcardscard-id))))))

Themove-card!functionreceivesacursorforthecolumnsinourapplicationstateandsimplymovescard-idbetweenthesourceanddestination.Youwillnoticewedidn’tneedanyaccesstocore.asyncorOmspecificfunctions,whichmeansthisfunctionispureandthereforeeasytotest.

Next,wehavethefunctionsthathandletransferdata:

(defnset-transfer-data![ekeyvalue]

(.setData(->e.-nativeEvent.-dataTransfer)

keyvalue))

(defnget-transfer-data![ekey]

(->(->e.-nativeEvent.-dataTransfer)

(.getDatakey)))

ThesefunctionsuseJavaScriptinteroperabilitytointeractwithHTML’sDataTransfer(seehttps://developer.mozilla.org/en-US/docs/Web/API/DataTransfer)object.Thisishowbrowserssharedatarelatedtodraganddropevents.

Now,let’ssimplysavethefileandmakesurethecodecompilesproperly.Wecanfinallyopendev-resources/public/index.htmlinthebrowserandplayaroundwiththeproductofourwork!

Page 244: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 245: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ExercisesInthisexercise,wewillmodifytheom-pmprojectwecreatedintheprevioussection.Theobjectiveistoaddkeyboardshortcutssothatpoweruserscanoperatetheagileboardmoreefficiently.

Theshortcutstobesupportedare:

Theup,down,left,andrightarrowkeys:Theseallowtheusertonavigatethroughthecards,highlightingthecurrentoneThenandpkeys:Theseareusedtomovethecurrentcardtothenext(right)orprevious(left)column,respectively

Thekeyinsighthereistocreateanewcore.asyncchannel,whichwillcontainkeypressevents.Theseeventswillthentriggertheactionsoutlinedpreviously.WecanusetheGoogleclosurelibrarytolistenforevents.Justaddthefollowingrequiretotheapplicationnamespace:

(:require[goog.events:asevents])

Then,usethisfunctiontocreateachannelfromDOMevents:

(defnlisten[eltype]

(let[c(chan)]

(events/listeneltype#(put!c%))

c))

Theactuallogicofmovingthecardsaroundbasedonkeyboardshortcutscanbeimplementedinanumberofways,sodon’tforgettocompareyoursolutionwiththeanswersprovidedinthisbook’scompanioncode.

Page 246: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 247: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

SummaryInthischapter,wesawadifferentapproachonhowtohandlereactivewebinterfacesbyOmandReact.Inturn,theseframeworksmakethispossibleandpainlessbyapplyingfunctionalprogrammingprinciplessuchasimmutabilityandpersistentdatastructuresforefficientrendering.

WealsolearnedtothinktheOmwaybystructuringourapplicationsasaseriesoffunctions,whichreceivestateandoutputaDOMrepresentationofstatechanges.

Additionally,wesawthatbystructuringapplicationstatetransitionsthroughcore.asyncchannels,weseparatethepresentationlogicfromthecode,whichwillactuallyperformthework,makingourcomponentseveneasiertoreasonabout.

Inthenextchapter,wewillturntoanoftenoverlookedyetusefultoolforcreatingreactiveapplications:Futures.

Page 248: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 249: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Chapter8.FuturesThefirststeptowardsreactiveapplicationsistobreakoutofsynchronousprocessing.Ingeneral,applicationswastealotoftimewaitingforthingstohappen.Maybewearewaitingonanexpensivecomputation—say,calculatingthe1000thFibonaccinumber.Perhapswearewaitingforsomeinformationtobewrittentothedatabase.Wecouldalsobewaitingforanetworkcalltoreturn,bringingusthelatestrecommendationsfromourfavoriteonlinestore.

Regardlessofwhatwe’rewaitingfor,weshouldneverblockclientsofourapplication.Thisiscrucialtoachievetheresponsivenesswedesirewhenbuildingreactivesystems.

Inanagewhereprocessingcoresareabundant—myMacBookProhaseightprocessorcores—blockingAPIsseverelyunderutilizestheresourceswehaveatourdisposal.

Asweapproachtheendofthisbook,itisappropriatetostepbackalittleandappreciatethatnotallclassesofproblemsthatdealwithconcurrent,asynchronouscomputationsrequirethemachineryofframeworkssuchasRxJavaorcore.async.

Inthischapter,wewilllookatanotherabstractionthathelpsusdevelopconcurrent,asynchronousapplications:futures.Wewilllearnabout:

TheproblemsandlimitationswithClojure’simplementationoffuturesAnalternativetoClojure’sfuturesthatprovidesasynchronous,composablesemanticsHowtooptimizeconcurrencyinthefaceofblockingIO

Page 250: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ClojurefuturesThefirststeptowardfixingthisissue—thatis,topreventapotentiallylong-runningtaskfromblockingourapplication—istocreatenewthreads,whichdotheworkandwaitforittocomplete.Thisway,wekeeptheapplication’smainthreadfreetoservemoreclients.

Workingdirectlywiththreads,however,istediousanderror-prone,soClojure’scorelibraryincludesfutures,whichareextremelysimpletouse:

(deff(clojure.core/future

(println"doingsomeexpensivework…")

(Thread/sleep5000)

(println"done")

10))

(println"You'llseemebeforethefuturefinishes")

;;doingsomeexpensivework…

;;You'llseemebeforethefuturefinishes

;;done

Intheprecedingsnippet,weinvoketheclojure.core/futuremacrowithabodysimulatinganexpensivecomputation.Inthisexample,itsimplysleepsfor5secondsbeforereturningthevalue10.Astheoutputdemonstrates,thisdoesnotblockthemainthread,whichisfreetoservemoreclients,pickworkitemsfromaqueue,orwhathaveyou.

Ofcourse,themostinterestingcomputations,suchastheexpensiveone,returnresultswecareabout.ThisiswherethefirstlimitationofClojurefuturesbecomesapparent.Ifweattempttoretrievetheresultofafuture—byderefingit—beforeithascompleted,thecallingthreadwillblockuntilthefuturereturnsavalue.Tryrunningthefollowingslightlymodifiedversionoftheprevioussnippet:

(deff(clojure.core/future

(println"doingsomeexpensivework…")

(Thread/sleep5000)

(println"done")

10))

(println"You'llseemebeforethefuturefinishes")

@f

(println"Icouldbedoingsomethingelse.InsteadIhadtowait")

;;doingsomeexpensivework…

;;You'llseemebeforethefuturefinishes

;;5SECONDSLATER

;;done

;;Icouldbedoingsomethingelse.Instead,Ihadtowait

Theonlydifferencenowisthatweimmediatelytrytoderefthefutureafterwecreateit.Sincethefutureisn’tdone,wesittherewaitingfor5secondsuntilitreturnsitsvalue.Onlythenisourprogramallowedtocontinue.

Ingeneral,thisposesaproblemwhenbuildingmodularsystems.Often,along-running

Page 251: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

operationliketheonedescribedearlierwouldbeinitiatedwithinaspecificmoduleorfunction,andhandedovertothenextlogicalstepforfurtherprocessing.

Clojurefuturesdon’tallowustoscheduleafunctiontobeexecutedwhenthefuturefinishesinordertoperformsuchfurtherprocessing.Thisisanimportantfeatureinbuildingreactivesystems.

Page 252: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 253: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

FetchingdatainparallelTounderstandbettertheissuesoutlinedintheprevioussection,let’sbuildamorecomplexexamplethatfetchesdataaboutoneofmyfavoritemovies,TheLordoftheRings.

Theideaisthatgiventhemovie,wewishtoretrieveitsactorsand,foreachactor,retrievethemoviestheyhavebeenapartof.Wealsowouldliketofindoutmoreinformationabouteachactor,suchastheirspouses.

Additionally,wewillmatcheachactor’smovieagainstthelistoftopfivemoviesinordertohighlightthemassuch.Finally,theresultwillbeprintedtothescreen.

Fromtheproblemstatement,weidentifythefollowingtwomaincharacteristicswewillneedtoaccountfor:

SomeofthesetasksneedtobeperformedinparallelTheyestablishdependenciesoneachother

Togetstarted,let’screateanewleiningenproject:

leinnewclj-futures-playground

Next,openthecorenamespacefileinsrc/clj_futures_playground/core.cljandaddthedatawewillbeworkingwith:

(nsclj-futures-playground.core

(:require[clojure.pprint:refer[pprint]]))

(defmovie

{:name"LordofTheRings:TheFellowshipofTheRing"

:cast["CateBlanchett"

"ElijahWood"

"LivTyler"

"OrlandoBloom"]})

(defactor-movies

[{:name"CateBlanchett"

:movies["LordofTheRings:TheFellowshipofTheRing"

"LordofTheRings:TheReturnofTheKing"

"TheCuriousCaseofBenjaminButton"]}

{:name"ElijahWood"

:movies["EternalSunshineoftheSpotlessMind"

"GreenStreetHooligans"

"TheHobbit:AnUnexpectedJourney"]}

{:name"LivTyler"

:movies["LordofTheRings:TheFellowshipofTheRing"

"LordofTheRings:TheReturnofTheKing"

"Armageddon"]}

{:name"OrlandoBloom"

:movies["LordofTheRings:TheFellowshipofTheRing"

"LordofTheRings:TheReturnofTheKing"

Page 254: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

"PiratesoftheCaribbean:TheCurseoftheBlackPearl"]}])

(defactor-spouse

[{:name"CateBlanchett":spouse"AndrewUpton"}

{:name"ElijahWood":spouse"Unknown"}

{:name"LivTyler":spouse"RoystonLangdon"}

{:name"OrlandoBloom":spouse"MirandaKerr"}])

(deftop-5-movies

["LordofTheRings:TheFellowshipofTheRing"

"TheMatrix"

"TheMatrixReloaded"

"PiratesoftheCaribbean:TheCurseoftheBlackPearl"

"Terminator"])

Thenamespacedeclarationissimpleandonlyrequiresthepprintfunction,whichwillhelpusprintourresultinaneasy-to-readformat.Withallthedatainplace,wecancreatethefunctionsthatwillsimulateremoteservicesresponsibleforfetchingtherelevantdata:

(defncast-by-movie[name]

(future(do(Thread/sleep5000)

(:castmovie))))

(defnmovies-by-actor[name]

(do(Thread/sleep2000)

(->>actor-movies

(filter#(=name(:name%)))

first)))

(defnspouse-of[name]

(do(Thread/sleep2000)

(->>actor-spouse

(filter#(=name(:name%)))

first)))

(defntop-5[]

(future(do(Thread/sleep5000)

top-5-movies)))

Eachservicefunctionsleepsthecurrentthreadbyagivenamountoftimetosimulateaslownetwork.Thefunctionscast-by-movieandTop5eachreturnsafuture,indicatingwewishtofetchthisdataonadifferentthread.Theremainingfunctionssimplyreturntheactualdata.Theywillalsobeexecutedinadifferentthread,however,aswewillseeshortly.

Thenextthingweneedisafunctiontoaggregateallfetcheddata,matchspousestoactors,andhighlightmoviesintheTop5list.We’llcallittheaggregate-actor-datafunction:

(defnaggregate-actor-data[spousesmoviestop-5]

(map(fn[{:keys[namespouse]}{:keys[movies]}]

{:namename

:spousespouse

:movies(map(fn[m]

(if(some#{m}top-5)

(strm"-(top5)")

m))

Page 255: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

movies)})

spouses

movies))

Theprecedingfunctionisfairlystraightforward.Itsimplyzipsspousesandmoviestogether,buildingamapofkeys:name,:spouse,and:movies.ItfurthertransformsmoviestoappendtheTop5suffixtotheonesinthetop-5list.

Thelastpieceofthepuzzleisthe-mainfunction,whichallowsustoruntheprogramfromthecommandline:

(defn-main[&args]

(time(let[cast(cast-by-movie"LordofTheRings:TheFellowshipof

TheRing")

movies(pmapmovies-by-actor@cast)

spouses(pmapspouse-of@cast)

top-5(top-5)]

(prn"Fetchingdata…")

(pprint(aggregate-actor-dataspousesmovies@top-5))

(shutdown-agents))))

Thereareanumberofthingsworthhighlightingintheprecedingsnippet.

First,wewrapthewholebodyinacalltotime,asimplebenchmarkingfunctionthatcomeswithClojure.Thisisjustsoweknowhowlongtheprogramtooktofetchalldata—thisinformationwillbecomerelevantlater.

Then,wesetupanumberofletbindings.Thefirst,cast,istheresultofcallingcast-by-movie,whichreturnsafuture.

Thenextbinding,movies,usesafunctionwehaven’tseenbefore:pmap.

Thepmapfunctionworkslikemap,exceptthefunctionismappedovertheitemsinthelistinparallel.Thepmapfunctionusesfuturesunderthecoversandthatisthereasonmovies-by-actordoesn’treturnafuture—itleavesthatforpmaptohandle.

TipThepmapfunctionisactuallymeantforCPU-boundoperations,butisusedheretokeepthecodesimple.InthefaceofblockingIO,pmapwouldn’tperformoptimally.WewilltalkmoreaboutblockingIOlaterinthischapter.

Wegetthelistofactorsbyderefingthecastbinding,which,aswesawintheprevioussection,blocksthecurrentthreadwaitingfortheasynchronousfetchtofinish.Onceallresultsareready,wesimplycalltheaggregate-actor-datafunction.

Lastly,wecalltheshutdown-agentsfunction,whichshutsdowntheThreadPoolbackingfuturesinClojure.Thisisnecessaryforourprogramtoterminateproperly,otherwiseitwouldsimplyhangintheterminal.

Toruntheprogram,typethefollowingintheterminal,undertheproject’srootdirectory:

leinrun-mclj-futures-playground.core

"Fetchingdata…"

Page 256: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

({:name"CateBlanchett",

:spouse"AndrewUpton",

:movies

("LordofTheRings:TheFellowshipofTheRing-(top5)"

"LordofTheRings:TheReturnofTheKing"

"TheCuriousCaseofBenjaminButton")}

{:name"ElijahWood",

:spouse"Unknown",

:movies

("EternalSunshineoftheSpotlessMind"

"GreenStreetHooligans"

"TheHobbit:AnUnexpectedJourney")}

{:name"LivTyler",

:spouse"RoystonLangdon",

:movies

("LordofTheRings:TheFellowshipofTheRing-(top5)"

"LordofTheRings:TheReturnofTheKing"

"Armageddon")}

{:name"OrlandoBloom",

:spouse"MirandaKerr",

:movies

("LordofTheRings:TheFellowshipofTheRing-(top5)"

"LordofTheRings:TheReturnofTheKing"

"PiratesoftheCaribbean:TheCurseoftheBlackPearl-(top5)")})

"Elapsedtime:10120.267msecs"

Youwillhavenoticedthattheprogramtakesawhiletoprintthefirstmessage.Additionally,becausefuturesblockwhentheyarederefed,theprogramdoesn’tstartfetchingthelistoftopfivemoviesuntilithascompletelyfinishedfetchingthecastofTheLordofTheRings.

Let’shavealookatwhythatisso:

(time(let[cast(cast-by-movie"LordofTheRings:TheFellowshipof

TheRing")

;;thefollowinglineblocks

movies(pmapmovies-by-actor@cast)

spouses(pmapspouse-of@cast)

top-5(top-5)]

Thehighlightedsectionintheprecedingsnippetshowswheretheprogramblockswaitingforcast-by-movietofinish.Asstatedpreviously,Clojurefuturesdon’tgiveusawaytorunsomepieceofcodewhenthefuturefinishes—likeacallback—forcingustoblocktoosoon.

Thispreventstop-5—acompletelyindependentparalleldatafetch—fromrunningbeforeweretrievethemovie’scast.

Ofcourse,thisisacontrivedexample,andwecouldsolvethisparticularannoyancebycallingtop-5beforeanythingelse.Theproblemisthatthesolutionisn’talwayscrystalclearandideallyweshouldnothavetoworryabouttheorderofexecution.

Aswewillseeinthenextsection,thereisabetterway.

Page 257: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 258: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Imminent–acomposablefutureslibraryforClojureInthepastfewmonths,IhavebeenworkingonanopensourcelibrarythataimstofixthepreviousissueswithClojurefutures.Theresultofthisworkiscalledimminent(seehttps://github.com/leonardoborges/imminent).

Thefundamentaldifferenceisthatimminentfuturesareasynchronousbydefaultandprovideanumberofcombinatorsthatallowustodeclarativelywriteourprogramswithouthavingtoworryaboutitsorderofexecution.

Thebestwaytodemonstratehowthelibraryworksistorewritethepreviousmoviesexampleinit.Wewilldothisintwosteps.

First,wewillexamineindividuallythebitsofimminent’sAPIthatwillbepartofourfinalsolution.Then,we’llputitalltogetherinaworkingapplication.Let’sstartbycreatinganewproject:

leinnewimminent-playground

Next,addadependencyonimminenttoyourproject.clj:

:dependencies[[org.clojure/clojure"1.6.0"]

[com.leonardoborges/imminent"0.1.0"]]

Then,createanewfile,src/imminent_playground/repl.clj,andaddimminent’scorenamespace:

(nsimminent-playground.repl

(:require[imminent.core:asIi]))

(defrepl-out*out*)

(defnprn-to-repl[&args]

(binding[*out*repl-out]

(applyprnargs)))

Theprecedingsnippetalsocreatesahelperfunctionthatisusefulwhenwe’redealingwithmultiplethreadsintheREPL—thiswillbeexplainedindetaillater,butfornowjusttakethisasbeingareliablewaytoprinttotheREPLacrossmultiplethreads.

FeelfreetotypethisintheREPLaswegoalong.Otherwise,youcanrequirethenamespacefilefromarunningREPLlikeso:

(require'imminent-playground.repl)

Allthefollowingexamplesshouldbeinthisfile.

Page 259: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

CreatingfuturesCreatingafutureinimminentisn’tmuchdifferentfromcreatingafutureinClojure.It’sassimpleasthefollowing:

(defage(i/future31))

;;#<Future@2ea0ca7d:#<Success@3e4dec75:31>>

Whatlooksverydifferent,however,isthereturnvalue.Akeydecisioninimminent’sAPIistorepresentthevalueofacomputationaseitheraSuccessoraFailuretype.Success,asintheprecedingexample,wrapstheresultofthecomputation.Failure,asyoumighthaveguessed,willwrapanyexceptionsthathappenedinthefuture:

(deffailed-computation(i/future(throw(Exception."Error"))))

;;#<Future@63cd0d58:#<Failure@2b273f98:#<Exceptionjava.lang.Exception:

Error>>>

(deffailed-computation-1(i/failed-future:invalid-data))

;;#<Future@a03588f:#<Failure@61ab196b::invalid-data>>

Asyoucansee,you’renotlimitedtoexceptionsonly.Wecanusethefailed-futurefunctiontocreateafuturethatcompletesimmediatelywiththegivenreason,which,inthesecondexample,issimplyakeyword.

Thenextquestionwemightaskis“Howdowegettheresultoutofafuture?”.AswithClojurefutures,wecanderefitasfollows:

@age;;#<Success@3e4dec75:31>

(deref@age);;31

(i/dderefage);;31

Theidiomofusingadouble-derefiscommon,soimminentprovidestheconvenienceshown,dderef,whichisequivalenttocallingdereftwice.

However,differentfromClojurefutures,thisisanon-blockingoperation,soifthefuturehasn’tcompletedyet,thefollowingiswhatyou’llget:

@(i/future(do(Thread/sleep500)

"hello"))

;;:imminent.future/unresolved

Theinitialstateofafutureisunresolved,sounlessyouareabsolutelycertainafuturehascompleted,derefingmightnotbethebestwaytoworkwiththeresultofacomputation.Thisiswherecombinatorsbecomeuseful.

Page 260: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

CombinatorsandeventhandlersLet’ssaywewouldliketodoublethevalueintheagefuture.Aswewouldwithlists,wecansimplymapafunctionoverthefuturetodojustthis:

(defdouble-age(i/mapage#(*%2)))

;;#<Future@659684cb:#<Success@7ce85f87:62>>

TipWhilei/futureschedulesitsbodyforexecutiononaseparatethread,it’sworthnotingthatfuturecombinatorssuchasmap,filter,andsoon,donotcreateanewthreadimmediately.Instead,theyscheduleafunctiontobeexecutedasynchronouslyinthethreadpooloncetheoriginalfuturecompletes.

Anotherwaytodosomethingwiththevalueofafutureistousetheon-successeventhandlerthatgetscalledwiththewrappedvalueofthefutureincaseitissuccessful:

(i/on-successage#(prn-to-repl(str"Ageis:"%)))

;;"Ageis:31"

Similarly,anon-failurehandlerexists,whichdoesthesameforFailuretypes.Whileonthesubjectoffailures,imminentfuturesunderstandthecontextinwhichtheyarebeingexecutedand,ifthecurrentfutureyieldsaFailure,itsimplyshort-circuitsthecomputation:

(->failed-computation

(i/map#(*%2)))

;;#<Future@7f74297a:#<Failure@2b273f98:#<Exceptionjava.lang.Exception:

Error>>>

Intheprecedingexample,wedon’tgetanewerror,butrathertheoriginalexceptioncontainedinfailed-computation.Thefunctionpassedtomapneverruns.

ThedecisiontowraptheresultofafutureinatypesuchasSuccessorFailuremightseemarbitrarybutisactuallyquitetheopposite.BothtypesimplementtheprotocolIReturn—andacoupleofotherones—whichcomeswithasetofusefulfunctions,oneofwhichismap:

(i/map(i/success"hello")

#(str%"world"))

;;#<Success@714eea92:"helloworld">

(i/map(i/failure"error")

#(str%"world"))

;;#<Failure@6d685b65:"error">

Wegetasimilarbehaviorhereaswedidpreviously:mappingafunctionoverafailuresimplyshort-circuitsthewholecomputation.Ifyoudo,however,wishtomapoverthefailure,youcanusemap’scounterpartmap-failure,whichbehavessimilarlytomapbutisitsinverse:

(i/map-failure(i/success"hello")

Page 261: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

#(str%"world"))

;;#<Success@779af3f4:"hello">

(i/map-failure(i/failure"Error")

#(str"Wefailed:"%))

;;#<Failure@52a02597:"Wefailed:Error">

Thisplayswellwiththelasteventhandlersimminentprovides—on-complete:

(i/on-completeage

(fn[result]

(i/mapresult#(prn-to-repl"success:"%))

(i/map-failureresult#(prn-to-repl"error:"%))))

;;"success:"31

Oncontrarytoon-successandon-failure,on-completecallstheprovidedfunctionwiththeresulttypewrapper,soitisaconvenientwaytohandlebothcasesinasinglefunction.

Comingbacktocombinators,sometimeswewillneedtomapafunctionoverafuture,whichitselfreturnsafuture:

(defnrange-future[n]

(i/const-future(rangen)))

(defage-range(i/mapagerange-future))

;;#<Future@3d24069e:#<Success@82e8e6e:#<Future@2888dbf4:#

<Success@312084f6:(012…)>>>>

Therange-futurefunctionreturnsasuccessfulfuturethatyieldsarangeofn.Theconst-futurefunctionisanalogoustofailed-future,exceptitimmediatelycompletesthefuturewithaSuccesstype.

However,weendupwithanestedfuture,whichisalmostneverwhatyouwant.That’sOK.Thisispreciselythescenarioinwhichyouwoulduseanothercombinator,flatmap.

Youcanthinkofitasmapcatforfutures—itflattensthecomputationforus:

(defage-range(i/flatmapagerange-future))

;;#<Future@601c1dfc:#<Success@55f4bcaf:(012…)>>

Anotherveryusefulcombinatorisusedtobringtogethermultiplecomputationstobeusedinasinglefunction—sequence:

(defname(i/future(do(Thread/sleep500)

"Leo")))

(defgenres(i/future(do(Thread/sleep500)

["HeavyMetal""BlackMetal""DeathMetal""Rock

'nRoll"])))

(->(i/sequence[nameagegenres])

(i/on-success

(fn[[nameagegenres]]

Page 262: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

(prn-to-repl(format"%sis%syearsoldandenjoys%s"

name

age

(clojure.string/join","genres))))))

;;"Leois31yearsoldandenjoysHeavyMetal,BlackMetal,DeathMetal,Rock

'nRoll"

Essentially,sequencecreatesanewfuture,whichwillcompleteonlywhenallotherfuturesinthevectorhavecompletedoranyoneofthemhavefailed.

Thisisanicesegueintothelastcombinatorwewilllookat—map-future—whichwewoulduseinplaceofpmap,usedinthemoviesexample:

(defncalculate-double[n]

(i/const-future(*n2)))

(->(i/map-futurecalculate-double[1234])

i/await

i/dderef)

;;[2468]

Intheprecedingexample,calculate-doubleisafunctionthatreturnsafuturewiththevaluendoubled.Themap-futurefunctionthenmapscalculate-doubleoverthelist,effectivelyperformingthecalculationsinparallel.Finally,map-futuresequencesallfuturestogether,returningasinglefuture,whichyieldstheresultofallcomputations.

Becauseweareperforminganumberofparallelcomputationsanddon’treallyknowwhentheywillfinish,wecallawaitonthefuture,whichisawaytoblockthecurrentthreaduntilitsresultisready.Ingeneral,youwouldusethecombinatorsandeventhandlersinstead,butforthisexample,usingawaitisacceptable.

Imminent’sAPIprovidesmanymorecombinators,whichhelpuswriteasynchronousprogramsinadeclarativeway.ThissectiongaveusatasteofwhatispossiblewiththeAPIandisenoughtoallowustowritethemoviesexampleusingimminentfutures.

Page 263: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 264: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ThemoviesexamplerevisitedStillwithinourimminent-playgroundproject,openthesrc/imminent_playground/core.cljfileandaddtheappropriatedefinitions:

(nsimminent-playground.core

(:require[clojure.pprint:refer[pprint]]

[imminent.core:asi]))

(defmovie…)

(defactor-movies…)

(defactor-spouse…)

(deftop-5-movies…)

Wewillbeusingthesamedataasinthepreviousprogram,representedintheprecedingsnippetbytheuseofellipses.Simplycopytherelevantdeclarationsover.

Theservicefunctionswillneedsmalltweaksinthisnewversion:

(defncast-by-movie[name]

(i/future(do(Thread/sleep5000)

(:castmovie))))

(defnmovies-by-actor[name]

(i/future(do(Thread/sleep2000)

(->>actor-movies

(filter#(=name(:name%)))

first))))

(defnspouse-of[name]

(i/future(do(Thread/sleep2000)

(->>actor-spouse

(filter#(=name(:name%)))

first))))

(defntop-5[]

(i/future(do(Thread/sleep5000)

top-5-movies)))

(defnaggregate-actor-data[spousesmoviestop-5]

...)

Themaindifferenceisthatallofthemnowreturnanimminentfuture.Theaggregate-actor-datafunctionisalsothesameasbefore.

Thisbringsustothe-mainfunction,whichwasrewrittentouseimminentcombinators:

(defn-main[&args]

(time(let[cast(cast-by-movie"LordofTheRings:TheFellowshipof

TheRing")

movies(i/flatmapcast#(i/map-futuremovies-by-actor%))

spouses(i/flatmapcast#(i/map-futurespouse-of%))

Page 265: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

result(i/sequence[spousesmovies(top-5)])]

(prn"Fetchingdata…")

(pprint(applyaggregate-actor-data

(i/dderef(i/awaitresult)))))))

Thefunctionstartsmuchlikeitspreviousversion,andeventhefirstbinding,cast,looksfamiliar.Nextwehavemovies,whichisobtainedbyfetchinganactor’smoviesinparallel.Thisinitselfreturnsafuture,soweflatmapitoverthecastfuturetoobtainourfinalresult:

movies(i/flatmapcast#(i/map-futuremovies-by-actor%))

spousesworksinexactlythesamewayasmovies,whichbringsustoresult.Thisiswherewewouldliketobringallasynchronouscomputationstogether.Therefore,weusethesequencecombinator:

result(i/sequence[spousesmovies(top-5)])

Finally,wedecidetoblockontheresultfuture—byusingawait—sowecanprintthefinalresult:

(pprint(applyaggregate-actor-data

(i/dderef(i/awaitresult)))

Weruntheprograminthesamewayasbefore,sosimplytypethefollowinginthecommandline,undertheproject’srootdirectory:

leinrun-mimminent-playground.core

"Fetchingdata…"

({:name"CateBlanchett",

:spouse"AndrewUpton",

:movies

("LordofTheRings:TheFellowshipofTheRing-(top5)"

"LordofTheRings:TheReturnofTheKing"

"TheCuriousCaseofBenjaminButton")}

...

"Elapsedtime:7088.398msecs"

Theresultoutputwastrimmedasitisexactlythesameasbefore,buttwothingsaredifferentanddeserveattention:

Thefirstoutput,Fetchingdata…,isprintedtothescreenalotfasterthanintheexampleusingClojurefuturesTheoveralltimeittooktofetchallthatisshorter,clockinginatjustover7seconds

Thishighlightstheasynchronousnatureofimminentfuturesandcombinators.Theonlytimewehadtowaitiswhenweexplicitlycalledawaitattheendoftheprogram.

Morespecifically,theperformanceboostcomesfromthefollowingsectioninthecode:

(let[...

result(i/sequence[spousesmovies(top-5)])]

...)

Becausenoneofthepreviousbindingsblockthecurrentthread,weneverhavetowaitto

Page 266: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

kickofftop-5inparallel,shavingoffroughly3secondsfromtheoverallexecutiontime.Wedidn’thavetoexplicitlythinkabouttheorderofexecution—thecombinatorssimplydidtherightthing.

Finally,onelastdifferenceisthatwedidn’thavetoexplicitlycallshutdown-agentsasbefore.Thereasonforthisisthatimminentusesadifferenttypeofthreadpool:aForkJoinPool(seehttp://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ForkJoinPool.html).

Thispoolhasanumberofadvantages—eachwithitsowntrade-off—overtheotherthreadpools,andonecharacteristicisthatwedon’tneedtoexplicitlyshutitdown—allthreadsitcreatesdaemonthreads.

WhentheJVMshutsdown,ithangswaitingforallnon-daemonthreadstofinish.Onlythendoesitexit.That’swhyusingClojurefutureswouldcausetheJVMtohang,ifwehadnotcalledshutdown-agents.

AllthreadscreatedbytheForkJoinPoolaresetasdaemonthreadsbydefault:whentheJVMattemptstoshutdown,andiftheonlythreadsrunningaredaemonones,theyareabandonedandtheJVMexitsgracefully.

Combinatorssuchasmapandflatmap,aswellasthefunctionssequenceandmap-future,aren’texclusivetofutures.Theyhavemanymorefundamentalprinciplesbywhichtheyabide,makingthemusefulinarangeofdomains.Understandingtheseprinciplesisn’tnecessaryforfollowingthecontentsofthisbook.Shouldyouwanttoknowmoreabouttheseprinciples,pleaserefertotheAppendix,TheAlgebraofLibraryDesign.

Page 267: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 268: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

FuturesandblockingIOThechoiceofusingForkJoinPoolforimminentisdeliberate.TheForkJoinPool—addedonJava7—isextremelysmart.Whencreated,yougiveitadesiredlevelofparallelism,whichdefaultstothenumberofavailableprocessors.

ForkJoinPoolthenattemptstohonorthedesiredparallelismbydynamicallyshrinkingandexpandingthepoolasrequired.Whenataskissubmittedtothispool,itdoesn’tnecessarilycreateanewthreadifitdoesn’thaveto.Thisallowsthepooltoserveanextremelylargenumberoftaskswithamuchsmallernumberofactualthreads.

However,itcannotguaranteesuchoptimizationsinthefaceofblockingIO,asitcan’tknowwhetherthethreadisblockingwaitingforanexternalresource.Nevertheless,ForkJoinPoolprovidesamechanismbywhichthreadscannotifythepoolwhentheymightblock.

ImminenttakesadvantageofthismechanismbyimplementingtheManagedBlocker(seehttp://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ForkJoinPool.ManagedBlocker.htmlinterface—andprovidesanotherwaytocreatefutures,asdemonstratedhere:

(->(immi/blocking-future

(Thread/sleep100)

10)

(immi/await))

;;#<Future@4c8ac77a:#<Success@45525276:10>>

(->(immi/blocking-future-call

(fn[]

(Thread/sleep100)

10))

(immi/await))

;;#<Future@37162438:#<Success@5a13697f:10>>

Theblocking-futureandblocking-future-callhavethesamesemanticsastheircounterparts,futureandfuture-call,butshouldbeusedwhenthetasktobeperformedisofablockingnature(thatis,notCPU-bound).ThisallowstheForkJoinPooltobetterutilizeitsresources,makingitapowerfulandflexiblesolution.

Page 269: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 270: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

SummaryInthischapter,welearnedthatClojurefuturesleavealottobedesired.Morespecifically,Clojurefuturesdon’tprovideawaytoexpressdependenciesbetweenresults.Itdoesn’tmean,however,thatweshoulddismissfuturesaltogether.

Theyarestillausefulabstractionandwiththerightsemanticsforasynchronouscomputationsandarichsetofcombinators—suchastheonesprovidedbyimminent—theycanbeabigallyinbuildingreactiveapplicationsthatareperformantandresponsive.Sometimes,thisisallweneed.

Forthetimeswhereweneedtomodeldatathatvariesovertime,weturntoricherframeworksinspiredbyFunctionalReactiveProgramming(FRP)andCompositionalEventSystems(CES)—suchasRxJava—orCommunicatingSequentialProcesses(CSP)—suchascore.async.Astheyhavealotmoretooffer,muchofthisbookhasbeendedicatedtothoseapproaches.

Inthenextchapter,wewillgobacktodiscussingFRP/CESbywayofacasestudy.

Page 271: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 272: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Chapter9.AReactiveAPItoAmazonWebServicesThroughoutthisbook,wehavelearnedanumberoftoolsandtechniquestoaidusinbuildingreactiveapplications—futureswithimminent,ObservableswithRxClojure/RxJava,channelswithcore.async—andeveninbuildingreactiveuserinterfacesusingOmandReact.

Intheprocess,wealsobecameacquaintedwiththeconceptofFunctionalReactiveProgrammingandCompositionalEventSystems,aswellaswhatmakesthemdifferent.

Inthislastchapter,wewillbringafewofthesedifferenttoolsandconceptstogetherbydevelopinganapplicationbasedonareal-worldusecasefromaclientIworkedwithinSydney,Australia.Wewill:

DescribetheproblemofinfrastructureautomationweweretryingtosolveHaveabrieflookatsomeofAmazon’sAWSservicesBuildanAWSdashboardusingtheconceptswehavelearnedsofar

Page 273: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

TheproblemThisclient—whichwewillcallBubbleCorpfromnowon—hadabigproblemthatisalltoocommonandwellknowntobigenterprises:onemassivemonolithicapplication.

Besidesmakingthemmoveslow,asindividualcomponentscan’tbeevolvedindependently,thisapplicationmakesdeploymentincrediblyhardduetoitsenvironmentconstraints:allinfrastructureneedstobeavailableinorderfortheapplicationtoworkatall.

Asaresult,developingnewfeaturesandbugfixesinvolveshavingonlyahandfulofdevelopmentenvironmentssharedacrossdozensofdeveloperseach.Thisrequiresawastefulamountofcoordinationbetweenteamsjustsothattheywon’tsteponeachother’stoes,contributingtoslowthewholelife-cyclefurther.

Thelong-termsolutiontothisproblemistobreakdownthisbigapplicationintosmallercomponents,whichcanbedeployedandworkedonindependently,butasgoodasthissounds,it’salaboriousandlengthyprocess.

Asafirststep,BubbleCorpdecidedthebestthingtheycouldimproveintheshorttermistogivedeveloperstheabilitytoworkintheapplicationindependentlyfromeachother,whichimpliesbeingabletocreateanewenvironmentaswell.

Giventheinfrastructureconstraints,runningtheapplicationonasingledevelopermachineisprohibitive.

Instead,theyturnedtoinfrastructureautomation:theywantedatoolthat,withthepressofabutton,wouldspinupacompletelynewenvironment.

Thisnewenvironmentwouldbealreadypreconfiguredwiththeproperapplicationservers,databaseinstances,DNSentries,andeverythingelseneededtoruntheapplication.

Thisway,developerswouldonlyneedtodeploytheircodeandtesttheirchanges,withouthavingtoworryabouttheapplicationsetup.

Page 274: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 275: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

InfrastructureautomationAmazonWebServices(AWS)isthemostmatureandcomprehensivecloudcomputingplatformavailabletoday,andassuchitwasanaturalchoiceforBubbleCorptohostitsinfrastructurein.

Ifyouhaven’tusedAWSbefore,don’tworry,we’llfocusonlyonafewofitsservices:

ElasticComputeCloud(EC2):Aservicethatprovidesuserswiththeabilitytorentvirtualcomputersinwhichtoruntheirapplications.RelationalDatabaseService(RDS):ThiscanbethoughtofasaspecializedversionofEC2thatprovidesmanageddatabaseservices.CloudFormation:WithCloudFormation,usershavetheabilitytospecifyinfrastructuretemplates,calledstacks,ofseveraldifferentAWSresources—suchasEC2,AWS,andmanyothers—aswellashowtheyinteractwitheachother.Oncewritten,theinfrastructuretemplatecanbesenttoAWStobeexecuted.

ForBubbleCorp,theideawastowritetheseinfrastructuretemplates,whichoncesubmittedwouldresultintoacompletelynew,isolatedenvironmentcontainingalldataandcomponentsrequiredtorunitsapp.Atanygiventime,therewouldbedozensoftheseenvironmentsrunningwithdevelopersworkingonthem.

Asdecentaplanasthissounds,bigcorporationsusuallyhaveanaddedburden:costcenters.Unfortunately,BubbleCorpcan’tsimplyallowdeveloperstologintotheAWSConsole—wherewecanmanageAWSresources—andspinupenvironmentsatwill.Theyneededawayto,amongotherthings,addcostcentermetadatatotheenvironmenttohandletheirinternalbillingprocess.

Thisbringsustotheapplicationwewillbefocusingonfortheremainderofthischapter.

Page 276: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 277: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

AWSresourcesdashboardMyteamandIweretaskedwithbuildingaweb-baseddashboardforAWS.ThisdashboardwouldallowdeveloperstologinusingtheirBubbleCorp’scredentialsand,onceauthenticated,createnewCloudFormationenvironmentsaswellasvisualizethestatusofeachindividualresourcewithinaCloudFormationstack.

Theapplicationitselfisfairlyinvolved,sowewillfocusonasubsetofit:interfacingwiththenecessaryAWSservicesinordertogatherinformationaboutthestatusofeachindividualresourceinagivenCloudFormationstack.

Oncefinished,thisiswhatoursimplifieddashboardwilllooklike:

ItwilldisplaytheID,type,andcurrentstatusofeachresource.Thismightnotseemlikemuchfornow,butgiventhatallthisinformationiscomingfromdifferent,independentwebservices,itisfartooeasytoendupwithunnecessarilycomplexcode.

WewillbeusingClojureScriptforthisandthereforetheJavaScriptversionoftheAWSSDK,whosedocumentationcanbefoundathttp://aws.amazon.com/sdk-for-node-js/.

Beforewegetstarted,let’shavealookateachoftheAWSServicesAPIswewillbeinteractingwith.

TipInreality,wewillnotbeinteractingwiththerealAWSservicesbutratherastubserverprovidedfordownloadfromthebook’sGitHubrepository.

Thereasonforthisistomakefollowingthischaptereasier,asyouwon’tneedtocreateanaccountaswellasgenerateanAPIaccesskeytointeractwithAWS.

Additionally,creatingresourcesincurscost,andthelastthingIwantisforyoutobechargedhundredsofdollarsattheendofthemonthbecausesomeoneaccidentallyleftresourcesrunningforlongerthantheyshould—trustmeithashappenedbefore.

Page 278: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 279: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

CloudFormationThefirstservicewewilllookatisCloudFormation.ThismakessenseastheAPIsfoundinherewillgiveusastartingpointforfindinginformationabouttheresourcesinagivenstack.

Page 280: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ThedescribeStacksendpointThisendpointisresponsibleforlistingallstacksassociatedwithaparticularAWSaccount.Foragivenstack,itsresponselookslikethefollowing:

{"Stacks"

[{"StackId"

"arn:aws:cloudformation:ap-southeast-2:337944750480:stack/DevStack-

62031/1",

"StackStatus""CREATE_IN_PROGRESS",

"StackName""DevStack-62031",

"Parameters"[{"ParameterKey""DevDB","ParameterValue"nil}]}]}

Unfortunately,itdoesn’tsayanythingaboutwhichresourcesbelongtothisstack.Itdoes,however,giveusthestackname,whichwecanusetolookupresourcesinthenextservice.

Page 281: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ThedescribeStackResourcesendpointThisendpointreceivesmanyarguments,buttheonewe’reinterestedinisthestackname,which,onceprovided,returnsthefollowing:

{"StackResources"

[{"PhysicalResourceId""EC2123",

"ResourceType""AWS::EC2::Instance"},

{"PhysicalResourceId""EC2456",

"ResourceType""AWS::EC2::Instance"}

{"PhysicalResourceId""EC2789",

"ResourceType""AWS::EC2::Instance"}

{"PhysicalResourceId""RDS123",

"ResourceType""AWS::RDS::DBInstance"}

{"PhysicalResourceId""RDS456",

"ResourceType""AWS::RDS::DBInstance"}]}

Weseemtobegettingsomewherenow.Thisstackhasseveralresources:threeEC2instancesandtwoRDSinstances—nottoobadforonlytwoAPIcalls.

However,aswementionedpreviously,ourdashboardneedstoshowthestatusofeachoftheresources.WiththelistofresourceIDsathand,weneedtolooktootherservicesthatcouldgiveusdetailedinformationabouteachresource.

Page 282: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 283: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

EC2ThenextservicewewilllookatisspecifictoEC2.Aswewillsee,theresponsesofthedifferentservicesaren’tasconsistentaswewouldlikethemtobe.

Page 284: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ThedescribeInstancesendpointThisendpointsoundspromising.Basedonthedocumentation,itseemswecangiveitalistofinstanceIDsanditwillgiveusbackthefollowingresponse:

{"Reservations"

[{"Instances"

[{"InstanceId""EC2123",

"Tags"

[{"Key""StackType","Value""Dev"}

{"Key""junkTag","Value""shouldnotbeincluded"}

{"Key""aws:cloudformation:logical-id","Value""theDude"}],

"State"{"Name""running"}}

{"InstanceId""EC2456",

"Tags"

[{"Key""StackType","Value""Dev"}

{"Key""junkTag","Value""shouldnotbeincluded"}

{"Key""aws:cloudformation:logical-id","Value""theDude"}],

"State"{"Name""running"}}

{"InstanceId""EC2789",

"Tags"

[{"Key""StackType","Value""Dev"}

{"Key""junkTag","Value""shouldnotbeincluded"}

{"Key""aws:cloudformation:logical-id","Value""theDude"}],

"State"{"Name""running"}}]}]}

Buriedinthisresponse,wecanseetheStatekey,whichgivesusthestatusofthatparticularEC2instance.ThisisallweneedasfarasEC2goes.ThisleavesuswithRDStohandle.

Page 285: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 286: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

RDSOnemightbetemptedtothinkthatgettingthestatusesofRDSinstanceswouldbejustaseasyaswithEC2.Let’sseeifthatisthecase.

Page 287: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ThedescribeDBInstancesendpointThisendpointisequivalentinpurposetotheanalogousEC2endpointwejustlookedat.Itsinput,however,isslightlydifferent:itacceptsasingleinstanceIDasinputand,asofthetimeofthiswriting,doesn’tsupportfilters.

ThismeansthatifourstackhasmultipleRDSinstances—say,inaprimary/replicasetup—weneedtomakemultipleAPIcallstogatherinformationabouteachoneofthem.Notabigdeal,ofcourse,butalimitationtobeawareof.

OncegivenaspecificdatabaseinstanceID,thisservicerespondswiththefollowingcode:

{"DBInstances"

[{"DBInstanceIdentifier""RDS123","DBInstanceStatus""available"}]}

Thefactthatasingleinstancecomesinsideavectorhintsatthefactthatfilteringwillbesupportedinthefuture.Itjusthasn’thappenedyet.

Page 288: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 289: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

DesigningthesolutionWenowhavealltheinformationweneedtostartdesigningourapplication.WeneedtocoordinatefourdifferentAPIcallsperCloudFormationstack:

describeStacks:TolistallavailablestacksdescribeStackResources:ToretrievedetailsofallresourcescontainedinastackdescribeInstances:ToretrievedetailsofallEC2instancesinastackdescribeDBInstances:ToretrievedetailsofallDB2instancesinastack

Next,Iwouldlikeyoutostepbackforamomentandthinkabouthowyouwoulddesigncodelikethis.Goahead,I’llwait.

Nowthatyou’reback,let’shavealookatonepossibleapproach.

Ifwerecallthescreenshotofwhatthedashboardwouldlooklike,werealizethat,forthepurposesofourapplication,thedifferencebetweenEC2andRDSresourcescanbecompletelyignoredsolongaseachonehastheattributesID,type,andstatus.

Thismeanswhateveroursolutionmaybe,ithastosomehowprovideauniformwayofabstractingthedifferentresourcetypes.

Additionally,apartfromdescribeStacksanddescribeStackResources,whichneedtobecalledsequentially,describeInstancesanddescribeDBInstancescanbeexecutedconcurrently,afterwhichwewillneedawaytomergetheresults.

Sinceanimageisworthathousandwords,thefollowingimageiswhatwewouldliketheworkflowtolooklike:

Page 290: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Theprecedingimagehighlightsanumberofkeyaspectsofoursolution:

WestartbyretrievingstacksbycallingdescribeStacksNext,foreachstack,wecalldescribeStackResourcestoretrievealistofresourcesforeachoneThen,wesplitthelistbytype,endingwithalistofEC2andonewithRDSresourcesWeproceedbyconcurrentlycallingdescribeInstancesanddescribeDBInstances,yieldingtwolistsofresults,oneperresourcetypeAstheresponseformatsaredifferent,wetransformeachresourceintoauniformrepresentationLastly,wemergeallresultsintoasinglelist,readyforrendering

Thisisquiteabittotakein,butasyouwillsoonrealize,oursolutionisn’ttoofaroffthishigh-leveldescription.

WecanquiteeasilythinkofthisproblemashavinginformationaboutseveraldifferenttypesofinstancesflowingthroughthisgraphofAPIcalls—beingtransformedasneededinbetween—untilwearriveattheinformationwe’reafter,intheformatwewouldliketoworkwith.

Asitturnsout,agreatwaytomodelthisproblemistouseoneoftheReactive

Page 291: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

abstractionswelearnedaboutearlierinthisbook:Observables.

Page 292: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

RunningtheAWSstubserverBeforewejumpintowritingourdashboard,weshouldmakesureourAWSstubserverisproperlysetup.ThestubserverisaClojurewebapplicationthatsimulateshowtherealAWSAPIbehavesandisthebackendourdashboardwilltalkto.

Let’sstartbygoingintoourterminal,cloningthebookrepositoryusingGitandthenstartingthestubserver:

$gitclonehttps://github.com/leonardoborges/ClojureReactiveProgramming

$cdClojureReactiveProgramming/code/chapter09/aws-api-stub

$leinringserver-headless3001

2014-11-2317:33:37.766:INFO:oejs.Server:jetty-7.6.8.v20121106

2014-11-2317:33:37.812:INFO:oejs.AbstractConnector:Started

[email protected]:3001

Startedserveronport3001

Thiswillhavestartedtheserveronport3001.Tovalidateitisworkingasexpected,pointyourbrowsertohttp://localhost:3001/cloudFormation/describeStacks.YoushouldseethefollowingJSONresponse:

{

"Stacks":[

{

"Parameters":[

{

"ParameterKey":"DevDB",

"ParameterValue":null

}

],

"StackStatus":"CREATE_IN_PROGRESS",

"StackId":"arn:aws:cloudformation:ap-southeast-

2:337944750480:stack/DevStack-62031/1",

"StackName":"DevStack-62031"

}

]

}

Page 293: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

SettingupthedashboardprojectAswepreviouslymentioned,wewillbedevelopingthedashboardusingClojureScriptwiththeUIrenderedusingOm.Additionally,aswehavechosenObservablesasourmainReactiveabstraction,wewillneedRxJS,oneofthemanyimplementationsofMicrosoft’sReactiveExtensions.Wewillbepullingthesedependenciesintoourprojectshortly.

Let’screateanewClojureScriptprojectcalledaws-dashusingtheom-startleiningentemplate:

$leinnewom-startaws-dash

Thisgivesusastartingpoint,butweshouldmakesureourversionsallmatch.Openuptheproject.cljfilefoundintherootdirectoryofthenewprojectandensurethedependenciessectionlookslikethefollowing:

...

:dependencies[[org.clojure/clojure"1.6.0"]

[org.clojure/clojurescript"0.0-2371"]

[org.clojure/core.async"0.1.346.0-17112a-alpha"]

[om"0.5.0"]

[com.facebook/react"0.9.0"]

[cljs-http"0.1.20"]

[com.cognitect/transit-cljs"0.8.192"]]

:plugins[[lein-cljsbuild"1.0.3"]]

...

Thisisthefirsttimeweseethelasttwodependencies.cljs-httpisasimpleHTTPlibrarywewillusetomakeAJAXrequeststoourAWSstubserver.transit-cljsallowsusto,amongotherthings,parseJSONresponsesintoClojureScriptdatastructures.

TipTransititselfisaformatandasetoflibrariesthroughwhichapplicationsdevelopedindifferenttechnologiescanspeaktoeachother.Inthiscase,weareusingtheClojurescriptlibrarytoparseJSON,butifyou’reinterestedinlearningmore,IrecommendreadingtheofficialblogpostannouncementbyRichHickeyathttp://blog.cognitect.com/blog/2014/7/22/transit.

Next,weneedRxJS,which,beingaJavaScriptdependency,isn’tavailablevialeiningen.That’sOK.Wewillsimplydownloaditintotheapplicationoutputdirectory,aws-dash/dev-resources/public/js/:

$cdaws-dash/dev-resources/public/js/

$wgethttps://raw.githubusercontent.com/Reactive-

Extensions/RxJS/master/dist/rx.all.js

--2014-11-2318:00:21--https://raw.githubusercontent.com/Reactive-

Extensions/RxJS/master/dist/rx.all.js

Resolvingraw.githubusercontent.com…103.245.222.133

Connectingtoraw.githubusercontent.com|103.245.222.133|:443…connected.

HTTPrequestsent,awaitingresponse…200OK

Length:355622(347K)[text/plain]

Savingto:'rx.all.js'

Page 294: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

100%[========================>]355,622966KB/sin0.4s

2014-11-2318:00:24(966KB/s)-'rx.all.js'saved[355622/355622]

Movingon,weneedtomakeourapplicationawareofournewdependencyonRxJS.Opentheaws-dash/dev-resources/public/index.htmlfileandaddascripttagtopullinRxJS:

<html>

<body>

<divid="app"></div>

<scriptsrc="http://fb.me/react-0.9.0.js"></script>

<scriptsrc="js/rx.all.js"></script>

<scriptsrc="js/aws_dash.js"></script>

</body>

</html>

Withallthedependenciesinplace,let’sstarttheauto-compilationforourClojureScriptsourcefilesasfollows:

$cdaws-dash/

$leincljsbuildauto

CompilingClojureScript.

Compiling"dev-resources/public/js/aws_dash.js"from("src/cljs""dev-

resources/tools/repl")...

Successfullycompiled"dev-resources/public/js/aws_dash.js"in0.981

seconds.

Page 295: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

CreatingAWSObservablesWe’renowreadytostartimplementingoursolution.IfyourecallfromtheReactiveExtensionschapter,RxJava/RxJS/RxClojureshipwithseveralusefulObservables.However,whenthebuilt-inObservablesaren’tenough,itgivesusthetoolstobuildourown.

SinceitishighlyunlikelyRxJSalreadyprovidesObservablesforAmazon’sAWSAPI,wewillstartbyimplementingourownprimitiveObservables.

Tokeepthingsneat,wewilldothisinanewfile,underaws-dash/src/cljs/aws_dash/observables.cljs:

(nsaws-dash.observables

(:require-macros[cljs.core.async.macros:refer[go]])

(:require[cljs-http.client:ashttp]

[cljs.core.async:refer[<!]]

[cognitect.transit:ast]))

(defr(t/reader:json))

(defaws-endpoint"http://localhost:3001")

(defnaws-uri[path]

(straws-endpointpath))

Thenamespacedeclarationrequiresthenecessarydependencieswewillneedinthisfile.NotehowthereisnoexplicitdependencyonRxJS.SinceitisaJavaScriptdependencythatwemanuallypulledin,itisgloballyavailableforustouseviaJavaScriptinteroperability.

ThenextlinesetsupatransitreaderforJSON,whichwewillusewhenparsingthestubserverresponses.

Then,wedefinetheendpointwewillbetalkingtoaswellasahelperfunctiontobuildthecorrectURIs.Makesurethevariableaws-endpointmatchesthehostandportofthestubserverstartedintheprevioussection.

AllObservablesweareabouttocreatefollowacommonstructure:theymakearequesttothestubserver,extractsomeinformationfromtheresponse,optionallytransformingit,andthenemiteachiteminthetransformedsequenceintothenewObservablesequence.

Toavoidrepetition,thispatterniscapturedinthefollowingfunction:

(defnobservable-seq[uritransform]

(.createjs/Rx.Observable

(fn[observer]

(go(let[response(<!(http/geturi{:with-credentials?

false}))

data(t/readr(:bodyresponse))

transformed(transformdata)]

(doseq[xtransformed]

(.onNextobserverx))

Page 296: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

(.onCompletedobserver)))

(fn[](.logjs/console"Disposed")))))

Let’sbreakthisfunctiondown:

observable-seqreceivestwoarguments:thebackendURItowhichwewillissueaGETrequest,andatransformfunctionwhichisgiventherawparsedJSONresponseandreturnsasequenceoftransformeditems.Then,itcallsthecreatefunctionoftheRxJSobjectRx.Observable.NotehowwemakeuseofJavaScriptinteroperabilityhere:weaccessthecreatefunctionbyprependingitwithadotmuchlikeinJavainteroperability.SinceRx.Observableisaglobalobject,weaccessitbyprependingtheglobalJavaScriptnamespaceClojureScriptmakesavailabletoourprogram,js/Rx.Observable.TheObservable’screatefunctionreceivestwoarguments.OneisafunctionthatgetscalledwithanObservertowhichwecanpushitemstobepublishedintheObservablesequence.ThesecondfunctionisafunctionthatiscalledwheneverthisObservableisdisposedof.Thisisthefunctionwherewecouldperformanycleanupneeded.Inourcase,thisfunctionsimplylogsthefactthatitiscalledtotheconsole.

Thefirstfunctionistheonethatinterestsusthough:

(fn[observer]

(go(let[response(<!(http/geturi

{:with-credentials?

false}))

data(t/readr(:bodyresponse))

transformed(transformdata)]

(doseq[xtransformed]

(.onNextobserverx))

(.onCompletedobserver))))

Assoonasitgetscalled,itperformsarequesttotheprovidedURIusingcljs-http’sgetfunction,whichreturnsacore.asyncchannel.That’swhythewholelogicisinsideagoblock.

Next,weusethetransitJSONreaderweconfiguredpreviouslytoparsethebodyoftheresponse,feedingtheresultintothetransformfunction.Rememberthisfunction,asperourdesign,returnsasequenceofthings.Therefore,allthatislefttodoispusheachitemintotheobserverinturn.

Oncewe’redone,weindicatethatthisObservablesequencewon’temitanynewitembyinvokingthe.onCompletedfunctionoftheobserverobject.

Now,wecanproceedcreatingourObservablesusingthishelperfunction,startingwiththeoneresponsibleforretrievingCloudFormationstacks:

(defndescribe-stacks[]

(observable-seq(aws-uri"/cloudFormation/describeStacks")

(fn[data]

(map(fn[stack]{:stack-id(stack"StackId")

:stack-name(stack"StackName")})

(data"Stacks")))))

Page 297: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Thiscreatesanobservablethatwillemitoneitemperstack,inthefollowingformat:

({:stack-id"arn:aws:cloudformation:ap-southeast-

2:337944750480:stack/DevStack-62031/1",:stack-name"DevStack-62031"})

Nowthatwehavestacks,weneedanObservabletodescribeitsresources:

(defndescribe-stack-resources[stack-name]

(observable-seq(aws-uri"/cloudFormation/describeStackResources")

(fn[data]

(map(fn[resource]

{:resource-id(resource"PhysicalResourceId")

:resource-type(resource"ResourceType")})

(data"StackResources")))))

Ithasasimilarpurposeandemitsresourceitemsinthefollowingformat:

({:resource-id"EC2123",:resource-type"AWS::EC2::Instance"}

{:resource-id"EC2456",:resource-type"AWS::EC2::Instance"}

{:resource-id"EC2789",:resource-type"AWS::EC2::Instance"}

{:resource-id"RDS123",:resource-type"AWS::RDS::DBInstance"}

{:resource-id"RDS456",:resource-type"AWS::RDS::DBInstance"})

Sincewe’refollowingourstrategyalmosttotheletter,weneedtwomoreobservables,oneforeachinstancetype:

(defndescribe-instances[instance-ids]

(observable-seq(aws-uri"/ec2/describeInstances")

(fn[data]

(let[instances(mapcat(fn[reservation]

(reservation"Instances"))

(data"Reservations"))]

(map(fn[instance]

{:instance-id(instance"InstanceId")

:type"EC2"

:status(get-ininstance["State"

"Name"])})

instances)))))

(defndescribe-db-instances[instance-id]

(observable-seq(aws-uri(str"/rds/describeDBInstances/"instance-id))

(fn[data]

(map(fn[instance]

{:instance-id(instance"DBInstanceIdentifier")

:type"RDS"

:status(instance"DBInstanceStatus")})

(data"DBInstances")))))

EachofwhichwillemitresourceitemsinthefollowingformatsforEC2andRDS,respectively:

({:instance-id"EC2123",:type"EC2",:status"running"}...)

({:instance-id"RDS123",:type"RDS",:status"available"}...)

Page 298: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

CombiningtheAWSObservablesItseemswehaveallmajorpiecesinplacenow.Allthatislefttodoistocombinethemoreprimitive,basicObservableswejustcreatedintomorecomplexandusefulonesbycombiningthemtoaggregateallthedataweneedinordertorenderourdashboard.

Wewillstartbycreatingafunctionthatcombinesboththedescribe-stacksanddescribe-stack-resourcesObservables:

(defnstack-resources[]

(->(describe-stacks)

(.map#(:stack-name%))

(.flatMapdescribe-stack-resources)))

Startinginthepreviousexample,webegintoseehowdefiningourAPIcallsintermsofObservablesequencespaysoff:it’salmostsimplecombiningthesetwoObservablesinadeclarativemanner.

RemembertheroleofflatMap:asdescribe-stack-resourcesitselfreturnsanObservable,weuseflatMaptoflattenbothObservables,aswehavedonebeforeinvariousdifferentabstractions.

Thestack-resourcesObservablewillemitresourceitemsforallstacks.Accordingtoourplan,wewouldliketoforktheprocessinghereinordertoconcurrentlyretrieveEC2andRDSinstancedata.

Byfollowingthistrainofthought,wearriveattwomorefunctionsthatcombineandtransformthepreviousObservables:

(defnec2-instance-status[resources]

(->resources

(.filter#(=(:resource-type%)"AWS::EC2::Instance"))

(.map#(:resource-id%))

(.reduceconj[])

(.flatMapdescribe-instances)))

(defnrds-instance-status[resources]

(->resources

(.filter#(=(:resource-type%)"AWS::RDS::DBInstance"))

(.map#(:resource-id%))

(.flatMapdescribe-db-instances)))

Boththefunctionsreceiveanargument,resources,whichistheresultofcallingthestack-resourcesObservable.Thatway,weonlyneedtocallitonce.

Onceagain,itisfairlysimpletocombinetheObservablesinawaythatmakessense,followingourhigh-levelideadescribedpreviously.

Startingwithresources,wefilteroutthetypeswe’renotinterestedin,retrieveitsIDs,andrequestitsdetailedinformationbyflatmappingthedescribe-instancesanddescribe-db-instancesObservables.

Note,however,thatduetoalimitationintheRDSAPIdescribedearlier,wehavetocallit

Page 299: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

multipletimestoretrieveinformationaboutallRDSinstances.

ThisseeminglyfundamentaldifferenceinhowweusetheAPIbecomesaminortransformationinourEC2observable,whichsimplyaccumulatesallIDsintoavectorsothatwecanretrievethemallatonce.

OursimpleReactiveAPItoAmazonAWSisnowcomplete,leavinguswiththeUItocreate.

Page 300: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

PuttingitalltogetherLet’snowturntobuildingouruserinterface.It’sasimpleone,solet’sjustjumpintoit.Openupaws-dash/src/cljs/aws_dash/core.cljsandaddthefollowing:

(nsaws-dash.core

(:require[aws-dash.observables:asobs]

[om.core:asom:include-macrostrue]

[om.dom:asdom:include-macrostrue]))

(enable-console-print!)

(defapp-state(atom{:instances[]}))

(defninstance-view[{:keys[instance-idtypestatus]}owner]

(reify

om/IRender

(render[this]

(dom/trnil

(dom/tdnilinstance-id)

(dom/tdniltype)

(dom/tdnilstatus)))))

(defninstances-view[instancesowner]

(reify

om/IRender

(render[this]

(applydom/table#js{:style#js{:border"1pxsolidblack;"}}

(dom/trnil

(dom/thnil"Id")

(dom/thnil"Type")

(dom/thnil"Status"))

(om/build-allinstance-viewinstances)))))

(om/root

(fn[appowner]

(dom/divnil

(dom/h1nil"StackResourceStatuses")

(om/buildinstances-view(:instancesapp))))

app-state

{:target(.js/document(getElementById"app"))})

Ourapplicationstatecontainsasinglekey,:instances,whichstartsasanemptyvector.AswecanseefromeachOmcomponent,instanceswillberenderedasrowsinaHTMLtable.

Aftersavingthefile,makesurethewebserverisrunningbystartingitfromtheREPL:

leinrepl

CompilingClojureScript.

nREPLserverstartedonport58209onhost127.0.0.1-

nrepl://127.0.0.1:58209

REPL-y0.3.5,nREPL0.2.6

Clojure1.6.0

JavaHotSpot(TM)64-BitServerVM1.8.0_25-b17

Page 301: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Docs:(docfunction-name-here)

(find-doc"part-of-name-here")

Source:(sourcefunction-name-here)

Javadoc:(javadocjava-object-or-class-here)

Exit:Control+Dor(exit)or(quit)

Results:Storedinvars*1,*2,*3,anexceptionin*e

user=>(run)

2015-02-0821:02:34.503:INFO:oejs.Server:jetty-7.6.8.v20121106

2015-02-0821:02:34.545:INFO:oejs.AbstractConnector:Started

[email protected]:3000

#<Serverorg.eclipse.jetty.server.Server@35bc3669>

Youshouldnowbeablepointyourbrowsertohttp://localhost:3000/,but,asyoumighthaveguessed,youwillseenothingbutanemptytable.

Thisisbecausewehaven’tyetusedourReactiveAWSAPI.

Let’sfixitandbringitalltogetheratthebottomofcore.cljs:

(defresources(obs/stack-resources))

(.subscribe(->(.merge(obs/rds-instance-statusresources)

(obs/ec2-instance-statusresources))

(.reduceconj[]))

#(swap!app-stateassoc:instances%))

Yes,thisisallweneed!Wecreateastack-resourcesObservableandpassitasanargumenttobothrds-instance-statusandec2-instance-status,whichwillconcurrentlyretrievestatusinformationaboutallinstances.

Next,wecreateanewObservablebymergingtheprevioustwofollowedbyacallto.reduce,whichwillaccumulateallinformationintoavector,convenientforrendering.

Finally,wesimplysubscribetothisObservableand,whenitemitsitsresults,wesimplyupdateourapplicationstate,leavingOmtodoalltherenderingforus.

SavethefileandmakesureClojureScripthascompiledsuccessfully.Then,gobacktoyourbrowserathttp://localhost:3000/,andyoushouldseeallinstancestatuses,aspicturedatthebeginningofthischapter.

Page 302: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 303: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ExercisesWithourpreviousapproach,theonlywaytoseenewinformationabouttheAWSresourcesisbyrefreshingthewholepage.Modifyourimplementationinsuchawaythatitqueriesthestubserviceseverysooften—say,every500milliseconds.

TipTheintervalfunctionfromRxJScanbehelpfulinsolvingthisexercise.ThinkhowyoumightuseittogetherwithourexistingstreambyreviewinghowflatMapworks.

Page 304: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 305: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

SummaryInthischapter,welookedatarealusecaseforReactiveapplications:buildingadashboardforAWSCloudFormationstacks.

Wehaveseenhowthinkingofalltheinformationneededasresources/itemsflowingthroughagraphfitsnicelywithhowonecreatesObservables.

Inaddition,bycreatingprimitiveObservablesthatdoonethingonlygivesusanicedeclarativewaytocombinethemintomorecomplexObservables,givingusadegreeofreusenotusuallyfoundwithcommontechniques.

Finally,wepackagedittogetherwithasimpleOm-basedinterfacetodemonstratehowusingdifferentabstractionsinthesameapplicationdoesnotaddtocomplexityaslongastheabstractionsarechosencarefullyfortheproblemathand.

ThisbringsustotheendofwhathopefullywasanenjoyableandinformativejourneythroughthedifferentwaysofReactiveProgramming.

Farfrombeingacompletereference,thisbookaimstoprovideyou,thereader,withenoughinformation,aswellasconcretetoolsandexamplesthatyoucanapplytoday.

Itisalsomyhopethatthereferencesandexercisesincludedinthisbookprovethemselvesuseful,shouldyouwishtoexpandyourknowledgeandseekoutmoredetails.

Lastly,IstronglyencourageyoutoturnthepageandreadtheAppendix,TheAlgebraofLibraryDesign,asItrulybelieveitwill,ifnothingelse,makeyouthinkhardabouttheimportanceofcompositioninprogramming.

Isincerelywishthisbookhasbeenasentertainingandinstructionaltoreadasitwastowrite.

Thankyouforreading.Ilookforwardtoseeingthegreatthingsyoubuild.

Page 306: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 307: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

AppendixA.TheAlgebraofLibraryDesignYoumighthavenoticedthatallreactiveabstractionswehaveencounteredinthisbookhaveafewthingsincommon.Forone,theyworkas“container-like”abstractions:

FuturesencapsulateacomputationthateventuallyyieldsasinglevalueObservablesencapsulatecomputationsthatcanyieldmultiplevaluesovertimeintheshapeofastreamChannelsencapsulatevaluespushedtothemandcanhavethempoppedfromit,workingasaconcurrentqueuethroughwhichconcurrentprocessescommunicate

Then,oncewehavethis“container,”wecanoperateonitinanumberofways,whichareverysimilaracrossthedifferentabstractionsandframeworks:wecanfilterthevaluescontainedinthem,transformthemusingmap,combineabstractionsofthesametypeusingbind/flatMap/selectMany,executemultiplecomputationsinparallel,aggregatetheresultsusingsequence,andmuchmore.

Assuch,eventhoughtheabstractionsandtheirunderlyingworkingsarefundamentallydifferent,itstillfeelstheybelongtosometypeofhigher-levelabstractions.

Inthisappendix,wewillexplorewhatthesehigher-levelabstractionsare,therelationshipbetweenthem,andhowwecantakeadvantageoftheminourprojects.

Page 308: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ThesemanticsofmapWewillgetstartedbytakingalookatoneofthemostusedoperationsintheseabstractions:map.

We’vebeenusingmapforalongtimeinordertotransformsequences.Thus,insteadofcreatinganewfunctionnameforeachnewabstraction,librarydesignerssimplyabstractthemapoperationoveritsowncontainertype.

Imaginethemesswewouldendupinifwehadfunctionssuchastransform-observable,transform-channel,combine-futures,andsoon.

Thankfully,thisisnotthecase.Thesemanticsofmaparewellunderstoodtothepointthatevenifadeveloperhasn’tusedaspecificlibrarybefore,hewillalmostalwaysassumethatmapwillapplyafunctiontothevalue(s)containedwithinwhateverabstractionthelibraryprovides.

Let’slookatthreeexamplesweencounteredinthisbook.Wewillcreateanewleiningenprojectinwhichtoexperimentwiththecontentsofthisappendix:

$leinnewlibrary-design

Next,let’saddafewdependenciestoourproject.cljfile:

...

:dependencies[[org.clojure/clojure"1.6.0"]

[com.leonardoborges/imminent"0.1.0"]

[com.netflix.rxjava/rxjava-clojure"0.20.7"]

[org.clojure/core.async"0.1.346.0-17112a-alpha"]

[uncomplicate/fluokitten"0.3.0"]]

...

Don’tworryaboutthelastdependency—we’llgettoitlateron.

Now,startanREPLsessionsothatwecanfollowalong:

$leinrepl

Then,enterthefollowingintoyourREPL:

(require'[imminent.core:asi]

'[rx.lang.clojure.core:asrx]

'[clojure.core.async:asasync])

(defrepl-out*out*)

(defnprn-to-repl[&args]

(binding[*out*repl-out]

(applyprnargs)))

(->(i/const-future31)

(i/map#(*%2))

(i/on-success#(prn-to-repl(str"Value:"%))))

(as->(rx/return31)obs

(rx/map#(*%2)obs)

Page 309: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

(rx/subscribeobs#(prn-to-repl(str"Value:"%))))

(defc(chan))

(defmapped-c(async/map<#(*%2)c))

(async/go(async/>!c31))

(async/go(prn-to-repl(str"Value:"(async/<!mapped-c))))

"Value:62"

"Value:62"

"Value:62"

Thethreeexamples—usingimminent,RxClojure,andcore.async,respectively—lookremarkablysimilar.Theyallfollowasimplerecipe:

1. Putthenumber31insidetheirrespectiveabstraction.2. Doublethatnumberbymappingafunctionovertheabstraction.3. PrintitsresulttotheREPL.

Asexpected,thisoutputsthevalue62threetimestothescreen.

Itwouldseemmapperformsthesameabstractstepsinallthreecases:itappliestheprovidedfunction,putstheresultingvalueinafreshnewcontainer,andreturnsit.Wecouldcontinuegeneralizing,butwewouldjustberediscoveringanabstractionthatalreadyexists:Functors.

Page 310: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

FunctorsFunctorsarethefirstabstractionwewilllookatandtheyarerathersimple:theydefineasingleoperationcalledfmap.InClojure,Functorscanberepresentedusingprotocolsandareusedforcontainersthatcanbemappedover.Suchcontainersinclude,butarenotlimitedto,lists,Futures,Observables,andchannels.

TipTheAlgebrainthetitleofthisAppendixreferstoAbstractAlgebra,abranchofMathematicsthatstudiesalgebraicstructures.Analgebraicstructureis,toputitsimply,asetwithoneormoreoperationsdefinedonit.

Asanexample,considerSemigroups,whichisonesuchalgebraicstructure.Itisdefinedtobeasetofelementstogetherwithanoperationthatcombinesanytwoelementsofthisset.Therefore,thesetofpositiveintegerstogetherwiththeadditionoperationformaSemigroup.

AnothertoolusedforstudyingalgebraicstructuresiscalledCategoryTheory,ofwhichFunctorsarepartof.

Wewon’tdelvetoomuchintothetheorybehindallthis,asthereareplentyofbooks[9][10]availableonthesubject.Itwas,however,anecessarydetourtoexplainthetitleusedinthisappendix.

DoesthismeanalloftheseabstractionsimplementaFunctorprotocol?Unfortunately,thisisnotthecase.AsClojureisadynamiclanguageanditdidn’thaveprotocolsbuiltin—theywereaddedinversion1.2ofthelanguage—theseframeworkstendtoimplementtheirownversionofthemapfunction,whichdoesn’tbelongtoanyprotocolinparticular.

Theonlyexceptionisimminent,whichimplementstheprotocolsincludedinfluokitten,aClojurelibraryprovidingconceptsfromCategorytheorysuchasFunctors.

ThisisasimplifiedversionoftheFunctorprotocolfoundinfluokitten:

(defprotocolFunctor

(fmap[fvg]))

Asmentionedpreviously,Functorsdefineasingleoperation.fmapappliesthefunctiongtowhatevervalueisinsidethecontainer,Functor,fv.

However,implementingthisprotocoldoesnotguaranteethatwehaveactuallyimplementedaFunctor.Thisisbecause,inadditiontoimplementingtheprotocol,Functorsarealsorequiredtoobeyacoupleoflaws,whichwewillexaminebriefly.

Theidentitylawisasfollows:

(=(fmapa-functoridentity)

(identitya-functor))

Theprecedingcodeisallweneedtoverifythislaw.Itsimplysaysthatmappingtheidentityfunctionovera-functoristhesameassimplyapplyingtheidentityfunction

Page 311: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

totheFunctoritself.

Thecompositionlawisasfollows:

(=(fmapa-functor(compfg))

(fmap(fmapa-functorg)f))

Thecompositionlaw,inturn,saysthatifwecomposetwoarbitraryfunctionsfandg,taketheresultingfunctionandapplythattoa-functor,thatisthesameasmappinggovertheFunctorandthenmappingfovertheresultingFunctor.

Noamountoftextwillbeabletoreplacepracticalexamples,sowewillimplementourownFunctor,whichwewillcallOption.Wewillthenrevisitthelawstoensurewehaverespectedthem.

Page 312: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

TheOptionFunctorAsTonyHoareonceputit,nullreferencesarehisonebilliondollarmistake(http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare).Regardlessofbackground,younodoubtwillhaveencounteredversionsofthedreadfulNullPointerException.Thisusuallyhappenswhenwetrytocallamethodonanobjectreferencethatisnull.

ClojureembracesnullvaluesduetoitsinteroperabilitywithJava,itshostlanguage,butitprovidesimprovedsupportfordealingwiththem.

Thecorelibraryispackedwithfunctionsthatdotherightthingifpassedanilvalue—Clojure’sversionofJava’snull.Forinstance,howmanyelementsarethereinanilsequence?

(countnil);;0

Thankstoconsciousdesigndecisionsregardingnil,wecan,forthemostpart,affordnotworryaboutit.Forallothercases,theOptionFunctormightbeofsomehelp.

Theremainingoftheexamplesinthisappendixshouldbeinafilecalledoption.cljunderlibrary-design/src/library_design/.You’rewelcometotrythisintheREPLaswell.

Let’sstartournextexamplebyaddingthenamespacedeclarationaswellasthedatawewillbeworkingwith:

(nslibrary-design.option

(:require[uncomplicate.fluokitten.protocols:asfkp]

[uncomplicate.fluokitten.core:asfkc]

[uncomplicate.fluokitten.jvm:asfkj]

[imminent.core:asI]))

(defpirates[{:name"JackSparrow":born1700:died1740:ship"Black

Pearl"}

{:name"Blackbeard":born1680:died1750:ship"Queen

Anne'sRevenge"}

{:name"HectorBarbossa":born1680:died1740:shipnil}])

(defnpirate-by-name[name]

(->>pirates

(filter#(=name(:name%)))

first))

(defnage[{:keys[borndied]}]

(-diedborn))

AsaPiratesoftheCaribbeanfan,Ithoughtitwouldbeinterestingtoplaywithpiratesforthisexample.Let’ssaywewouldliketocalculateJackSparrow’sage.Giventhedataandfunctionswejustcovered,thisisasimpletask:

(->(pirate-by-name"JackSparrow")

age);;40

Page 313: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

However,whatifwewouldliketoknowDavyJones’age?Wedon’tactuallyhaveanydataforthispirate,soifwerunourprogramagain,thisiswhatwe’llget:

(->(pirate-by-name"DavyJones")

age);;NullPointerExceptionclojure.lang.Numbers.ops

(Numbers.java:961)

Thereitis.ThedreadfulNullPointerException.Thishappensbecauseintheimplementationoftheagefunction,weenduptryingtosubtracttwonilvalues,whichisincorrect.Asyoumighthaveguessed,wewillattempttofixthisbyusingtheOptionFunctor.

Traditionally,Optionisimplementedasanalgebraicdatatype,morespecificallyasumtypewithtwovariants:SomeandNone.Thesevariantsareusedtoidentifywhetheravalueispresentornotwithoutusingnils.YoucanthinkofbothSomeandNoneassubtypesofOption.

InClojure,wewillrepresentthemusingrecords:

(defrecordSome[v])

(defrecordNone[])

(defnoption[v]

(ifv

(Some.v)

(None.)))

Aswecansee,SomecancontainasinglevaluewhereasNonecontainsnothing.It’ssimplyamarkerindicatingtheabsenceofcontent.Wehavealsocreatedahelperfunctioncalledoption,whichcreatestheappropriaterecorddependingonwhetheritsargumentisnilornot.

ThenextstepistoextendtheFunctorprotocoltobothrecords:

(extend-protocolfkp/Functor

Some

(fmap[fg]

(Some.(g(:vf))))

None

(fmap[__]

(None.)))

Here’swherethesemanticmeaningoftheOptionFunctorbecomesapparent:asSomecontainsavalue,itsimplementationoffmapsimplyappliesthefunctiongtothevalueinsidetheFunctorf,whichisoftypeSome.Finally,weputtheresultinsideanewSomerecord.

NowwhatdoesitmeantomapafunctionoveraNone?Youprobablyguessedthatitdoesn’treallymakesense—theNonerecordholdsnovalues.TheonlythingwecandoisreturnanotherNone.Aswewillseeshortly,thisgivestheOptionFunctorashort-circuitingsemantic.

Page 314: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

TipInthefmapimplementationofNone,wecouldhavereturnedareferencetothisinsteadofanewrecordinstance.I’venotdonesosimplytomakeitclearthatweneedtoreturnaninstanceofNone.

Nowthatwe’veimplementedtheFunctorprotocol,wecantryitout:

(->>(option(pirate-by-name"JackSparrow"))

(fkc/fmapage));;#library_design.option.Some{:v40}

(->>(option(pirate-by-name"DavyJones"))

(fkc/fmapage));;#library_design.option.None{}

Thefirstexampleshouldn’tholdanysurprises.Weconvertthepiratemapwegetfromcallingpirate-by-nameintoanoption,andthenfmaptheagefunctionoverit.

Thesecondexampleistheinterestingone.Asstatedpreviously,wehavenodataaboutDavyJones.However,mappingageoveritdoesnotthrowanexceptionanylonger,insteadreturningNone.

Thismightseemlikeasmallbenefit,butthebottomlineisthattheOptionFunctormakesitsafetochainoperationstogether:

(->>(option(pirate-by-name"JackSparrow"))

(fkc/fmapage)

(fkc/fmapinc)

(fkc/fmap#(*2%)));;#library_design.option.Some{:v82}

(->>(option(pirate-by-name"DavyJones"))

(fkc/fmapage)

(fkc/fmapinc)

(fkc/fmap#(*2%)));;#library_design.option.None{}

Atthispoint,somereadersmightbethinkingaboutthesome->macro—introducedinClojure1.5—andhowiteffectivelyachievesthesameresultastheOptionFunctor.Thisintuitioniscorrectasdemonstratedasfollows:

(some->(pirate-by-name"DavyJones")

age

inc

(*2));;nil

Thesome->macrothreadstheresultofthefirstexpressionthroughthefirstformifitisnotnil.Then,iftheresultofthatexpressionisn’tnil,itthreadsitthroughthenextformandsoon.Assoonasanyoftheexpressionsevaluatestonil,some->short-circuitsandreturnsnilimmediately.

Thatbeingsaid,Functorisamuchmoregeneralconcept,soaslongasweareworkingwiththisconcept,ourcodedoesn’tneedtochangeasweareoperatingatahigherlevelofabstraction:

(->>(i/future(pirate-by-name"JackSparrow"))

(fkc/fmapage)

Page 315: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

(fkc/fmapinc)

(fkc/fmap#(*2%)));;#<Future@30518bfc:#<Success@39bd662c:82>>

Intheprecedingexample,eventhoughweareworkingwithafundamentallydifferenttool—futures—thecodeusingtheresultdidnothavetochange.ThisisonlypossiblebecausebothOptionsandfuturesareFunctorsandimplementthesameprotocolprovidedbyfluokitten.WehavegainedcomposabilityandsimplicityaswecanusethesameAPItoworkwithvariousdifferentabstractions.

Speakingofcomposability,thispropertyisguaranteedbythesecondlawofFunctors.Let’sseeifourOptionFunctorrespectsthisandthefirst—theidentity—laws:

;;Identity

(=(fkc/fmapidentity(option1))

(identity(option1)));;true

;;Composition

(=(fkc/fmap(compidentityinc)(option1))

(fkc/fmapidentity(fkc/fmapinc(option1))));;true

Andwe’redone,ourOptionFunctorisalawfulcitizen.Theremainingtwoabstractionsalsocomepairedwiththeirownlaws.Wewillnotcoverthelawsinthissection,butIencouragethereadertoreadaboutthem(http://www.leonardoborges.com/writings/2012/11/30/monads-in-small-bites-part-i-functors/).

Page 316: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 317: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

FindingtheaverageofagesInthissection,wewillexploreadifferentusecasefortheOptionFunctor.Wewouldliketo,givenanumberofpirates,calculatetheaverageoftheirages.Thisissimpleenoughtodo:

(defnavg[&xs]

(float(/(apply+xs)(countxs))))

(let[a(some->(pirate-by-name"JackSparrow")age)

b(some->(pirate-by-name"Blackbeard")age)

c(some->(pirate-by-name"HectorBarbossa")age)]

(avgabc));;56.666668

Notehowweareusingsome->heretoprotectusfromnilvalues.Now,whathappensifthereisapirateforwhichwehavenoinformation?

(let[a(some->(pirate-by-name"JackSparrow")age)

b(some->(pirate-by-name"DavyJones")age)

c(some->(pirate-by-name"HectorBarbossa")age)]

(avgabc));;NullPointerExceptionclojure.lang.Numbers.ops

(Numbers.java:961)

Itseemswe’rebackatsquareone!It’sworsenowbecauseusingsome->doesn’thelpifweneedtouseallvaluesatonce,asopposedtothreadingthemthroughachainoffunctioncalls.

Ofcourse,notallislost.Allweneedtodoischeckifallvaluesarepresentbeforecalculatingtheaverage:

(let[a(some->(pirate-by-name"JackSparrow")age)

b(some->(pirate-by-name"DavyJones")age)

c(some->(pirate-by-name"HectorBarbossa")age)]

(when(andabc)

(avgabc)));;nil

Whilethisworksperfectlyfine,ourimplementationsuddenlyhadtobecomeawarethatanyorallofthevaluesa,b,andccouldbenil.Thenextabstractionwewilllookat,ApplicativeFunctors,fixesthis.

Page 318: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 319: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ApplicativeFunctorsLikeFunctors,ApplicativeFunctorsareasortofcontaineranddefinestwooperations:

(defprotocolApplicative

(pure[avv])

(fapply[agav]))

ThepurefunctionisagenericwaytoputavalueinsideanApplicativeFunctor.Sofar,wehavebeenusingtheoptionhelperfunctionforthispurpose.Wewillbeusingitalittlelater.

ThefapplyfunctionwillunwrapthefunctioncontainedintheApplicativeagandapplyittothevaluecontainedintheapplicativeav.

Thepurposeofboththefunctionswillbecomeclearwithanexample,butfirst,weneedtopromoteourOptionFunctorintoanApplicativeFunctor:

(extend-protocolfkp/Applicative

Some

(pure[_v]

(Some.v))

(fapply[agav]

(if-let[v(:vav)]

(Some.((:vag)v))

(None.)))

None

(pure[_v]

(Some.v))

(fapply[agav]

(None.)))

Theimplementationofpureisthesimplest.AllitdoesiswrapthevaluevintoaninstanceofSome.EquallysimpleistheimplementationoffapplyforNone.Asthereisnovalue,wesimplyreturnNoneagain.

ThefapplyimplementationofSomeensuresbothargumentshaveavalueforthe:vkeyword—strictlyspeakingtheybothhavetobeinstancesofSome.If:visnon-nil,itappliesthefunctioncontainedinagtov,finallywrappingtheresult.Otherwise,itreturnsNone.

ThisshouldbeenoughtotryourfirstexampleusingtheApplicativeFunctorAPI:

(fkc/fapply(optioninc)(option2))

;;#library_design.option.Some{:v3}

(fkc/fapply(optionnil)(option2))

;;#library_design.option.None{}

WearenowabletoworkwithFunctorsthatcontainfunctions.Additionally,wehavealsopreservedthesemanticsofwhatshouldhappenwhenanyoftheFunctorsdon’thavea

Page 320: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

value.

Wecannowrevisittheageaverageexamplefrombefore:

(defage-option(comp(partialfkc/fmapage)optionpirate-by-name))

(let[a(age-option"JackSparrow")

b(age-option"Blackbeard")

c(age-option"HectorBarbossa")]

(fkc/<*>(option(fkj/curryavg3))

abc))

;;#library_design.option.Some{:v56.666668}

TipThevarargfunction<*>isdefinedbyfluokittenandperformsaleft-associativefapplyonitsarguments.Essentially,itisaconveniencefunctionthatmakes(<*>fgh)equivalentto(fapply(fapplyfg)h).

Westartbydefiningahelperfunctiontoavoidrepetition.Theage-optionfunctionretrievestheageofapirateasanoptionforus.

Next,wecurrytheavgfunctionto3argumentsandputitintoanoption.Then,weusethe<*>functiontoapplyittotheoptionsa,b,andc.Wegettothesameresult,buthavetheApplicativeFunctortakecareofnilvaluesforus.

TipFunctioncurrying

Curryingisthetechniqueoftransformingafunctionofmultipleargumentsintoahigher-orderfunctionofasingleargumentthatreturnsmoresingle-argumentfunctionsuntilallargumentshavebeensupplied.

Roughlyspeaking,curryingmakesthefollowingsnippetsequivalent:

(defcurried-1(fkj/curry+2))

(defcurried-2(fn[a]

(fn[b]

(+ab))))

((curried-110)20);;30

((curried-210)20);;30

UsingApplicativeFunctorsthiswayissocommonthatthepatternhasbeencapturedasthefunctionalift,asshowninthefollowing:

(defnalift

"Liftsan-aryfunction`f`intoaapplicativecontext"

[f]

(fn[&as]

{:pre[(seqas)]}

(let[curried(fkj/curryf(countas))]

(applyfkc/<*>

(fkc/fmapcurried(firstas))

(restas)))))

Page 321: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

ThealiftfunctionisresponsibleforliftingafunctioninsuchawaythatitcanbeusedwithApplicativeFunctorswithoutmuchceremony.BecauseoftheassumptionsweareabletomakeaboutApplicativeFunctors—forinstance,thatitisalsoaFunctor—wecanwritegenericcodethatcanbere-usedacrossanyApplicatives.

Withaliftinplace,ourageaverageexampleturnsintothefollowing:

(let[a(age-option"JackSparrow")

b(age-option"Blackbeard")

c(age-option"HectorBarbossa")]

((aliftavg)abc))

;;#library_design.option.Some{:v56.666668}

WeliftavgintoanApplicativecompatibleversion,makingthecodelookremarkablylikesimplefunctionapplication.Andsincewearenotdoinganythinginterestingwiththeletbindings,wecansimplifyitfurtherasfollows:

((aliftavg)(age-option"JackSparrow")

(age-option"Blackbeard")

(age-option"HectorBarbossa"))

;;#library_design.option.Some{:v56.666668}

((aliftavg)(age-option"JackSparrow")

(age-option"DavyJones")

(age-option"HectorBarbossa"))

;;#library_design.option.None{}

AswithFunctors,wecantakethecodeasitis,andsimplyreplacetheunderlyingabstraction,preventingrepetitiononceagain:

((aliftavg)(i/future(some->(pirate-by-name"JackSparrow")age))

(i/future(some->(pirate-by-name"Blackbeard")age))

(i/future(some->(pirate-by-name"HectorBarbossa")age)))

;;#<Future@17b1be96:#<Success@16577601:56.666668>>

Page 322: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 323: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

GatheringstatsaboutagesNowthatwecansafelycalculatetheaverageageofanumberofpirates,itmightbeinterestingtotakethisfurtherandcalculatethemedianandstandarddeviationofthepirates’ages,inadditiontotheiraverageage.

Wealreadyhaveafunctiontocalculatetheaverage,solet’sjustcreatetheonestocalculatethemedianandthestandarddeviationofalistofnumbers:

(defnmedian[&ns]

(let[ns(sortns)

cnt(countns)

mid(bit-shift-rightcnt1)]

(if(odd?cnt)

(nthnsmid)

(/(+(nthnsmid)(nthns(decmid)))2))))

(defnstd-dev[&samples]

(let[n(countsamples)

mean(/(reduce+samples)n)

intermediate(map#(Math/pow(-%1mean)2)samples)]

(Math/sqrt

(/(reduce+intermediate)n))))

Withthesefunctionsinplace,wecanwritethecodethatwillgatherallthestatsforus:

(let[a(some->(pirate-by-name"JackSparrow")age)

b(some->(pirate-by-name"Blackbeard")age)

c(some->(pirate-by-name"HectorBarbossa")age)

avg(avgabc)

median(medianabc)

std-dev(std-devabc)]

{:avgavg

:medianmedian

:std-devstd-dev})

;;{:avg56.666668,

;;:median60,

;;:std-dev12.472191289246473}

Thisimplementationisfairlystraightforward.Wefirstretrieveallageswe’reinterestedinandbindthemtothelocalsa,b,andc.Wethenreusethevalueswhencalculatingtheremainingstats.Wefinallygatherallresultsinamapforeasyaccess.

Bynowthereaderwillprobablyknowwherewe’reheaded:whatifanyofthosevaluesisnil?

(let[a(some->(pirate-by-name"JackSparrow")age)

b(some->(pirate-by-name"DavyJones")age)

c(some->(pirate-by-name"HectorBarbossa")age)

avg(avgabc)

median(medianabc)

std-dev(std-devabc)]

{:avgavg

Page 324: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

:medianmedian

:std-devstd-dev})

;;NullPointerExceptionclojure.lang.Numbers.ops(Numbers.java:961)

Thesecondbinding,b,returnsnil,aswedon’thaveanyinformationaboutDavyJones.Assuch,itcausesthecalculationstofail.Likebefore,wecanchangeourimplementationtoprotectusfromsuchfailures:

(let[a(some->(pirate-by-name"JackSparrow")age)

b(some->(pirate-by-name"DavyJones")age)

c(some->(pirate-by-name"HectorBarbossa")age)

avg(when(andabc)(avgabc))

median(when(andabc)(medianabc))

std-dev(when(andabc)(std-devabc))]

(when(andabc)

{:avgavg

:medianmedian

:std-devstd-dev}))

;;nil

Thistimeit’sevenworsethanwhenweonlyhadtocalculatetheaverage;thecodeischeckingfornilvaluesinfourextraspots:beforecallingthethreestatsfunctionsandjustbeforegatheringthestatsintotheresultmap.

Canwedobetter?

Page 325: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 326: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

MonadsOurlastabstractionwillsolvetheveryproblemweraisedintheprevioussection:howtosafelyperformintermediatecalculationsbypreservingthesemanticsoftheabstractionswe’reworkingwith—inthiscase,options.

ItshouldbenosurprisenowthatfluokittenalsoprovidesaprotocolforMonads,simplifiedandshownasfollows:

(defprotocolMonad

(bind[mvg]))

Ifyouthinkintermsofaclasshierarchy,Monadswouldbeatthebottomofit,inheritingfromApplicativeFunctors,which,inturn,inheritfromFunctors.Thatis,ifyou’reworkingwithaMonad,youcanassumeitisalsoanApplicativeandaFunctor.

Thebindfunctionofmonadstakesafunctiongasitssecondargument.ThisfunctionreceivesasinputthevaluecontainedinmvandreturnsanotherMonadcontainingitsresult.Thisisacrucialpartofthecontract:ghastoreturnaMonad.

Thereasonwhywillbecomecleareraftersomeexamples.Butfirst,let’spromoteourOptionabstractiontoaMonad—atthispoint,OptionisalreadyanApplicativeFunctorandaFunctor:

(extend-protocolfkp/Monad

Some

(bind[mvg]

(g(:vmv)))

None

(bind[__]

(None.)))

Theimplementationisfairlysimple.IntheNoneversion,wecan’treallydoanything,sojustlikewehavebeendoingsofar,wereturnaninstanceofNone.

TheSomeimplementationextractsthevaluefromtheMonadmvandappliesthefunctiongtoit.Notehowthistimewedon’tneedtowraptheresultasthefunctiongalreadyreturnsaMonadinstance.

UsingtheMonadAPI,wecouldsumtheagesofourpiratesasfollows:

(defopt-ctx(None.))

(fkc/bind(age-option"JackSparrow")

(fn[a]

(fkc/bind(age-option"Blackbeard")

(fn[b]

(fkc/bind(age-option"HectorBarbossa")

(fn[c]

(fkc/pureopt-ctx

(+abc))))))))

;;#library_design.option.Some{:v170.0}

Page 327: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Firstly,wearemakinguseofApplicative’spurefunctionintheinner-mostfunction.RememberthatroleofpureistoprovideagenericwaytoputavalueintoanApplicativeFunctor.SinceMonadsarealsoApplicative,wemakeuseofthemhere.

However,sinceClojureisadynamicallytypedlanguage,weneedtohintpurewiththecontext—container—typewewishtouse.ThiscontextissimplyaninstanceofeitherSomeorNone.Theybothhavethesamepureimplementation.

Whilewedogettherightanswer,theprecedingexampleisfarfromwhatwewouldliketowriteduetoitsexcessivenesting.Itisalsohardtoread.

Thankfully,fluokittenprovidesamuchbetterwaytowritemonadiccode,calledthedo-notation:

(fkc/mdo[a(age-option"JackSparrow")

b(age-option"Blackbeard")

c(age-option"HectorBarbossa")]

(fkc/pureopt-ctx(+abc)))

;;#library_design.option.Some{:v170.0}

Suddenly,thesamecodebecomesalotcleanerandeasiertoread,withoutlosinganyofthesemanticsoftheOptionMonad.Thisisbecausemdoisamacrothatexpandstothecodeequivalentofthenestedversion,aswecanverifybyexpandingthemacroasfollows:

(require'[clojure.walk:asw])

(w/macroexpand-all'(fkc/mdo[a(age-option"JackSparrow")

b(age-option"Blackbeard")

c(age-option"HectorBarbossa")]

(option(+abc))))

;;(uncomplicate.fluokitten.core/bind

;;(age-option"JackSparrow")

;;(fn*

;;([a]

;;(uncomplicate.fluokitten.core/bind

;;(age-option"Blackbeard")

;;(fn*

;;([b]

;;(uncomplicate.fluokitten.core/bind

;;(age-option"HectorBarbossa")

;;(fn*([c](fkc/pureopt-ctx(+abc)))))))))))

TipItisimportanttostopforamomenthereandappreciatethepowerofClojure—andLispingeneral.

LanguagessuchasHaskellandScala,whichmakeheavyuseofabstractionssuchasFunctors,Applicative,andMonads,alsohavetheirownversionsofthedo-notation.However,thissupportisbakedintothecompileritself.

Asanexample,whenHaskelladdeddo-notationtothelanguage,anewversionofthecompilerwasreleased,anddeveloperswishingtousethenewfeaturehadtoupgrade.

Page 328: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

InClojure,ontheotherhand,thisnewfeaturecanbeshippedasalibraryduetothepowerandflexibilityofmacros.Thisisexactlywhatfluokittenhasdone.

Now,wearereadytogobacktoouroriginalproblem,gatheringstatsaboutthepirates’ages.

First,wewilldefineacoupleofhelperfunctionsthatconverttheresultofourstatsfunctionsintotheOptionMonad:

(defavg-opt(compoptionavg))

(defmedian-opt(compoptionmedian))

(defstd-dev-opt(compoptionstd-dev))

Here,wetakeadvantageoffunctioncompositiontocreatemonadicversionsofexistingfunctions.

Next,wewillrewriteoursolutionusingthemonadicdo-notationwelearnedearlier:

(fkc/mdo[a(age-option"JackSparrow")

b(age-option"Blackbeard")

c(age-option"HectorBarbossa")

avg(avg-optabc)

median(median-optabc)

std-dev(std-dev-optabc)]

(option{:avgavg

:medianmedian

:std-devstd-dev}))

;;#library_design.option.Some{:v{:avg56.666668,

;;:median60,

;;:std-dev12.472191289246473}}

Thistimewewereabletowritethefunctionaswenormallywould,withouthavingtoworryaboutwhetheranyvaluesintheintermediatecomputationsareemptyornot.ThissemanticthatistheveryessenceoftheOptionMonadisstillpreserved,ascanbeseeninthefollowing:

(fkc/mdo[a(age-option"JackSparrow")

b(age-option"Blackbeard")

c(age-option"HectorBarbossa")

avg(avg-optabc)

median(median-optabc)

std-dev(std-dev-optabc)]

(fkc/pureopt-ctx{:avgavg

:medianmedian

:std-devstd-dev}))

;;#library_design.option.None{}

Forthesakeofcompleteness,wewillusefuturestodemonstratehowthedo-notationworksforanyMonad:

(defavg-fut(compi/future-callavg))

(defmedian-fut(compi/future-callmedian))

(defstd-dev-fut(compi/future-callstd-dev))

(fkc/mdo[a(i/future(some->(pirate-by-name"JackSparrow")age))

Page 329: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

b(i/future(some->(pirate-by-name"Blackbeard")age))

c(i/future(some->(pirate-by-name"HectorBarbossa")

age))

avg(avg-futabc)

median(median-futabc)

std-dev(std-dev-futabc)]

(i/const-future{:avgavg

:medianmedian

:std-devstd-dev}))

;;#<Future@3fd0b0d0:#<Success@1e08486b:{:avg56.666668,

;;:median60,

;;:std-dev12.472191289246473}>>

Page 330: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 331: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

SummaryThisappendixhastakenusonabrieftouroftheworldofcategorytheory.Welearnedthreeofitsabstractions:Functors,ApplicativeFunctors,andMonads.Theyweretheguidingprinciplebehindimminent’sAPI.

Todeepenourknowledgeandunderstanding,weimplementedourownOptionMonad,acommonabstractionusedtosafelyhandletheabsenceofvalues.

Wehavealsoseenthatusingtheseabstractionsallowustomakesomeassumptionsaboutourcode,asseeninfunctionssuchasalift.Therearemanyotherfunctionswewouldnormallyrewriteoverandoveragainfordifferentpurposes,butthatcanbereusedifwerecognizeourcodefitsintooneoftheabstractionslearned.

Finally,Ihopethisencouragesreaderstoexplorecategorytheorymore,asitwillundoubtedlychangethewayyouthink.AndifIcanbesobold,Ihopethiswillalsochangethewayyoudesignlibrariesinthefuture.

Page 332: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but
Page 333: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

AppendixB.Bibliography[1]RenePardoandRemyLandau,TheWorld’sFirstElectronicSpreadsheet:http://www.renepardo.com/articles/spreadsheet.pdf

[2]ConalElliottandPaulHudak,FunctionalReactiveAnimation:http://conal.net/papers/icfp97/icfp97.pdf

[3]EvanCzaplicki,Elm:ConcurrentFRPforFunctionalGUIs:http://elm-lang.org/papers/concurrent-frp.pdf

[4]ErikMeijer,Subject/ObserverisDualtoIterator:http://csl.stanford.edu/~christos/pldi2010.fit/meijer.duality.pdf

[5]HenrikNilsson,AntonyCourtneyandJohnPeterson,FunctionalReactiveProgramming,Continued:http://haskell.cs.yale.edu/wp-content/uploads/2011/02/workshop-02.pdf

[6]JohnHughes,GeneralisingMonadstoArrows:http://www.cse.chalmers.se/~rjmh/Papers/arrows.pdf

[7]ZhanyongWan,WalidTahaandPaulHudak,Real-TimeFRP:http://haskell.cs.yale.edu/wp-content/uploads/2011/02/rt-frp.pdf

[8]WalidTaha,ZhanyongWan,andPaulHudak,Event-DrivenFRP:http://www.cs.yale.edu/homes/zwan/papers/mcu/efrp.pdf

[9]BenjaminC.Pierce,BasicCategoryTheoryforComputerScientists:http://www.amazon.com/Category-Computer-Scientists-Foundations-Computing-ebook/dp/B00MG7E5WE/ref=sr_1_7?ie=UTF8&qid=1423484917&sr=8-7&keywords=category+theory

[10]SteveAwodey,CategoryTheory(OxfordLogicGuides):http://www.amazon.com/Category-Theory-Oxford-Logic-Guides/dp/0199237182/ref=sr_1_2?ie=UTF8&qid=1423484917&sr=8-2&keywords=category+theory

[11]DuncanCoutts,RomanLeshchinskiy,andDonStewart,StreamFusion:http://code.haskell.org/~dons/papers/icfp088-coutts.pdf

[12]PhilipWadler,Transformingprogramstoeliminatetrees:http://homepages.inf.ed.ac.uk/wadler/papers/deforest/deforest.ps

Page 334: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

IndexA

AbstractAlgebraabout/Functors

agileboardcreating,withOm/CreatinganagileboardwithOmstate/Theboardstatecomponents/Componentsoverviewlifecycle/Lifecycleandcomponentlocalstatecomponentlocalstate/Lifecycleandcomponentlocalstatemultiplecolumn-viewcomponents,creating/Remainingcomponentsutilityfunctions,adding/Utilityfunctions

ApplicativeFunctorsabout/ApplicativeFunctorspurefunction/ApplicativeFunctorsfapplyfunction/ApplicativeFunctorsvarargfunction/ApplicativeFunctorsstats,calculatingofages/Gatheringstatsaboutages

ArrowizedFRPabout/ArrowizedFRP

Asteroidsabout/Settinguptheproject

asynchronousdataflowabout/Asynchronousdataflow

asynchronousprogrammingabout/AsynchronousCompositionalEventSystem(CES)aboutprogrammingandconcurrency

AutomatonURL/ArrowizedFRP

AWSusing/InfrastructureautomationEC2/InfrastructureautomationRDS/InfrastructureautomationCloudFormation/Infrastructureautomation

AWSresourcesdashboardbuilding/AWSresourcesdashboardbuilding,withCloudFormation/CloudFormationbuilding,withEC2/EC2building,withRDS/RDSdesigning/Designingthesolutionstubserver,settingup/RunningtheAWSstubserversettingup/Settingupthedashboardproject

Page 335: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Observables,creating/CreatingAWSObservablesObservables,combining/CombiningtheAWSObservablesuserinterface,building/Puttingitalltogether

Page 336: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Bbackpressure

about/Backpressuresamplecombinator/Samplestrategies/Backpressurestrategiesreferencelink/Backpressurestrategies

Bacon.jsURL/FunctionalReactiveProgrammingabout/Asynchronousdataflow

blockingIOabout/FuturesandblockingIO

bufferingabout/Backpressurefixedbuffer/Fixedbufferdroppingbuffer/Droppingbufferslidingbuffer/Slidingbuffer

Page 337: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Ccatchcombinator

about/CatchCES

versusFRP/Asynchronousdataflowversuscore.async/CESversuscore.async

ChestnutURL/AtasteofReactiveProgramming

cljs-startURL/Arespondentapplication,Settinguptheproject

cljxURL/ClojureorClojureScript?about/ClojureorClojureScript?

ClojureURL,fordocumentation/Applicationcomponentsfutures/Clojurefutures

Clojurescriptabout/ClojureScriptandOm

ClojureScriptgameproject,settingup/Settinguptheprojectgameentities,creating/Gameentitiesimplementing/Puttingitalltogetheruserinput,modelingaseventstreams/Modelinguserinputaseventstreamsactivekeysstream,workingwith/Workingwiththeactivekeysstream

CloudFormationabout/Infrastructureautomationused,forbuildingAWSresourcesdashboard/CloudFormationdescribeStacksendpoint/ThedescribeStacksendpointdescribeStackResourcesendpoint/ThedescribeStackResourcesendpoint

combinatorsusing/Combinatorsandeventhandlers

complexWebUIsproblems/TheproblemwithcomplexwebUIsfeatures/TheproblemwithcomplexwebUIs

CompositionalEventSystem(CES)about/AsynchronousCompositionalEventSystem(CES)aboutprogrammingandconcurrency

CompositionalEventSystemsabout/Onemoreflatmapfortheroad

concurrencyabout/AsynchronousCompositionalEventSystem(CES)aboutprogrammingandconcurrency

Contactsapplication

Page 338: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

building/BuildingasimpleContactsapplicationwithOmstate/TheContactsapplicationstateproject,settingup/SettinguptheContactsprojectcomponents/Applicationcomponentscursors,using/Omcursorscontacts-appcomponent,creating/Fillingintheblankscontacts-viewcomponent,creating/Fillingintheblankscontact-summary-viewcomponent,creating/Fillingintheblanksdetails-panel-viewcomponent,creating/Fillingintheblankscontact-details-viewcomponent,creating/Fillingintheblankscontact-details-form-viewcomponent,creating/Fillingintheblankscontactinformation,updating/Fillingintheblanks

core.asyncabout/core.asyncCSP/Communicatingsequentialprocessesstockmarketapplication,rewriting/Rewritingthestockmarketapplicationwithcore.asyncerrorhandling/Errorhandlingbackpressure/Backpressuretransducers/Transducersandcore.asyncfeatures/SummaryversusCES/CESversuscore.async

core.asyncchannelsusing/Intercomponentcommunication

CSPabout/CommunicatingsequentialprocessesURL/Communicatingsequentialprocesses

cursorsabout/Omcursors

Page 339: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Ddata

fetching,inparallel/Fetchingdatainparalleldataflowprogramming

about/DataflowprogrammingDataTransfer

referencelink/Utilityfunctionsdc-lib

URL/DataflowprogrammingdescribeDBInstancesendpoint

about/ThedescribeDBInstancesendpointdescribeInstancesendpoint

about/ThedescribeInstancesendpointdescribeStackResourcesendpoint

about/ThedescribeStackResourcesendpointdescribeStacksendpoint

about/ThedescribeStacksendpointdroppingbuffer

about/Droppingbuffer

Page 340: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

EEC2

about/Infrastructureautomationused,forbuildingAWSresourcesdashboard/EC2describeInstancesendpoint/ThedescribeInstancesendpoint

Elmabout/First-orderFRPURL/First-orderFRP

errorhandlingabout/ErrorhandlingonErrorcombinator/OnErrorcatchcombinator/Catchretrycombinator/Retryreferencelink/Retryincore.async/Errorhandling

eventhandlersusing/Combinatorsandeventhandlers

eventsabout/Signalsandevents

Page 341: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Ffactorymethods

referencelink/CustomObservablesfirst-orderFRP

about/First-orderFRPfixedbuffer

about/FixedbufferFlapjax

about/ComplexGUIsandanimationsflatmap

about/Flatmapandfriendswithmultiplevalues/Onemoreflatmapfortheroad

ForkJoinPoolURL/Themoviesexamplerevisitedusing/FuturesandblockingIO

FRPabout/FunctionalReactiveProgrammingimplementationchallenges/ImplementationchallengesversusCES/Asynchronousdataflow

FRP,usecasesasynchronousprogramming/Asynchronousprogrammingandnetworkingnetworking/AsynchronousprogrammingandnetworkingcomplexGUIs/ComplexGUIsandanimationsanimations/ComplexGUIsandanimations

FunctionalProgrammingabout/Lessonsfromfunctionalprogramming

functioncurryingabout/ApplicativeFunctors

Functorsabout/FunctorsOptionFunctor/TheOptionFunctorApplicativeFunctors/ApplicativeFunctors

futuresabout/Clojurefuturescreating,inimminent/CreatingfuturesblockingIO/FuturesandblockingIO

Page 342: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

GGraphicalUserInterfaces(GUIs)

about/Object-orientedReactiveProgramming

Page 343: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

HHaskell

about/Monadshigher-orderFRP

about/Higher-orderFRPhistory,ReactiveProgramming

about/Abitofhistorydataflowprogramming/Dataflowprogrammingobject-orientedReactiveProgramming/Object-orientedReactiveProgrammingLANguageforProgrammingArraysatRandom(LANPAR)/ThemostwidelyusedreactiveprogramObserverdesignpattern/TheObserverdesignpatternFRP/FunctionalReactiveProgramminghigher-orderFRP/Higher-orderFRP

Page 344: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Iimminent

about/Imminent–acomposablefutureslibraryforClojureURL/Imminent–acomposablefutureslibraryforClojurefutures,creating/Creatingfuturescombinators,using/Combinatorsandeventhandlerseventhandlers,using/Combinatorsandeventhandlersexample/Themoviesexamplerevisited

implementationchallenges,FRPfirst-orderFRP/First-orderFRPasynchronousdataflow/AsynchronousdataflowArrowizedFRP/ArrowizedFRP

incidentalcomplexityabout/Identifyingproblemswithourcurrentapproachremoving,withRxClojure/RemovingincidentalcomplexitywithRxClojure

infrastructureautomationproblem/TheproblemwithAWS/Infrastructureautomation

intercomponentcommunicationabout/Intercomponentcommunicationagileboard,creating/CreatinganagileboardwithOm

Iteratorinterfaceabout/Observer–anIterator’sdual

Page 345: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

JJavaInterop

URL/FillingintheblanksJSPerf

URL/TheproblemwithcomplexwebUIs

Page 346: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

LLANguageforProgrammingArraysatRandom(LANPAR)

about/Themostwidelyusedreactiveprogramlein-cljsbuild

URL/ClojureorClojureScript?about/ClojureorClojureScript?

Page 347: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Mmacros

referencelink/GameentitiesManagedBlocker

referencelink/FuturesandblockingIOmap

about/ThesemanticsofmapFunctors/Functors

MimmoCosenzaURL/SettinguptheContactsproject

minimalCESframeworkabout/AminimalCESframeworkproject,settingup/ClojureorClojureScript?publicAPI,designing/DesigningthepublicAPItokens,implementing/Implementingtokenseventstreams,implementing/Implementingeventstreamsbehaviors,implementing/Implementingbehaviorsrespondentapplication,building/Arespondentapplication

Monadabout/Onemoreflatmapfortheroad

Monadsabout/Monadsbindfunction/Monadsusing/Monadsexample/Monads

monetURL/Settinguptheproject

Page 348: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

NNetflix

about/Asynchronousprogrammingandnetworking

Page 349: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Oobject-orientedReactiveProgramming

about/Object-orientedReactiveProgrammingObservables

creating/CreatingAWSObservablescombining/CombiningtheAWSObservables

observablescreating/CreatingObservablescustomobservables,creating/CustomObservablesmanipulating/ManipulatingObservables

ObservableSequencesabout/Asynchronousdataflow

Observerdesignpatternabout/TheObserverdesignpattern,TheObserverpatternrevisitedIteratorinterface/Observer–anIterator’sdual

Omabout/ClojureScriptandOmContactsapplication,building/BuildingasimpleContactsapplicationwithOmagileboard,creating/CreatinganagileboardwithOm

om-starttemplateURL/SettinguptheContactsproject

OmProjectManagement/CreatinganagileboardwithOmonErrorcombinator

about/OnErrorOptionFunctor

about/TheOptionFunctorusecase/Findingtheaverageofages

Page 350: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

PPurelyFunctionalDataStructures

about/LessonsfromfunctionalprogrammingURL/Lessonsfromfunctionalprogramming

Page 351: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

RRDS

about/Infrastructureautomationused,forbuildingAWSresourcesdashboard/RDSdescribeDBInstancesendpoint/ThedescribeDBInstancesendpoint

React.jsabout/EnterReact.jsFunctionalProgramming/Lessonsfromfunctionalprogramming

ReactiveCocoaURL/FunctionalReactiveProgrammingabout/Asynchronousdataflow

ReactiveExtensions(Rx)about/Asynchronousdataflow,ReagiandotherCESframeworksdrawbacks/ReagiandotherCESframeworks

Reagicomparing,withotherCESframeworks/ReagiandotherCESframeworksabout/ReagiandotherCESframeworks

respondentapplicationbuilding/Arespondentapplication

retrycombinatorabout/Retry

Rxobservables,creating/CreatingObservablesobservables,manipulating/ManipulatingObservablesflatmap/Flatmapandfriends

RXerrorhandling/Errorhandling

RxClojureURL/CreatingObservables

RxJavaURL/FunctionalReactiveProgramming,CreatingObservables

RxJSURL/AtasteofReactiveProgramming

Page 352: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

Ssamplecombinator

about/SampleScala

about/MonadsScheduledThreadPoolExecutorpool/BuildingastockmarketmonitoringapplicationSemigroups

about/Functorssignals

about/Signalsandeventssinewaveanimation

creating/AtasteofReactiveProgrammingtime,creating/Creatingtimecoloring/Morecolorsupdating/Makingitreactiveenhancing/Exercise1.1

slidingbufferabout/Slidingbuffer

stockmarketapplicationrewriting,withcore.async/Rewritingthestockmarketapplicationwithcore.asyncapplicationcode,implementing/Implementingtheapplicationcode

stockmarketmonitoringapplicationbuilding/Buildingastockmarketmonitoringapplicationrollingaverages,displaying/Rollingaveragesproblems,identifying/Identifyingproblemswithourcurrentapproachincidentalcomplexity,removingwithRxClojure/RemovingincidentalcomplexitywithRxClojureobservablerollingaverages/Observablerollingaverages

stubserversettingup/RunningtheAWSstubserver

Page 353: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

TThreadPool

about/Fetchingdatainparalleltools.namespace

URL/Implementingeventstreamsusing/Implementingeventstreams

transducersabout/Transducersreferencelink/Transducerswithcore.async/Transducersandcore.async

transitabout/SettingupthedashboardprojectURL/Settingupthedashboardproject

TrelloURL/Intercomponentcommunication

Page 354: Clojure Reactive Programming - Programmer Books€¦ · Clojure and ClojureScript to help build real-time collaborative editing technology. This is his first full-length book, but

VVirtualDOM

about/Lessonsfromfunctionalprogramming