Top Banner
354

Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

Jul 31, 2018

Download

Documents

ngotruc
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 2: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 3: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

ClojureReactiveProgramming

Page 4: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

ApplicativeFunctors

Gatheringstatsaboutages

Monads

Summary

B.Bibliography

Index

Page 10: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 11: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

ClojureReactiveProgramming

Page 12: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 13: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 15: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 17: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 19: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 21: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 23: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

www.PacktPub.com

Page 24: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

Whysubscribe?FullysearchableacrosseverybookpublishedbyPacktCopyandpaste,print,andbookmarkcontentOndemandandaccessibleviaawebbrowser

Page 26: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

FreeaccessforPacktaccountholdersIfyouhaveanaccountwithPacktatwww.PacktPub.com,youcanusethistoaccessPacktLibtodayandview9entirelyfreebooks.Simplyuseyourlogincredentialsforimmediateaccess.

Page 27: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 28: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

PrefaceHighlyconcurrentapplicationssuchasuserinterfaceshavetraditionallymanagedstatethroughthemutationofglobalvariables.Variousactionsarecoordinatedviaeventhandlers,whichareproceduralinnature.

Overtime,thecomplexityofasystemincreases.Newfeaturerequestscomein,anditbecomesharderandhardertoreasonabouttheapplication.

Functionalprogrammingpresentsitselfasanextremelypowerfulallyinbuildingreliablesystemsbyeliminatingmutablestatesandallowingapplicationstobewritteninadeclarativeandcomposableway.

SuchprinciplesgaverisetoFunctionalReactiveProgrammingandCompositionalEventSystems(CES),programmingparadigmsthatareexceptionallyusefulinbuildingasynchronousandconcurrentapplications.Theyallowyoutomodelmutablestatesinafunctionalstyle.

Thisbookisdevotedtotheseideasandpresentsanumberofdifferenttoolsandtechniquestohelpmanagetheincreasingcomplexityofmodernsystems.

Page 29: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 31: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 33: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 35: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 37: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

ReaderfeedbackFeedbackfromourreadersisalwayswelcome.Letusknowwhatyouthinkaboutthisbook—whatyoulikedormayhavedisliked.Readerfeedbackisimportantforustodeveloptitlesthatyoureallygetthemostoutof.

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

Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,seeourauthorguideatwww.packtpub.com/authors.

Page 38: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 39: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

CustomersupportNowthatyouaretheproudownerofaPacktbook,wehaveanumberofthingstohelpyoutogetthemostfromyourpurchase.

Page 40: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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

Page 41: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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

Pleasecontactusat<[email protected]>withalinktothesuspectedpiratedmaterial.

Weappreciateyourhelpinprotectingourauthors,andourabilitytobringyouvaluablecontent.

Page 43: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

QuestionsYoucancontactusat<[email protected]>ifyouarehavingaproblemwithanyaspectofthisbook,andwewilldoourbesttoaddressit.

Page 44: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 45: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

thepointofthislittleexercise:eventhoughweknownothingaboutthespecificsofthisexample,weareabletodevelopanintuitionofhowitmightwork.

Let’sseewhatelseisnecessarytomakethissnippetanimateasinewaveonourscreen.

Page 47: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

Beforewemoveontothat,wemusttakealittledetourandunderstandhowwegothere.

Page 54: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 56: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

(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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

Asfamiliarasthisstyleofprogrammingmaybe,makingapplicationsreactivethiswayisn’talwaysideal.

Givenpreviousdiscussions,wenoticewestillhadtobefairlyexplicitinsettinguptherelationshipsbetweenthevariouscomponentsasevidencedbyhavingtowriteacustomhandlerandbindittobothinputfields.

Aswewillseethroughouttherestofthisbook,thereisamuchbetterwaytohandlesimilarscenarios.

Page 61: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 66: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 68: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 73: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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

Page 74: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

AsynchronousprogrammingandnetworkingGUIsareagreatexampleofasynchronousprogramming.Onceyouopenaweboradesktopapplication,itsimplysitsthere,idle,waitingforuserinput.

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

Eachofthesestimuliisassociatedwithaneventhandlerthatgetscalledwhenoneoftheseeventshappen,hencetheasynchronousnatureofGUIsystems.

Thisisastyleofprogrammingwehavebeenusedtoformanyyears,butasbusinessanduserneedsgrow,theseapplicationsgrowincomplexityaswell,andbetterabstractionsareneededtohandlethedependenciesbetweenallthecomponentsofanapplication.

AnothergreatexamplethatdealswithmanagingcomplexityaroundnetworktrafficisNetflix,whichusesCEStoprovideareactiveAPItotheirbackendservices.

Page 75: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

ComplexGUIsandanimationsGamesare,perhaps,thebestexampleofcomplexuserinterfacesastheyhaveintricaterequirementsarounduserinputandanimations.

TheElmlanguagewementionedbeforeisoneofthemostexcitingeffortsinbuildingcomplexGUIs.AnotherexampleisFlapjax,alsotargetedatwebapplications,butisprovidedasaJavaScriptlibrarythatcanbeintegratedwithexistingJavaScriptcodebases.

Page 76: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 77: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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

CESaimstobringtoReactiveProgrammingthedeclarativewayofmodelingproblemsthatisatthecoreofFunctionalProgramming.Weshouldworryaboutwhatandnotabouthow.

Innextchapters,wewilllearnhowwecanapplyCEStoourownprograms.

Page 78: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 79: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 83: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 88: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 91: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

(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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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

Youcanthinkofflatmapasmapcatforobservablesequences.

Therestofthecodeisstraightforward.Wesimplytakethefirstfiveelementsfromthisobservableandsubscribetoit,aswehavebeendoingsofar.

Page 95: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 98: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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

RxJavaembracesthisfactandprovidesarichsetofcombinatorstodealwithfailure,afewofwhichweexaminehere.

Page 99: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 103: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 107: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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

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

Finally,wefinishedbylookingaterrorhandlingandbackpressure,bothofwhichareimportantcharacteristicsofreliableapplicationsthatshouldalwaysbekeptinmind.

Page 108: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 109: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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

Wewillbuildthisapplicationfirstusingamoretraditionalapproach.Indoingso,wewill:

BeabletoidentifyandunderstandthedrawbacksofthefirstdesignLearnhowtouseRxClojuretodealwithstatefulcomputationssuchasrollingaveragesRewritetheexampleinadeclarativefashionusingobservablesequences,thusreducingthecomplexityfoundinourfirstapproach

Page 110: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 114: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

(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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

YoushouldseethatinadditiontodisplayingthecurrentsharepriceforXYZ,theprogramalsokeepstrackandrefreshestherunningaverageofthestreamofprices.

Page 117: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 118: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 120: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

wanttheprogramtodo.

Thisalreadyprovidesgreatbenefitsinmaintainabilityandreadability.However,wearestillmissingtheotherhalfofourprogram:rollingaverages.

Page 123: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

"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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

(: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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 127: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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

Theresultingprogramisshorter,simpler,andeasiertoreadonceyougetfamiliarwiththecoreconceptsofRxClojureandRxJava.

InthenextChapterwewillbeintroducedtocore.asyncinpreparationforimplementingourownbasicCESframework.

Page 128: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 129: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 133: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

(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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

—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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 138: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

(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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 144: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 147: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

BackpressureThemainmechanismbywhichcore.asyncallowsforcoordinatingbackpressureisbuffering.core.asyncdoesn’tallowunboundedbuffersasthiscanbeasourceofbugsandaresourcehog.

Instead,wearerequiredtothinkhardaboutourapplication’suniqueneedsandchooseanappropriatebufferingstrategy.

Page 148: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

blocked/parkedsinceitsbufferwasfull.

Aswithmostthings,thismightbeOKbutifwearenotwillingtoblockafastproducerjustbecausewecan’tconsumeitsitemsfastenough,wemustlookforanotheroption.

Page 150: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 153: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 158: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 160: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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

Also,notehowweareexcludingfunctionsfromtheClojurestandardAPIaswewishtousethesamenamesanddon’twantanyundesirednamingcollisions.

Thissectionsetusupwithanewprojectandthepluginsandconfigurationsneededforourframework.We’rereadytostartimplementingit.

Page 165: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

(defprotocolIToken

(dispose[tk]

"Calledwhenthesubscriberisn'tinterestedinreceivingmoreitems"))

Page 167: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

ImplementingtokensThetokentypeisthesimplestinthewholeframeworkasithasgotasinglefunctionwithastraightforwardimplementation:

(deftypeToken[ch]

IToken

(dispose[_]

(close!ch)))

Itsimplycloseswhateverchannelitisgiven,stoppingeventsfromflowingthroughsubscriptions.

Page 168: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

Wecanseefromtheprecedingoutputthatthestreamcreatedbyrange-esgetsflattenedintoes2allowingustoreceiveallvaluesbysimplysubscribingtoitonce.

ThisleavesuswithsinglefunctionfromIEventStreamlefttoimplement:

(completed?[_]@completed)

completed?simplyreturnsthecurrentvalueofthecompletedatom.Wearenowreadytoimplementbehaviors.

Page 173: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

(#+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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

Createdrespondent/target/respondent-0.1.0-SNAPSHOT.jar

Wroterespondent/pom.xml

Page 176: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 177: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

ExercisesThefollowingsectionshaveafewexercisesforyou.

Page 178: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 181: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

(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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 184: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 186: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 188: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

(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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

(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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

(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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

(defPAUSE80);;lower-caseP

Thisshouldlooksensibleasweareusingthekeystraditionallyusedforthesetypesofactions.

Page 198: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

(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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

(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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

(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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 207: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 209: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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

WealsolearnedthebasicsofReagi,aframeworkforCESsimilartotheonewecreatedinChapter4,Introductiontocore.async,butthatismorefeaturerichandrobust.

Inthenextchapter,wewilltakeabreakfromCESandseehowamoretraditionalreactiveapproachbasedondataflowscanbeuseful.

Page 210: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 211: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 216: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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

Butwhatmakesitspecial?

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

Page 217: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 220: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 222: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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

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

Thisiswhatourusersshouldbeabletodointheapplication:

DisplayalistofcontactscurrentlyinstorageDisplaythedetailsofagivencontactEditthedetailsofaspecificcontact

Andoncewe’redone,itwilllooklikethefollowing:

Page 223: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

Byusingcursors,wecaneasilypropagatechangeswithoutknowingwhattheapplicationstatelookslike,thusavoidingtheneedtoaccesstheglobalstate.

TipYoucanthinkofcursorsaszippers.Conceptually,theyserveasimilarpurposebuthavedifferentAPIs.

Page 230: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

(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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

: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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 235: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

...

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

(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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 245: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 247: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

SummaryInthischapter,wesawadifferentapproachonhowtohandlereactivewebinterfacesbyOmandReact.Inturn,theseframeworksmakethispossibleandpainlessbyapplyingfunctionalprogrammingprinciplessuchasimmutabilityandpersistentdatastructuresforefficientrendering.

WealsolearnedtothinktheOmwaybystructuringourapplicationsasaseriesoffunctions,whichreceivestateandoutputaDOMrepresentationofstatechanges.

Additionally,wesawthatbystructuringapplicationstatetransitionsthroughcore.asyncchannels,weseparatethepresentationlogicfromthecode,whichwillactuallyperformthework,makingourcomponentseveneasiertoreasonabout.

Inthenextchapter,wewillturntoanoftenoverlookedyetusefultoolforcreatingreactiveapplications:Futures.

Page 248: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 249: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

operationliketheonedescribedearlierwouldbeinitiatedwithinaspecificmoduleorfunction,andhandedovertothenextlogicalstepforfurtherprocessing.

Clojurefuturesdon’tallowustoscheduleafunctiontobeexecutedwhenthefuturefinishesinordertoperformsuchfurtherprocessing.Thisisanimportantfeatureinbuildingreactivesystems.

Page 252: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 253: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

"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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

({: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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 258: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

#(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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

(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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 264: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 268: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 270: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 272: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

Chapter9.AReactiveAPItoAmazonWebServicesThroughoutthisbook,wehavelearnedanumberoftoolsandtechniquestoaidusinbuildingreactiveapplications—futureswithimminent,ObservableswithRxClojure/RxJava,channelswithcore.async—andeveninbuildingreactiveuserinterfacesusingOmandReact.

Intheprocess,wealsobecameacquaintedwiththeconceptofFunctionalReactiveProgrammingandCompositionalEventSystems,aswellaswhatmakesthemdifferent.

Inthislastchapter,wewillbringafewofthesedifferenttoolsandconceptstogetherbydevelopinganapplicationbasedonareal-worldusecasefromaclientIworkedwithinSydney,Australia.Wewill:

DescribetheproblemofinfrastructureautomationweweretryingtosolveHaveabrieflookatsomeofAmazon’sAWSservicesBuildanAWSdashboardusingtheconceptswehavelearnedsofar

Page 273: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 275: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 277: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 279: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

CloudFormationThefirstservicewewilllookatisCloudFormation.ThismakessenseastheAPIsfoundinherewillgiveusastartingpointforfindinginformationabouttheresourcesinagivenstack.

Page 280: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 283: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

EC2ThenextservicewewilllookatisspecifictoEC2.Aswewillsee,theresponsesofthedifferentservicesaren’tasconsistentaswewouldlikethemtobe.

Page 284: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 286: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

RDSOnemightbetemptedtothinkthatgettingthestatusesofRDSinstanceswouldbejustaseasyaswithEC2.Let’sseeifthatisthecase.

Page 287: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 289: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

abstractionswelearnedaboutearlierinthisbook:Observables.

Page 292: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

(.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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

multipletimestoretrieveinformationaboutallRDSinstances.

ThisseeminglyfundamentaldifferenceinhowweusetheAPIbecomesaminortransformationinourEC2observable,whichsimplyaccumulatesallIDsintoavectorsothatwecanretrievethemallatonce.

OursimpleReactiveAPItoAmazonAWSisnowcomplete,leavinguswiththeUItocreate.

Page 300: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 303: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

ExercisesWithourpreviousapproach,theonlywaytoseenewinformationabouttheAWSresourcesisbyrefreshingthewholepage.Modifyourimplementationinsuchawaythatitqueriesthestubserviceseverysooften—say,every500milliseconds.

TipTheintervalfunctionfromRxJScanbehelpfulinsolvingthisexercise.ThinkhowyoumightuseittogetherwithourexistingstreambyreviewinghowflatMapworks.

Page 304: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 305: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 307: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

(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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

totheFunctoritself.

Thecompositionlawisasfollows:

(=(fmapa-functor(compfg))

(fmap(fmapa-functorg)f))

Thecompositionlaw,inturn,saysthatifwecomposetwoarbitraryfunctionsfandg,taketheresultingfunctionandapplythattoa-functor,thatisthesameasmappinggovertheFunctorandthenmappingfovertheresultingFunctor.

Noamountoftextwillbeabletoreplacepracticalexamples,sowewillimplementourownFunctor,whichwewillcallOption.Wewillthenrevisitthelawstoensurewehaverespectedthem.

Page 312: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

(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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 317: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 319: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 323: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

: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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 326: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 331: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

SummaryThisappendixhastakenusonabrieftouroftheworldofcategorytheory.Welearnedthreeofitsabstractions:Functors,ApplicativeFunctors,andMonads.Theyweretheguidingprinciplebehindimminent’sAPI.

Todeepenourknowledgeandunderstanding,weimplementedourownOptionMonad,acommonabstractionusedtosafelyhandletheabsenceofvalues.

Wehavealsoseenthatusingtheseabstractionsallowustomakesomeassumptionsaboutourcode,asseeninfunctionssuchasalift.Therearemanyotherfunctionswewouldnormallyrewriteoverandoveragainfordifferentpurposes,butthatcanbereusedifwerecognizeourcodefitsintooneoftheabstractionslearned.

Finally,Ihopethisencouragesreaderstoexplorecategorytheorymore,asitwillundoubtedlychangethewayyouthink.AndifIcanbesobold,Ihopethiswillalsochangethewayyoudesignlibrariesinthefuture.

Page 332: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author
Page 333: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

Observables,creating/CreatingAWSObservablesObservables,combining/CombiningtheAWSObservablesuserinterface,building/Puttingitalltogether

Page 336: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

Bbackpressure

about/Backpressuresamplecombinator/Samplestrategies/Backpressurestrategiesreferencelink/Backpressurestrategies

Bacon.jsURL/FunctionalReactiveProgrammingabout/Asynchronousdataflow

blockingIOabout/FuturesandblockingIO

bufferingabout/Backpressurefixedbuffer/Fixedbufferdroppingbuffer/Droppingbufferslidingbuffer/Slidingbuffer

Page 337: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

GGraphicalUserInterfaces(GUIs)

about/Object-orientedReactiveProgramming

Page 343: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

JJavaInterop

URL/FillingintheblanksJSPerf

URL/TheproblemwithcomplexwebUIs

Page 346: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

LLANguageforProgrammingArraysatRandom(LANPAR)

about/Themostwidelyusedreactiveprogramlein-cljsbuild

URL/ClojureorClojureScript?about/ClojureorClojureScript?

Page 347: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

NNetflix

about/Asynchronousprogrammingandnetworking

Page 349: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

PPurelyFunctionalDataStructures

about/LessonsfromfunctionalprogrammingURL/Lessonsfromfunctionalprogramming

Page 351: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

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 - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

TThreadPool

about/Fetchingdatainparalleltools.namespace

URL/Implementingeventstreamsusing/Implementingeventstreams

transducersabout/Transducersreferencelink/Transducerswithcore.async/Transducersandcore.async

transitabout/SettingupthedashboardprojectURL/Settingupthedashboardproject

TrelloURL/Intercomponentcommunication

Page 354: Clojure Reactive Programming - DropPDF1.droppdf.com/files/tAPCu/clojure-reactive-programming-by-leonardo... · Table of Contents Clojure Reactive Programming Credits About the Author

VVirtualDOM

about/Lessonsfromfunctionalprogramming