Functional Programming in JavaScript
-
Upload
khangminh22 -
Category
Documents
-
view
12 -
download
0
Transcript of Functional Programming in JavaScript
TableofContents
FunctionalProgramminginJavaScript
Credits
AbouttheAuthor
AbouttheReviewers
www.PacktPub.com
Supportfiles,eBooks,discountoffersandmore
WhySubscribe?
FreeAccessforPacktaccountholders
Preface
Whatthisbookcovers
Whatyouneedforthisbook
Whothisbookisfor
Conventions
Readerfeedback
Customersupport
Downloadingtheexamplecode
Errata
Piracy
Questions
1.ThePowersofJavaScript’sFunctionalSide–aDemonstration
Introduction
Thedemonstration
Theapplication–ane-commercewebsite
Imperativemethods
Functionalprogramming
Summary
2.FundamentalsofFunctionalProgramming
Functionalprogramminglanguages
Whatmakesalanguagefunctional?
Advantages
Cleanercode
Modularity
Reusability
Reducedcoupling
Mathematicallycorrect
Functionalprogramminginanonfunctionalworld
IsJavaScriptafunctionalprogramminglanguage?
Workingwithfunctions
Self-invokingfunctionsandclosures
Higher-orderfunctions
Purefunctions
Anonymousfunctions
Methodchains
Recursion
Divideandconquer
Lazyevaluation
Thefunctionalprogrammer’stoolkit
Callbacks
Array.prototype.map()
Array.prototype.filter()
Array.prototype.reduce()
Honorablementions
Array.prototype.forEach
Array.prototype.concat
Array.prototype.reverse
Array.prototype.sort
Array.prototype.everyandArray.prototype.some
Summary
3.SettingUptheFunctionalProgrammingEnvironment
Introduction
FunctionallibrariesforJavaScript
Underscore.js
FantasyLand
Bilby.js
Lazy.js
Bacon.js
Honorablementions
Developmentandproductionenvironments
Browsers
Server-sideJavaScript
Afunctionalusecaseintheserver-sideenvironment
CLI
UsingfunctionallibrarieswithotherJavaScriptmodules
FunctionallanguagesthatcompileintoJavaScript
Summary
4.ImplementingFunctionalProgrammingTechniquesinJavaScript
Partialfunctionapplicationandcurrying
Functionmanipulation
Apply,call,andthethiskeyword
Bindingarguments
Functionfactories
Partialapplication
Partialapplicationfromtheleft
Partialapplicationfromtheright
Currying
Functioncomposition
Compose
Sequence–composeinreverse
Compositionsversuschains
Programmingwithcompose
Mostlyfunctionalprogramming
Handlingevents
Functionalreactiveprogramming
Reactivity
Puttingitalltogether
Summary
5.CategoryTheory
Categorytheory
Categorytheoryinanutshell
Typesafety
Objectidentities
Functors
Creatingfunctors
Arraysandfunctors
Functioncompositions,revisited
Monads
Maybes
Promises
Lenses
jQueryisamonad
Implementingcategories
Summary
6.AdvancedTopicsandPitfallsinJavaScript
Recursion
Tailrecursion
TheTail-callelimination
Trampolining
TheY-combinator
Memoization
Variablescope
Scoperesolutions
Globalscope
Localscope
Objectproperties
Closures
Gotchas
Functiondeclarationsversusfunctionexpressionsversusthefunctionconstructor
Functiondeclarations
Functionexpressions
Thefunctionconstructor
Unpredictablebehavior
Summary
7.FunctionalandObject-orientedProgramminginJavaScript
JavaScript–themulti-paradigmlanguage
JavaScript’sobject-orientedimplementation–usingprototypes
Inheritance
JavaScript’sprototypechain
InheritanceinJavaScriptandtheObject.create()method
Mixingfunctionalandobject-orientedprogramminginJavaScript
Functionalinheritance
StrategyPattern
Mixins
Classicalmixins
Functionalmixins
Summary
A.CommonFunctionsforFunctionalProgramminginJavaScript
B.GlossaryofTerms
Index
FunctionalProgramminginJavaScriptCopyright©2015PacktPublishing
Allrightsreserved.Nopartofthisbookmaybereproduced,storedinaretrievalsystem,ortransmittedinanyformorbyanymeans,withoutthepriorwrittenpermissionofthepublisher,exceptinthecaseofbriefquotationsembeddedincriticalarticlesorreviews.
Everyefforthasbeenmadeinthepreparationofthisbooktoensuretheaccuracyoftheinformationpresented.However,theinformationcontainedinthisbookissoldwithoutwarranty,eitherexpressorimplied.NeithertheauthornorPacktPublishing,anditsdealersanddistributorswillbeheldliableforanydamagescausedorallegedtobecauseddirectlyorindirectlybythisbook.
PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthecompaniesandproductsmentionedinthisbookbytheappropriateuseofcapitals.However,PacktPublishingcannotguaranteetheaccuracyofthisinformation.
Firstpublished:March2015
Productionreference:1230315
PublishedbyPacktPublishingLtd.
LiveryPlace
35LiveryStreet
BirminghamB32PB,UK
ISBN978-1-78439-822-4
www.packtpub.com
CoverImagebyDanMantyla
CreditsAuthor
DanMantyla
Reviewers
DomDerrien
JoeDorocak
PeterEhrlich
EdwardE.GriebelJr.
CommissioningEditor
JulianUrsell
AcquisitionEditor
OwenRoberts
ContentDevelopmentEditor
KirtiPatil
TechnicalEditor
AbhishekR.Kotian
CopyEditors
AdityaNair
AartiSaldanha
VikrantPhadkey
ProjectCoordinator
NidhiJoshi
Proofreaders
StephenCopestake
MariaGould
PaulHindle
Indexer
TejalDaruwaleSoni
ProductionCoordinator
AparnaBhagat
CoverWork
AbouttheAuthorDanMantylaworksasawebapplicationdeveloperfortheUniversityofKansas.Heenjoyscontributingtoopensourcewebframeworksandwrenchingonmotorcycles.DaniscurrentlylivinginLawrence,Kansas,USA—thebirthplaceofPythonDjangoandhometoLinuxNewsMedia.
Danhasalsoclickedthecoverimage,whichwastakenoutsidehishomeinLawrence,Kansas,USA,wherethesunflowerfieldsareinbloomforonlyoneshortweekinSeptember.
AbouttheReviewersDomDerrienisafullstackwebdeveloperwhohasrecentlybeendefiningapplicationenvironmentswithafocusonhighavailabilityandscalability.He’sbeeninthedevelopmentfieldformorethan15yearsandhasworkedforbigandsmallcompaniesandasanentrepreneur.
He’scurrentlyworkingforthegamecompanyUbisoft,wherehedefinesthenextgenerationservicesplatformforitssuccessfulAAAgames.ToextendthegamerexperienceontotheWebandonmobiles,heprovidestechnicalmeansthataretransparent,efficient,andhighlyflexible.
HavingdevelopedsmartclientsbeforetheintroductionofXHR,usingaframesettagtokeepthecontextandahiddenframeofsize=0todynamicallyexchangedatawithservers,hehadagreatpleasureofreviewingthisbook,whichpushesthelanguagetoitslimits.Hehopesthatitwillhelpdevelopersimprovetheirprogrammingskills.
Iwanttothankmywife,Sophie,andoursons,ErwanandGoulven,withwhomIenjoyapeacefullifeinMontréal,Québec,Canada.
JoeDorocak,whoseInternetmonikerisJoeCodeswell,isaveryexperiencedprogrammer.Heenjoyscreatingreadablecodethatimplementsprojectrequirementsefficientlyandinamannerthatcanbeeasilyunderstood.Heconsiderswritingcodeakintowritingpoetry.
Joeprideshimselfontheabilitytocommunicateclearlyandprofessionally.Heconsidershiscodetobecommunication,notonlywiththemachineplatformsonwhichitruns,butalsowithhumanprogrammerswhomightreaditinthefuture.
JoehasworkedasanemployeeaswellasinacontractualroleformajorbrandssuchasIBM,HP,GTE/Sprint,andothertop-shelfcompanies.Heispresentlyconsultingonweb,mobile,anddesktopapplications,whicharecodedprimarily,butnotexclusively,inPythonandJavaScript.Formoredetailsabouthim,pleasevisithttps://www.linkedin.com/in/joedorocak.
PeterEhrlichtaughthimselfwebprogrammingin2007,andnowworksonperformanceJavaScriptandWebGLatLeapMotion,Inc.Inhissparetime,heenjoysdancing,rockclimbing,andtakingnaps.
EdwardE.GriebelJr.hasbeendevelopingenterprisesoftwareforover20yearsinC,C++,andJava.Hehasabachelorofsciencedegreeincomputerengineering.HeiscurrentlyamiddlewarearchitectataleadingpayrollandfinancialservicesproviderintheU.S.,focusingonsystemsintegrationandUIandserverdevelopment.
Supportfiles,eBooks,discountoffersandmoreForsupportfilesanddownloadsrelatedtoyourbook,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,youcanaccess,readandsearchacrossPackt’sentirelibraryofbooks.
WhySubscribe?FullysearchableacrosseverybookpublishedbyPacktCopyandpaste,printandbookmarkcontentOndemandandaccessibleviawebbrowser
FreeAccessforPacktaccountholdersIfyouhaveanaccountwithPacktatwww.PacktPub.com,youcanusethistoaccessPacktLibtodayandviewnineentirelyfreebooks.Simplyuseyourlogincredentialsforimmediateaccess.
PrefaceFunctionalprogrammingisastylethatemphasizesandenablesthewritingofsmartercode,whichminimizescomplexityandincreasesmodularity.It’sawayofwritingcleanercodethroughcleverwaysofmutating,combining,andusingfunctions.JavaScriptprovidesanexcellentmediumforthisapproach.JavaScript,theInternet’sscriptinglanguage,isactuallyafunctionallanguageatheart.Bylearninghowtoexposeitstrueidentityasafunctionallanguage,wecanimplementwebapplicationsthatarepowerful,easiertomaintain,andmorereliable.Bydoingthis,JavaScript’soddquirksandpitfallswillsuddenlybecomeclearandthelanguageasawholewillmakeinfinitelymoresense.Learninghowtousefunctionalprogrammingwillmakeyouabetterprogrammerforlife.
ThisbookisaguideforbothnewandexperiencedJavaScriptdeveloperswhoareinterestedinlearningfunctionalprogramming.Withafocusontheprogressionoffunctionalprogrammingtechniques,styles,anddetailedinformationaboutJavaScriptlibraries,thisbookwillhelpyoutowritesmartercodeandbecomeabetterprogrammer.
WhatthisbookcoversChapter1,ThePowersofJavaScript’sFunctionalSide–aDemonstration,setsthepaceofthebookbycreatingasmallwebapplicationwiththehelpofbothtraditionalmethodsandfunctionalprogramming.Itthencomparesthesetwomethodstounderlinetheimportanceoffunctionalprogramming.
Chapter2,FundamentalsofFunctionalProgramming,introducesyoutothecoreconceptsoffunctionalprogrammingaswellasbuilt-inJavaScriptfunctions.
Chapter3,SettingUptheFunctionalProgrammingEnvironment,exploresdifferentJavaScriptlibrariesandhowtheycanbeoptimizedforfunctionalprogramming.
Chapter4,ImplementingFunctionalProgrammingTechniquesinJavaScript,explainsthefunctionalparadigminJavaScript.Itcoversseveralstylesoffunctionalprogramminganddemonstrateshowtheycanbeemployedindifferentscenarios.
Chapter5,CategoryTheory,explainstheconceptofCategoryTheoryindetailandthenimplementsitinJavaScript.
Chapter6,AdvancedTopicsandPitfallsinJavaScript,highlightsvariousdrawbacksyoumayfacewhileprogramminginJavaScript,andthevariouswaystosuccessfullydealwiththem.
Chapter7,FunctionalandObject-orientedProgramminginJavaScript,relatesbothfunctionalandobject-orientedprogrammingtoJavaScript,andshowsyouhowthetwoparadigmscancomplementeachotherandcoexistsidebyside.
AppendixA,CommonFunctionsforFunctionalProgramminginJavaScript,containscommonfunctionsusedtoperformfunctionalprogramminginJavaScript.
AppendixB,GlossaryofTerms,includesaglossaryoftermsusedthroughoutthebook.
WhothisbookisforIfyouareaJavaScriptdeveloperinterestedinlearningfunctionalprogramming,lookingforaquantumleaptowardmasteringtheJavaScriptlanguage,orjustwanttobecomeabetterprogrammeringeneral,thenthisbookisidealforyou.Thisguideisaimedatprogrammersinvolvedindevelopingreactivefrontendapplications,server-sideapplicationsthatwranglewithreliabilityandconcurrency,andeverythingelseinbetween.
ConventionsInthisbook,youwillfindanumberoftextstylesthatdistinguishbetweendifferentkindsofinformation.Herearesomeexamplesofthesestylesandanexplanationoftheirmeaning.
Codewordsintext,databasetablenames,foldernames,filenames,fileextensions,pathnames,dummyURLs,userinput,andTwitterhandlesareshownasfollows:“Wecanincludeothercontextsthroughtheuseoftheincludedirective.”
Ablockofcodeissetasfollows:
Function.prototype.partialApply=function(){
varfunc=this;
args=Array.prototype.slice.call(arguments);
returnfunction(){
returnfunc.apply(this,args.concat(
Array.prototype.slice.call(arguments)
));
};
};
Whenwewishtodrawyourattentiontoaparticularpartofacodeblock,therelevantlinesoritemsaresetinbold:
varmessages=['Hi','Hello','Sup','Hey','Hola'];
messages.map(function(s,i){
returnprintSomewhere(s,i*10,i*10);
}).forEach(document.body.appendChild);
Newtermsandimportantwordsareshowninbold.Wordsthatyouseeonthescreen,forexample,inmenusordialogboxes,appearinthetextlikethis:“ClickingtheNextbuttonmovesyoutothenextscreen.”
NoteWarningsorimportantnotesappearinaboxlikethis.
TipTipsandtricksappearlikethis.
ReaderfeedbackFeedbackfromourreadersisalwayswelcome.Letusknowwhatyouthinkaboutthisbook—whatyoulikedordisliked.Readerfeedbackisimportantforusasithelpsusdeveloptitlesthatyouwillreallygetthemostoutof.
Tosendusgeneralfeedback,simplye-mail<[email protected]>,andmentionthebook’stitleinthesubjectofyourmessage.
Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,seeourauthorguideatwww.packtpub.com/authors.
CustomersupportNowthatyouaretheproudownerofaPacktbook,wehaveanumberofthingstohelpyoutogetthemostfromyourpurchase.
DownloadingtheexamplecodeYoucandownloadtheexamplecodefilesfromyouraccountathttp://www.packtpub.comforallthePacktPublishingbooksyouhavepurchased.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.
ErrataAlthoughwehavetakeneverycaretoensuretheaccuracyofourcontent,mistakesdohappen.Ifyoufindamistakeinoneofourbooks—maybeamistakeinthetextorthecode—wewouldbegratefulifyoucouldreportthistous.Bydoingso,youcansaveotherreadersfromfrustrationandhelpusimprovesubsequentversionsofthisbook.Ifyoufindanyerrata,pleasereportthembyvisitinghttp://www.packtpub.com/submit-errata,selectingyourbook,clickingontheErrataSubmissionFormlink,andenteringthedetailsofyourerrata.Onceyourerrataareverified,yoursubmissionwillbeacceptedandtheerratawillbeuploadedtoourwebsiteoraddedtoanylistofexistingerrataundertheErratasectionofthattitle.
Toviewthepreviouslysubmittederrata,gotohttps://www.packtpub.com/books/content/supportandenterthenameofthebookinthesearchfield.TherequiredinformationwillappearundertheErratasection.
PiracyPiracyofcopyrightedmaterialontheInternetisanongoingproblemacrossallmedia.AtPackt,wetaketheprotectionofourcopyrightandlicensesveryseriously.IfyoucomeacrossanyillegalcopiesofourworksinanyformontheInternet,pleaseprovideuswiththelocationaddressorwebsitenameimmediatelysothatwecanpursuearemedy.
Pleasecontactusat<[email protected]>withalinktothesuspectedpiratedmaterial.
Weappreciateyourhelpinprotectingourauthorsandourabilitytobringyouvaluablecontent.
QuestionsIfyouhaveaproblemwithanyaspectofthisbook,youcancontactusat<[email protected]>,andwewilldoourbesttoaddresstheproblem.
IntroductionFordecades,functionalprogramminghasbeenthedarlingofcomputerscienceaficionados,prizedforitsmathematicalpurityandpuzzlingnaturethatkeptithiddenindustycomputerlabsoccupiedbydatascientistsandPhDhopefuls.Butnow,itisgoingthrougharesurgence,thankstomodernlanguagessuchasPython,Julia,Ruby,Clojureand—lastbutnotleast—JavaScipt.
JavaScript,yousay?Theweb’sscriptinglanguage?Yes!
JavaScripthasproventobeanimportanttechnologythatisn’tgoingawayforquiteawhile.Thisislargelyduetothefactthatitiscapableofbeingrebornandextendedwithnewframeworksandlibraries,suchasbackbone.js,jQuery,Dojo,underscore.js,andmanymore.ThisisdirectlyrelatedtoJavaScript’strueidentityasafunctionalprogramminglanguage.AnunderstandingoffunctionalprogrammingwithJavaScriptwillbewelcomeandusefulforalongtimeforprogrammersofanyskilllevel.
Whyso?Functionalprogrammingisverypowerful,robust,andelegant.Itisusefulandefficientonlargedatastructures.ItcanbeveryadvantageoustouseJavaScript—aclient-sidescriptinglanguage,asafunctionalmeanstomanipulatetheDOM,sortAPIresponsesorperformothertasksonincreasinglycomplexwebsites.
Inthisbook,youwilllearneverythingyouneedtoknowaboutfunctionalprogrammingwithJavaScript:howtoempoweryourJavaScriptwebapplicationswithfunctionalprogramming,howtounlockJavaScript’shiddenpowers,andhowtowritebettercodethatisbothmorepowerfuland—becauseitissmaller—easiertomaintain,fastertodownload,andtakeslessoverhead.Youwillalsolearnthecoreconceptsoffunctionalprogramming,howtoapplythemtoJavaScript,howtoside-stepthecaveatsandissuesthatmayarisewhenusingJavaScriptasafunctionallanguage,andhowtomixfunctionalprogrammingwithobject-orientedprogramminginJavaScript.
Butbeforewebegin,let’sperformanexperiment.
ThedemonstrationPerhapsaquickdemonstrationwillbethebestwaytointroducefunctionalprogrammingwithJavaScript.WewillperformthesametaskusingJavaScript—onceusingtraditional,nativemethods,andoncewithfunctionalprogramming.Then,wewillcomparethetwomethods.
Theapplication–ane-commercewebsiteInpursuitofareal-worldapplication,let’ssayweneedane-commercewebapplicationforamail-ordercoffeebeancompany.Theysellseveraltypesofcoffeeandindifferentquantities,bothofwhichaffecttheprice.
ImperativemethodsFirst,let’sgowiththeproceduralroute.Tokeepthisdemonstrationdowntoearth,we’llhavetocreateobjectsthatholdthedata.Thisallowstheabilitytofetchthevaluesfromadatabaseifweneedto.Butfornow,we’llassumethey’restaticallydefined:
//createsomeobjectstostorethedata.
varcolumbian={
name:'columbian',
basePrice:5
};
varfrenchRoast={
name:'frenchroast',
basePrice:8
};
vardecaf={
name:'decaf',
basePrice:6
};
//we'lluseahelperfunctiontocalculatethecost
//accordingtothesizeandprintittoanHTMLlist
functionprintPrice(coffee,size){
if(size=='small'){
varprice=coffee.basePrice+2;
}
elseif(size=='medium'){
varprice=coffee.basePrice+4;
}
else{
varprice=coffee.basePrice+6;
}
//createthenewhtmllistitem
varnode=document.createElement("li");
varlabel=coffee.name+''+size;
vartextnode=document.createTextNode(label+'price:$'+price);
node.appendChild(textnode);
document.getElementById('products').appendChild(node);
}
//nowallweneedtodoiscalltheprintPricefunction
//foreverysinglecombinationofcoffeetypeandsize
printPrice(columbian,'small');
printPrice(columbian,'medium');
printPrice(columbian,'large');
printPrice(frenchRoast,'small');
printPrice(frenchRoast,'medium');
printPrice(frenchRoast,'large');
printPrice(decaf,'small');
printPrice(decaf,'medium');
printPrice(decaf,'large');
Tip
Downloadingtheexamplecode
YoucandownloadexamplecodefilesforallPacktbooksyouhavepurchasedfromyouraccountathttp://www.packtpub.com.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.
Asyoucansee,thiscodeisverybasic.Whatifthereweremanymorecoffeestylesthanjustthethreewehavehere?Whatiftherewere20?50?Whatif,inadditiontosize,therewereorganicandnon-organicoptions.Thatcouldincreasethelinesofcodeextremelyquickly!
Usingthismethod,wearetellingthemachinewhattoprintforeachcoffeetypeandforeachsize.Thisisfundamentallywhatiswrongwithimperativecode.
FunctionalprogrammingWhileimperativecodetellsthemachine,step-by-step,whatitneedstodotosolvetheproblem,functionalprogramminginsteadseekstodescribetheproblemmathematicallysothatthemachinecandotherest.
Withamorefunctionalapproach,thesameapplicationcanbewrittenasfollows:
//separatethedataandlogicfromtheinterface
varprintPrice=function(price,label){
varnode=document.createElement("li");
vartextnode=document.createTextNode(label+'price:$'+price);
node.appendChild(textnode);
document.getElementById('products2').appendChild(node);
}
//createfunctionobjectsforeachtypeofcoffee
varcolumbian=function(){
this.name='columbian';
this.basePrice=5;
};
varfrenchRoast=function(){
this.name='frenchroast';
this.basePrice=8;
};
vardecaf=function(){
this.name='decaf';
this.basePrice=6;
};
//createobjectliteralsforthedifferentsizes
varsmall={
getPrice:function(){returnthis.basePrice+2},
getLabel:function(){returnthis.name+'small'}
};
varmedium={
getPrice:function(){returnthis.basePrice+4},
getLabel:function(){returnthis.name+'medium'}
};
varlarge={
getPrice:function(){returnthis.basePrice+6},
getLabel:function(){returnthis.name+'large'}
};
//putallthecoffeetypesandsizesintoarrays
varcoffeeTypes=[columbian,frenchRoast,decaf];
varcoffeeSizes=[small,medium,large];
//buildnewobjectsthatarecombinationsoftheabove
//andputthemintoanewarray
varcoffees=coffeeTypes.reduce(function(previous,current){
varnewCoffee=coffeeSizes.map(function(mixin){
//`plusmix`functionforfunctionalmixins,seeCh.7
varnewCoffeeObj=plusMixin(current,mixin);
returnnewnewCoffeeObj();
});
returnprevious.concat(newCoffee);
},[]);
//we'venowdefinedhowtogetthepriceandlabelforeach
//coffeetypeandsizecombination,nowwecanjustprintthem
coffees.forEach(function(coffee){
printPrice(coffee.getPrice(),coffee.getLabel());
});
Thefirstthingthatshouldbeobviousisthatitismuchmoremodular.Thismakesaddinganewsizeoranewcoffeetypeassimpleasshowninthefollowingcodesnippet:
varperuvian=function(){
this.name='peruvian';
this.basePrice=11;
};
varextraLarge={
getPrice:function(){returnthis.basePrice+10},
getLabel:function(){returnthis.name+'extralarge'}
};
coffeeTypes.push(Peruvian);
coffeeSizes.push(extraLarge);
Arraysofcoffeeobjectsandsizeobjectsare“mixed”together,—thatis,theirmethodsandmembervariablesarecombined—withacustomfunctioncalledplusMixin(seeChapter7,FunctionalandObject-orientedProgramminginJavaScript).Thecoffeetypeclassescontainthemembervariablesandthesizescontainmethodstocalculatethenameandprice.The“mixing”happenswithinamapoperation,whichappliesapurefunctiontoeachelementinanarrayandreturnsanewfunctioninsideareduce()operation—anotherhigher-orderfunctionsimilartothemapfunction,exceptthatalltheelementsinthearrayarecombinedintoone.Finally,thenewarrayofallpossiblecombinationsoftypesandsizesisiteratedthroughwiththeforEach()methodTheforEach()methodisyetanotherhigher-orderfunctionthatappliesacallbackfunctiontoeachobjectinanarray.Inthisexample,weprovideitasananonymousfunctionthatinstantiatestheobjectsandcallstheprintPrice()functionwiththeobject’sgetPrice()andgetLabel()methodsasarguments.
Actually,wecouldmakethisexampleevenmorefunctionalbyremovingthecoffeesvariableandchainingthefunctionstogether—anotherlittletrickinfunctionalprogramming.
coffeeTypes.reduce(function(previous,current){
varnewCoffee=coffeeSizes.map(function(mixin){
//`plusMixin`functionforfunctionalmixins,seeCh.7
varnewCoffeeObj=plusMixin(current,mixin);
returnnewnewCoffeeObj();
});
returnprevious.concat(newCoffee);
},[]).forEach(function(coffee){
printPrice(coffee.getPrice(),coffee.getLabel());
});
Also,thecontrolflowisnotastop-to-bottomastheimperativecodewas.Infunctionalprogramming,themap()functionandotherhigher-orderfunctionstaketheplaceofforandwhileloopsandverylittleimportanceisplacedontheorderofexecution.Thismakesitalittletrickierfornewcomerstotheparadigmtoreadthecodebut,onceyougetthehangofit,it’snothardatalltofollowandyou’llseethatitismuchbetter.
ThisexamplebarelytouchedonwhatfunctionalprogrammingcandoinJavaScript.Throughoutthisbook,youwillseeevenmorepowerfulexamplesofthefunctionalapproach.
SummaryFirst,thebenefitsofadoptingafunctionalstyleareclear.
Second,don’tbescaredoffunctionalprogramming.Yes,itisoftenthoughtofaspurelogicintheformofcomputerlanguage,butwedon’tneedtounderstandLambdacalculustobeabletoapplyittoeverydaytasks.Thefactis,byallowingourprogramstobebrokendownintosmallerpieces,they’reeasiertounderstand,simplertomaintain,andmorereliable.map()andreduce()function’sarelesser-knownbuilt-infunctionsinJavaScript,butwe’lllookatthem.
JavaScriptisascriptinglanguage,interactiveandapproachable.Nocompilingisnecessary.Wedon’tevenneedtodownloadanydevelopmentsoftware,yourfavoritebrowserworksastheinterpreterandasthedevelopmentenvironment.
Interested?Alright,let’sgetstarted!
Chapter2.FundamentalsofFunctionalProgrammingBynow,you’veseenasmallglimpseofwhatfunctionalprogrammingcando.Butwhatexactlyisfunctionalprogramming?Whatmakesonelanguagefunctionalandnotanother?Whatmakesoneprogrammingstylefunctionalandnotanother?
Inthischapter,wewillfirstanswerthesequestionsandthencoverthecoreconceptsoffunctionalprogramming:
UsingfunctionsandarraysforcontrolflowWritingpurefunctions,anonymousfunctions,recursivefunctions,andmorePassingfunctionsaroundlikeobjectsUtilizingthemap(),filter(),andreduce()functions
FunctionalprogramminglanguagesFunctionalprogramminglanguagesarelanguagesthatfacilitatethefunctionalprogrammingparadigm.Attheriskofoversimplifying,wecouldsaythat,ifalanguageincludesthefeaturesrequiredforfunctionalprogramming,thenitisafunctionallanguage—assimpleasthat.Inmostcases,it’stheprogrammingstylethattrulydetermineswhetheraprogramisfunctionalornot.
Whatmakesalanguagefunctional?FunctionalprogrammingcannotbeperformedinC.FunctionalprogrammingcannotbeperformedinJava(withoutalotofcumbersomeworkaroundsfor“almost”functionalprogramming).Thoseandmanymorelanguagessimplydon’tcontaintheconstructstosupportit.Theyarepurelyobject-orientedandstrictlynon-functionallanguages.
Atthesametime,object-orientedprogrammingcannotbeperformedonpurelyfunctionallanguages,suchasScheme,Haskell,andLisp,justtonameafew.
However,therearecertainlanguagesthatsupportbothmodels.Pythonisafamousexample,butthereareothers:Ruby,Julia,and—here’stheonewe’reinterestedin—JavaScript.Howcantheselanguagessupporttwodesignpatternsthatareverydifferentfromeachother?Theycontainthefeaturesrequiredforbothprogrammingparadigms.However,inthecaseofJavaScript,thefunctionalfeaturesaresomewhathidden.
Butreally,it’salittlemoreinvolvedthanthat.Sowhatmakesalanguagefunctional?
Characteristic Imperative Functional
ProgrammingStyle
Performstep-by-steptasksandmanagechangesinstate
Definewhattheproblemisandwhatdatatransformationsareneededtoachievethesolution
StateChanges Important Non-existent
OrderofExecution Important Notasimportant
PrimaryFlowControl
Loops,conditionals,andfunctioncalls Functioncallsandrecursion
PrimaryManipulationUnit
Structuresandclassobjects Functionsasfirst-classobjectsanddatasets
Thesyntaxofthelanguagemustallowforcertaindesignpatterns,suchasaninferredtypesystem,andtheabilitytouseanonymousfunctions.Essentially,thelanguagemustimplementLambdacalculus.Also,theinterpreter’sevaluationstrategyshouldbenon-strictandcall-by-need(alsoknownasdeferredexecution),whichallowsforimmutabledatastructuresandnon-strict,lazyevaluation.
AdvantagesYoucouldsaythattheprofoundenlightenmentyouexperiencewhenyoufinally“getit”willmakelearningfunctionalprogrammingworthit.Anexperiencesuchasthiswillmakeyouabetterprogrammerfortherestofyourlife,whetheryouactuallybecomeafull-timefunctionalprogrammerornot.
Butwe’renottalkingaboutlearningtomeditate;we’retalkingaboutlearninganextremelyusefultoolthatwillmakeyouabetterprogrammer.
Formallyspeaking,whatexactlyarethepracticaladvantagesofusingfunctionalprogramming?
CleanercodeFunctionalprogramsarecleaner,simpler,andsmaller.Thissimplifiesdebugging,testing,andmaintenance.
Forexample,let’ssayweneedafunctionthatconvertsatwo-dimensionalarrayintoaone-dimensionalarray.Usingonlyimperativetechniques,wecouldwriteitthefollowingway:
functionmerge2dArrayIntoOne(arrays){
varcount=arrays.length;
varmerged=newArray(count);
varc=0;
for(vari=0;i<count;++i){
for(varj=0,jlen=arrays[i].length;j<jlen;++j){
merged[c++]=arrays[i][j];
}
}
returnmerged
}
Andusingfunctionaltechniques,itcouldbewrittenasfollows:
varmerge2dArrayIntoOne2=function(arrays){
returnarrays.reduce(function(p,n){
returnp.concat(n);
});
};
Bothofthesefunctionstakethesameinputandreturnthesameoutput.However,thefunctionalexampleismuchmoreconciseandclean.
ModularityFunctionalprogrammingforceslargeproblemstobebrokendownintosmallerinstancesofthesameproblemtobesolved.Thismeansthatthecodeismoremodular.Programsthataremodularareclearlyspecified,easiertodebug,andsimplertomaintain.Testingiseasierbecauseeachpieceofmodularcodecanpotentiallybecheckedforcorrectness.
Reusability
Functionalprogramsshareavarietyofcommonhelperfunctions,duetothemodularityoffunctionalprogramming.You’llfindthatmanyofthesefunctionscanbereusedforavarietyofdifferentapplications.
Manyofthemostcommonfunctionswillbecoveredlaterinthischapter.However,asyouworkasafunctionalprogrammer,youwillinevitablycompileyourownlibraryoflittlefunctionsthatcanbeusedoverandoveragain.Forexample,awell-designedfunctionthatsearchesthroughthelinesofaconfigurationfilecouldalsobeusedtosearchthroughahashtable.
ReducedcouplingCouplingistheamountofdependencybetweenmodulesinaprogram.Becausethefunctionalprogrammerworkstowritefirst-class,higher-order,purefunctionsthatarecompletelyindependentofeachotherwithnosideeffectsonglobalvariables,couplingisgreatlyreduced.Certainly,functionswillunavoidablyrelyoneachother.Butmodifyingonefunctionwillnotchangeanother,solongastheone-to-onemappingofinputstooutputsremainscorrect.
MathematicallycorrectThislastoneisonamoretheoreticallevel.ThankstoitsrootsinLambdacalculus,functionalprogramscanbemathematicallyproventobecorrect.Thisisabigadvantageforresearcherswhoneedtoprovethegrowthrate,timecomplexity,andmathematicalcorrectnessofaprogram.
Let’slookatFibonacci’ssequence.Althoughit’srarelyusedforanythingotherthanaproof-of-concept,itillustratesthisconceptquitewell.ThestandardwayofevaluatingaFibonaccisequenceistocreatearecursivefunctionthatexpressesfibonnaci(n)=fibonnaci(n-2)+fibonnaci(n–1)withabasecasetoreturn1whenn<2,whichmakesitpossibletostoptherecursionandbeginaddingupthevaluesreturnedateachstepintherecursivecallstack.
Thisdescribestheintermediarystepsinvolvedincalculatingthesequence.
varfibonacci=function(n){
if(n<2){
return1;
}
else{
returnfibonacci(n-2)+fibonacci(n-1);
}
}
console.log(fibonacci(8));
//Output:34
However,withthehelpofalibrarythatimplementsalazyexecutionstrategy,anindefinitesequencecanbegeneratedthatstatesthemathematicalequationthatdefinestheentiresequenceofnumbers.Onlyasmanynumbersasneededwillbecomputed.
varfibonacci2=Lazy.generate(function(){
varx=1,
y=1;
returnfunction(){
varprev=x;
x=y;
y+=prev;
returnprev;
};
}());
console.log(fibonacci2.length());//Output:undefined
console.log(fibonacci2.take(12).toArray());//Output:[1,1,2,3,5,8,
13,21,34,55,89,144]
varfibonacci3=Lazy.generate(function(){
varx=1,
y=1;
returnfunction(){
varprev=x;
x=y;
y+=prev;
returnprev;
};
}());
console.log(fibonacci3.take(9).reverse().first(1).toArray());//Output:
[34]
Thesecondexampleisclearlymoremathematicallysound.ItreliesontheLazy.jslibraryofJavaScript.Thereareotherlibrariesthatcanhelphereaswell,suchasSloth.jsandwu.js.ThesewillbecoveredinChapter3,SettingUptheFunctionalProgrammingEnvironment.
FunctionalprogramminginanonfunctionalworldCanfunctionalandnonfunctionalprogrammingbemixedtogether?AlthoughthisisthesubjectofChapter7,Functional&Object-orientedProgramminginJavaScript,itisimportanttogetafewthingsstraightbeforewegoanyfurther.
Thisbookisnotintendedtoteachyouhowtoimplementanentireapplicationthatstrictlyadherestotherigorsofpurefunctionalprogramming.SuchapplicationsarerarelyappropriateoutsideAcademia.Rather,thisbookwillteachyouhowtousefunctionalprogrammingdesignstrategieswithinyourapplicationstocomplementthenecessaryimperativecode.
Forexample,ifyouneedthefirstfourwordsthatonlycontainlettersoutofsometext,theycouldnaivelybewrittenlikethis:
varwords=[],count=0;
text=myString.split('');
for(i=0;count<4,i<text.length;i++){
if(!text[i].match(/[0-9]/)){
words=words.concat(text[i]);
count++;
}
}
console.log(words);
Incontrast,afunctionalprogrammermightwritethemasfollows:
varwords=[];
varwords=myString.split('').filter(function(x){
return(!x.match(/[1-9]+/));
}).slice(0,4);
console.log(words);
Or,withalibraryoffunctionalprogrammingutilities,theycanbesimplifiedevenfurther:
varwords=toSequence(myString).match(/[a-zA-Z]+/).first(4);
Thekeytoidentifyingfunctionsthatcanbewritteninamorefunctionalwayistolookforloopsandtemporaryvariables,suchaswordsandcountinstancesintheprecedingexample.Wecanusuallydoawaywithbothtemporaryvariablesandloopsbyreplacingthemwithhigher-orderfunctions,whichwewillexplorelaterinthischapter.
IsJavaScriptafunctionalprogramminglanguage?Thereisonelastquestionwemustaskourselves.IsJavaScriptafunctionallanguageoranon-functionallanguage?
JavaScriptisarguablytheworld’smostpopularandleastunderstoodfunctionalprogramminglanguage.JavaScriptisafunctionalprogramminglanguageinC-likeclothing.ItssyntaxisundeniablyC-like,meaningitusesC’sblocksyntaxandin-fixordering.Andit’soneoftheworstnamedlanguagesinexistence.Itdoesn’ttakealotofimaginationtoseehowsomanypeoplecanconfuseJavaScriptasbeingrelatedtoJava;somehow,itsnameimpliesthatitshouldbe!Butinrealityithasverylittleincommon
withJava.And,toreallycementtheideathatJavaScriptisanobject-orientedlanguage,librariesandframeworkssuchasDojoandease.jshavebeenhardatworkattemptingtoabstractitandmakeitsuitableforobject-orientedprogramming.JavaScriptcameofageinthe1990swhenOOPwasallthebuzz,andwe’vebeentoldthatJavaScriptisobject-orientedbecausewewantittobesobadly.Butitisnot.
Itstrueidentityismuchmorealignedwithitsancestors:SchemeandLisp,twoclassicfunctionallanguages.JavaScriptisafunctionallanguage,alltheway.Itsfunctionsarefirst-classandcanbenested,ithasclosuresandcompositions,anditallowsforcurryingandmonads.Allofthesearekeytofunctionalprogramming.HereareafewmorereasonswhyJavaScriptisafunctionallanguage:
JavaScript’slexicalgrammarincludestheabilitytopassfunctionsasarguments,hasaninferredtypesystem,andallowsforanonymousfunctions,higher-orderfunctions,closuresandmore.Thesefactsareparamounttoachievingthestructureandbehavioroffunctionalprogramming.Itisnotapureobject-orientedlanguage,withmostobject-orienteddesignpatternsachievedbycopyingthePrototypeobject,aweakmodelforobject-orientedprogramming.EuropeanComputerManufacturersAssociationScript(ECMAScript),JavaScript’sformalandstandardizedspecificationsforimplementation,statesthefollowinginspecification4.2.1:
“ECMAScriptdoesnotcontainproperclassessuchasthoseinC++,Smalltalk,orJava,butrather,supportsconstructorswhichcreateobjects.Inaclass-basedobject-orientedlanguage,ingeneral,stateiscarriedbyinstances,methodsarecarriedbyclasses,andinheritanceisonlyofstructureandbehavior.InECMAScript,thestateandmethodsarecarriedbyobjects,andstructure,behaviorandstateareallinherited.”
Itisaninterpretedlanguage.Sometimescalled“engines”,JavaScriptinterpretersoftencloselyresembleSchemeinterpreters.Botharedynamic,bothhaveflexibledatatypesthateasilycombineandtransform,bothevaluatethecodeintoblocksofexpressions,andbothtreatfunctionssimilarly.
Thatbeingsaid,itistruethatJavaScriptisnotapurefunctionallanguage.What’slackingislazyevaluationandbuilt-inimmutabledata.Thisisbecausemostinterpretersarecall-by-nameandnotcall-by-need.JavaScriptalsoisn’tverygoodwithrecursionduetothewayithandlestailcalls.However,alloftheseissuescanbemitigatedwithalittlebitofattention.Non-strictevaluation,requiredforinfinitesequencesandlazyevaluation,canbeachievedwithalibrarycalledLazy.js.Immutabledatacanbeachievedsimplybyprogrammingtechnique,butthisrequiresmoreprogrammerdisciplineratherthanrelyingonthelanguagetotakecareofit.AndrecursivetailcalleliminationcanbeachievedwithamethodcalledTrampolining.TheseissueswillbeaddressedinChapter6,AdvancedTopics&PitfallsinJavaScript.
ManydebateshavebeenwagedoverwhetherornotJavaScriptisafunctionallanguage,anobject-orientedlanguage,both,orneither.Andthiswon’tbethelastdebate.
Intheend,functionalprogrammingiswayofwritingcleanercodethroughcleverwaysofmutating,combining,andusingfunctions.AndJavaScriptprovidesanexcellentmediumforthisapproach.IfyoureallywanttouseJavaScripttoitsfullpotential,youmustlearnhowtouseitasafunctionallanguage.
Workingwithfunctions Sometimes,theelegantimplementationisafunction.Notamethod.Notaclass.Notaframework.Justafunction.
—JohnCarmack,leadprogrammeroftheDoomvideogame
Functionalprogrammingisallaboutdecomposingaproblemintoasetoffunctions.Often,functionsarechainedtogether,nestedwithineachother,passedaround,andtreatedasfirst-classcitizens.Ifyou’veusedframeworkssuchasjQueryandNode.js,you’veprobablyusedsomeofthesetechniques,youjustdidn’trealizeit!
Let’sstartwithalittleJavaScriptdilemma.
Sayweneedtocompilealistofvaluesthatareassignedtogenericobjects.Theobjectscouldbeanything:dates,HTMLobjects,andsoon.
var
obj1={value:1},
obj2={value:2},
obj3={value:3};
varvalues=[];
functionaccumulate(obj){
values.push(obj.value);
}
accumulate(obj1);
accumulate(obj2);
console.log(values);//Output:[obj1.value,obj2.value]
Itworksbutit’svolatile.Anycodecanmodifythevaluesobjectwithoutcallingtheaccumulate()function.Andifweforgettoassigntheemptyset,[],tothevaluesinstancethenthecodewillnotworkatall.
Butifthevariableisdeclaredinsidethefunction,itcan’tbemutatedbyanyroguelinesofcode.
functionaccumulate2(obj){
varvalues=[];
values.push(obj.value);
returnvalues;
}
console.log(accumulate2(obj1));//Returns:[obj1.value]
console.log(accumulate2(obj2));//Returns:[obj2.value]
console.log(accumulate2(obj3));//Returns:[obj3.value]
Itdoesnotwork!Onlythevalueoftheobjectlastpassedinisreturned.
Wecouldpossiblysolvethiswithanestedfunctioninsidethefirstfunction.
varValueAccumulator=function(obj){
varvalues=[]
varaccumulate=function(){
values.push(obj.value);
};
accumulate();
returnvalues;
};
Butit’sthesameissue,andnowwecannotreachtheaccumulatefunctionorthevaluesvariable.
Whatweneedisaself-invokingfunction.
Self-invokingfunctionsandclosuresWhatifwecouldreturnafunctionexpressionthatin-turnreturnsthevaluesarray?Variablesdeclaredinafunctionareavailabletoanycodewithinthefunction,includingself-invokingfunctions.
Byusingaself-invokingfunction,ourdilemmaissolved.
varValueAccumulator=function(){
varvalues=[];
varaccumulate=function(obj){
if(obj){
values.push(obj.value);
returnvalues;
}
else{
returnvalues;
}
};
returnaccumulate;
};
//Thisallowsustodothis:
varaccumulator=ValueAccumulator();
accumulator(obj1);
accumulator(obj2);
console.log(accumulator());
//Output:[obj1.value,obj2.value]
It’sallaboutvariablescoping.Thevaluesvariableisavailabletotheinneraccumulate()function,evenwhencodeoutsidethescopecallsthefunctions.Thisiscalledaclosure.
NoteClosuresinJavaScriptarefunctionsthathaveaccesstotheparentscope,evenwhentheparentfunctionhasclosed.
Closuresareafeatureofallfunctionallanguages.Traditionalimperativelanguagesdonotallowthem.
Higher-orderfunctionsSelf-invokingfunctionsareactuallyaformofhigher-orderfunctions.Higher-orderfunctionsarefunctionsthateithertakeanotherfunctionastheinputorreturnafunctionastheoutput.
Higher-orderfunctionsarenotcommonintraditionalprogramming.Whileanimperativeprogrammermightusealooptoiterateanarray,afunctionalprogrammerwouldtakeanotherapproachentirely.Byusingahigher-orderfunction,thearraycanbeworkedonbyapplyingthatfunctiontoeachiteminthearraytocreateanewarray.
Thisisthecentralideaofthefunctionalprogrammingparadigm.Whathigher-orderfunctionsallowistheabilitytopasslogictootherfunctions,justlikeobjects.
Functionsaretreatedasfirst-classcitizensinJavaScript,adistinctionJavaScriptshareswithScheme,Haskell,andtheotherclassicfunctionallanguages.Thismaysoundbizarre,butallthisreallymeansisthatfunctionsaretreatedasprimitives,justlikenumbersandobjects.Ifnumbersandobjectscanbepassedaround,socanfunctions.
Toseethisinaction,let’suseahigher-orderfunctionwithourValueAccumulator()functionfromtheprevioussection:
//usingforEach()toiteratethroughanarrayandcalla
//callbackfunction,accumulator,foreachitem
varaccumulator2=ValueAccumulator();
varobjects=[obj1,obj2,obj3];//couldbehugearrayofobjects
objects.forEach(accumulator2);
console.log(accumulator2());
PurefunctionsPurefunctionsreturnavaluecomputedusingonlytheinputspassedtoit.Outsidevariablesandglobalstatesmaynotbeusedandtheremaybenosideeffects.Inotherwords,itmustnotmutatethevariablespassedtoitforinput.Therefore,purefunctionsareonlyusedfortheirreturnedvalue.
Asimpleexampleofthisisamathfunction.TheMath.sqrt(4)functionwillalwaysreturn2,doesnotuseanyhiddeninformationsuchassettingsorstate,andwillneverinflictanysideeffects.
Purefunctionsarethetrueinterpretationofthemathematicaltermfor‘function’,arelationbetweeninputsandanoutput.Theyaresimpletothinkaboutandarereadilyre-usable.Becausetheyaretotallyindependent,purefunctionsaremorecapableofbeingusedagainandagain.
Toillustratethis,comparethefollowingnon-purefunctiontothepureone.
//functionthatprintsamessagetothecenterofthescreen
varprintCenter=function(str){
varelem=document.createElement("div");
elem.textContent=str;
elem.style.position='absolute';
elem.style.top=window.innerHeight/2+"px";
elem.style.left=window.innerWidth/2+"px";
document.body.appendChild(elem);
};
printCenter('helloworld');
//purefunctionthataccomplishesthesamething
varprintSomewhere=function(str,height,width){
varelem=document.createElement("div");
elem.textContent=str;
elem.style.position='absolute';
elem.style.top=height;
elem.style.left=width;
returnelem;
};
document.body.appendChild(printSomewhere('helloworld',
window.innerHeight/2)+10+"px",window.innerWidth/2)+10+"px")
);
Whilethenon-purefunctionreliesonthestateofthewindowobjecttocomputetheheightandwidth,thepure,self-sufficientfunctioninsteadasksthatthosevaluesbepassedin.Whatthisactuallydoesisallowthemessagetobeprintedanywhere,andthismakesthefunctionmuchmoreversatile.
Andwhilethenon-purefunctionmayseemliketheeasieroptionbecauseitperformstheappendingitselfinsteadofreturninganelement,thepurefunctionprintSomewhere()anditsreturnedvalueplaybetterwithotherfunctionalprogrammingdesigntechniques.
varmessages=['Hi','Hello','Sup','Hey','Hola'];
messages.map(function(s,i){
returnprintSomewhere(s,100*i*10,100*i*10);
}).forEach(function(element){
document.body.appendChild(element);
});
NoteWhenthefunctionsarepureanddon’trelyonstateorenvironment,thenwedon’tcareaboutwhenorwheretheyactuallygetcomputed.We’llseethislaterwithlazyevaluation.
AnonymousfunctionsAnotherbenefitoftreatingfunctionsasfirst-classobjectsistheadventofanonymousfunctions.
Asthenamemightimply,anonymousfunctionsarefunctionswithoutnames.Buttheyaremorethanthat.Whattheyallowistheabilitytodefinead-hoclogic,on-the-spotandasneeded.Usually,it’sforthebenefitofconvenience;ifthefunctionisonlyreferredtoonce,thenavariablenamedoesn’tneedtobewastedonit.
Someexamplesofanonymousfunctionsareasfollows:
//Thestandardwaytowriteanonymousfunctions
function(){return"helloworld"};
//Anonymousfunctionassignedtovariable
varanon=function(x,y){returnx+y};
//Anonymousfunctionusedinplaceofanamedcallbackfunction,
//thisisoneofthemorecommonusesofanonymousfunctions.
setInterval(function(){console.log(newDate().getTime())},1000);
//Output:1413249010672,1413249010673,1413249010674,...
//Withoutwrappingitinananonymousfunction,itimmediately//execute
onceandthenreturnundefinedasthecallback:
setInterval(console.log(newDate().getTime()),1000)
//Output:1413249010671
Amoreinvolvedexampleofanonymousfunctionsusedwithinhigher-orderfunctions:
functionpowersOf(x){
returnfunction(y){
//thisisananonymousfunction!
returnMath.pow(x,y);
};
}
powerOfTwo=powersOf(2);
console.log(powerOfTwo(1));//2
console.log(powerOfTwo(2));//4
console.log(powerOfTwo(3));//8
powerOfThree=powersOf(3);
console.log(powerOfThree(3));//9
console.log(powerOfThree(10));//59049
Thefunctionthatisreturneddoesn’tneedtobenamed;itcan’tbeusedanywhereoutsidethepowersOf()function,andsoitisananonymousfunction.
Rememberouraccumulatorfunction?Itcanbere-writtenusinganonymousfunctions.
var
obj1={value:1},
obj2={value:2},
obj3={value:3};
varvalues=(function(){
//anonymousfunction
varvalues=[];
returnfunction(obj){
//anotheranonymousfunction!
if(obj){
values.push(obj.value);
returnvalues;
}
else{
returnvalues;
}
}
})();//makeitself-executing
console.log(values(obj1));//Returns:[obj.value]
console.log(values(obj2));//Returns:[obj.value,obj2.value]
Righton!Apure,high-order,anonymousfunction.Howdidweevergetsolucky?Actually,it’smorethanthat.It’salsoself-executingasindicatedbythestructure,(function(){...})();.Thepairofparenthesesfollowingtheanonymousfunctioncausesthefunctiontobecalledrightaway.Intheaboveexample,thevaluesinstanceisassignedtotheoutputoftheself-executingfunctioncall.
NoteAnonymousfunctionsaremorethanjustsyntacticalsugar.TheyaretheembodimentofLambdacalculus.Staywithmeonthis…Lambdacalculuswasinventedlongbeforecomputersorcomputerlanguages.Itwasjustamathematicalnotionforreasoningaboutfunctions.Remarkably,itwasdiscoveredthat—despitethefactthatitonlydefinesthreekindsofexpressions:variablereferences,functioncalls,andanonymousfunctions—itwasTuring-complete.Today,Lambdacalculusliesatthecoreofallfunctionallanguagesifyouknowhowtofindit,includingJavaScript.
Forthisreason,anonymousfunctionsareoftencalledlambdaexpressions.
Onedrawbacktoanonymousfunctionsremains.They’redifficulttoidentifyincallstacks,whichmakesdebuggingtrickier.Theyshouldbeusedsparingly.
MethodchainsChainingmethodstogetherinJavaScriptisquitcommon.Ifyou’veusedjQuery,you’velikelyperformedthistechnique.It’ssometimescalledthe“BuilderPattern”.
It’satechniquethatisusedtosimplifycodewheremultiplefunctionsareappliedtoanobjectoneafteranother.
//Insteadofapplyingthefunctionsoneperline…
arr=[1,2,3,4];
arr1=arr.reverse();
arr2=arr1.concat([5,6]);
arr3=arr2.map(Math.sqrt);
//...theycanbechainedtogetherintoaone-liner
console.log([1,2,3,4].reverse().concat([5,6]).map(Math.sqrt));
//parenthesesmaybeusedtoillustrate
console.log(((([1,2,3,4]).reverse()).concat([5,6])).map(Math.sqrt));
Thisonlyworkswhenthefunctionsaremethodsoftheobjectbeingworkedon.Ifyoucreatedyourownfunctionthat,forexample,takestwoarraysandreturnsanarraywiththetwoarrayszippedtogether,youmustdeclareitasamemberoftheArray.prototypeobject.Takealookatthefollowingcodesnippet:
Array.prototype.zip=function(arr2){
//...
}
Thiswouldallowustothefollowing:
arr.zip([11,12,13,14).map(function(n){returnn*2});
//Output:2,22,4,24,6,26,8,28
RecursionRecursionislikelythemostfamousfunctionalprogrammingtechnique.Ifyoudon’tknowbynow,arecursivefunctionisafunctionthatcallsitself.
Whenafunctionscallsitself,somethingstrangehappens.Itactsbothasaloop,inthatitexecutesthesamecodemultipletimes,andasafunctionstack.
Recursivefunctionsmustbeverycarefultoavoidaninfiniteloop(rather,infiniterecursioninthiscase).Sojustlikeloops,aconditionmustbeusedtoknowwhentostop.Thisiscalledthebasecase.
Anexampleisasfollows:
varfoo=function(n){
if(n<0){
//basecase
return'hello';
}
else{
//recursivecase
foo(n-1);
}
}
console.log(foo(5));
It’spossibletoconvertanylooptoarecursivealgorithmandanyrecursivealgorithmtoaloop.Butrecursivealgorithmsaremoreappropriate,almostnecessary,forsituationsthatdiffergreatlyfromthosewhereloopsareappropriate.
Agoodexampleistreetraversal.Whileit’snottoohardtotraverseatreeusingarecursivefunction,aloopwouldbemuchmorecomplexandwouldneedtomaintainastack.Andthatwouldgoagainstthespiritoffunctionalprogramming.
vargetLeafs=function(node){
if(node.childNodes.length==0){
//basecase
returnnode.innerText;
}
else{
//recursivecase:
returnnode.childNodes.map(getLeafs);
}
}
DivideandconquerRecursionismorethananinterestingwaytoiteratewithoutforandwhileloops.Analgorithmdesign,knownasdivideandconquer,recursivelybreaksproblemsdownintosmallerinstancesofthesameproblemuntilthey’resmallenoughtosolve.
ThehistoricalexampleofthisistheEuclidanalgorithmforfindingthegreatestcommondenominatorfortwonumbers.
functiongcd(a,b){
if(b==0){
//basecase(conquer)
returna;
}
else{
//recursivecase(divide)
returngcd(b,a%b);
}
}
console.log(gcd(12,8));
console.log(gcd(100,20));
Sointheory,divideandconquerworksquiteeloquently,butdoesithaveanyuseintherealworld?Yes!TheJavaScriptfunctionforsortingarraysisnotverygood.Notonlydoesitsortthearrayinplace,whichmeansthatthedataisnotimmutable,butitisunreliableandinflexible.Withdivideandconquer,wecandobetter.
Themergesortalgorithmusesthedivideandconquerrecursivealgorithmdesigntoefficientlysortanarraybyrecursivelydividingthearrayintosmallersub-arraysandthenmergingthemtogether.
ThefullimplementationinJavaScriptisabout40linesofcode.However,pseudo-codeisasfollows:
varmergeSort=function(arr){
if(arr.length<2){
//basecase:0or1itemarraysdon'tneedsorting
returnitems;
}
else{
//recursivecase:dividethearray,sort,thenmerge
varmiddle=Math.floor(arr.length/2);
//divide
varleft=mergeSort(arr.slice(0,middle));
varright=mergeSort(arr.slice(middle));
//conquer
//mergeisahelperfunctionthatreturnsanewarray
//ofthetwoarraysmergedtogether
returnmerge(left,right);
}
}
LazyevaluationLazyevaluation,alsoknownasnon-strictevaluation,call-by-needanddefferedexecution,isanevaluationstrategythatwaitsuntilthevalueisneededtocomputetheresultofafunctionandisparticularlyusefulforfunctionalprogramming.It’sclearthatalineofcodethatstatesx=func()iscallingforxtobeassignedtothereturnedvaluebyfunc().Butwhatxactuallyequatestodoesnotmatteruntilitisneeded.Waitingtocallfunc()untilxisneededisknownaslazyevaluation.
Thisstrategycanresultinamajorincreaseinperformance,especiallywhenusedwithmethodchainsandarrays,thefavoriteprogramflowtechniquesofthefunctionalprogrammer.
Oneexcitingbenefitoflazyevaluationistheexistenceofinfiniteseries.Becausenothingisactuallycomputeduntilitcan’tbedelayedanyfurther,it’spossibletodothis:
//wishfulJavaScriptpseudocode:
varinfinateNums=range(1toinfinity);
vartenPrimes=infinateNums.getPrimeNumbers().first(10);
Thisopensthedoorformanypossibilities:asynchronousexecution,parallelization,andcomposition,justtonameafew.
However,there’soneproblem:JavaScriptdoesnotperformLazyevaluationonitsown.Thatbeingsaid,thereexistlibrariesforJavaScriptthatsimulatelazyevaluationverywell.ThatisthesubjectofChapter3,SettingUptheFunctionalProgrammingEnvironment.
Thefunctionalprogrammer’stoolkitIfyou’velookedcloselyatthefewexamplespresentedsofar,you’llnoticeafewmethodsbeingusedthatyoumaynotbefamiliarwith.Theyarethemap(),filter(),andreduce()functions,andtheyarecrucialtoeveryfunctionalprogramofanylanguage.Theyenableyoutoremoveloopsandstatements,resultingincleanercode.
Themap(),filter(),andreduce()functionsmakeupthecoreofthefunctionalprogrammer’stoolkit,acollectionofpure,higher-orderfunctionsthataretheworkhorsesofthefunctionalmethod.Infact,they’retheepitomeofwhatapurefunctionandwhatahigher-orderfunctionshouldbelike;theytakeafunctionasinputandreturnanoutputwithzerosideeffects.
Whilethey’restandardforbrowsersthatimplementECMAScript5.1,theyonlyworkonarrays.Eachtimeit’scalled,anewarrayiscreatedandreturned.Theexistingarrayisnotmodified.Butthere’smore,theytakefunctionsasinputs,oftenintheformofanonymousfunctionsreferredtoascallbackfunctions;theyiterateoverthearrayandapplythefunctiontoeachiteminthearray!
myArray=[1,2,3,4];
newArray=myArray.map(function(x){returnx*2});
console.log(myArray);//Output:[1,2,3,4]
console.log(newArray);//Output:[2,4,6,8]
Onemorething.Becausetheyonlyworkonarrays,theydonotworkonotheriterabledatastructures,likecertainobjects.Fretnot,librariessuchasunderscore.js,Lazy.js,stream.js,andmanymoreallimplementtheirownmap(),filter(),andreduce()methodsthataremoreversatile.
CallbacksIfyou’veneverworkedwithcallbacksbefore,youmightfindtheconceptalittlepuzzling.ThisisespeciallytrueinJavaScript,giventheseveraldifferentwaysthatJavaScriptallowsyoutodeclarefunctions.
Acallback()functionisusedforpassingtootherfunctionsforthemtouse.It’sawaytopasslogicjustasyouwouldpassanobject:
varmyArray=[1,2,3];
functionmyCallback(x){returnx+1};
console.log(myArray.map(myCallback));
Tomakeitsimplerforeasytasks,anonymousfunctionscanbeused:
console.log(myArray.map(function(x){returnx+1}));
Theyarenotonlyusedinfunctionalprogramming,theyareusedformanythingsinJavaScript.Purelyforexample,here’sacallback()functionusedinanAJAXcallmadewithjQuery:
functionmyCallback(xhr){
console.log(xhr.status);
returntrue;
}
$.ajax(myURI).done(myCallback);
Noticethatonlythenameofthefunctionwasused.Andbecausewe’renotcallingthecallbackandareonlypassingthenameofit,itwouldbewrongtowritethis:
$.ajax(myURI).fail(myCallback(xhr));
//or
$.ajax(myURI).fail(myCallback());
Whatwouldhappenifwedidcallthecallback?Inthatcase,themyCallback(xhr)methodwouldtrytoexecute—‘undefined’wouldbeprintedtotheconsoleanditwouldreturnTrue.Whentheajax()callcompletes,itwillhave‘true’asthenameofthecallbackfunctiontouse,andthatwillthrowanerror.
Whatthisalsomeansisthatwecannotspecifywhatargumentsarepassedtothecallbackfunctions.Ifweneeddifferentparametersfromwhattheajax()callwillpasstoit,wecanwrapthecallbackfunctioninananonymousfunction:
functionmyCallback(status){
console.log(status);
returntrue;
}
$.ajax(myURI).done(function(xhr){myCallback(xhr.status)});
Array.prototype.map()Themap()functionistheringleaderofthebunch.Itsimplyappliesthecallbackfunctiononeachiteminthearray.
NoteSyntax:arr.map(callback[,thisArg]);
Parameters:
callback():Thisfunctionproducesanelementforthenewarray,receivingthesearguments:
currentValue:Thisargumentgivesthecurrentelementbeingprocessedinthearrayindex:Thisargumentgivestheindexofthecurrentelementinthearrayarray:Thisargumentgivesthearraybeingprocessed
thisArg():Thisfunctionisoptional.Thevalueisusedasthiswhenexecutingcallback.
Examples:
var
integers=[1,-0,9,-8,3],
numbers=[1,2,3,4],
str='helloworldhowyadoing?';
//mapintegerstotheirabsolutevalues
console.log(integers.map(Math.abs));
//multiplyanarrayofnumbersbytheirpositioninthearray
console.log(numbers.map(function(x,i){returnx*i}));
//Capitalizeeveryotherwordinastring.
console.log(str.split('').map(function(s,i){
if(i%2==0){
returns.toUpperCase();
}
else{
returns;
}
}));
NoteWhiletheArray.prototype.mapmethodisastandardmethodfortheArrayobjectinJavaScript,itcanbeeasilyextendedtoyourcustomobjectsaswell.
MyObject.prototype.map=function(f){
returnnewMyObject(f(this.value));
};
Array.prototype.filter()Thefilter()functionisusedtotakeelementsoutofanarray.ThecallbackmustreturnTrue(toincludetheiteminthenewarray)orFalse(todropit).Somethingsimilarcouldbeachievedbyusingthemap()functionandreturninganullvalueforitemsyouwantdropped,butthefilter()functionwilldeletetheitemfromthenewarrayinsteadofinsertinganullvalueinitsplace.
NoteSyntax:arr.filter(callback[,thisArg]);
Parameters:
callback():Thisfunctionisusedtotesteachelementinthearray.ReturnTruetokeeptheelement,Falseotherwise.Withtheseparameters:
currentValue:Thisparametergivesthecurrentelementbeingprocessedinthearrayindex:Thisparametergivestheindexofthecurrentelementinthearray
array:Thisparametergivesthearraybeingprocessed.thisArg():Thisfunctionisoptional.Valueisusedasthiswhenexecutingcallback.
Examples:
varmyarray=[1,2,3,4]
words='hello123worldhow345yadoing'.split('');
re='[a-zA-Z]';
//removeallnegativenumbers
console.log([-2,-1,0,1,2].filter(function(x){returnx>0}));
//removenullvaluesafteramapoperation
console.log(words.filter(function(s){
returns.match(re);
}));
//removerandomobjectsfromanarray
console.log(myarray.filter(function(){
returnMath.floor(Math.random()*2)})
);
Array.prototype.reduce()Sometimescalledfold,thereduce()functionisusedtoaccumulateallthevaluesofthearrayintoone.Thecallbackneedstoreturnthelogictobeperformedtocombinetheobjects.Inthecaseofnumbers,they’reusuallyaddedtogethertogetasumormultipliedtogethertogetaproduct.Inthecaseofstrings,thestringsareoftenappendedtogether.
NoteSyntax:arr.reduce(callback[,initialValue]);
Parameters:
callback():Thisfunctioncombinestwoobjectsintoone,whichisreturned.Withtheseparameters:
previousValue:Thisparametergivesthevaluepreviouslyreturnedfromthelastinvocationofthecallback,ortheinitialValue,ifsuppliedcurrentValue:Thisparametergivesthecurrentelementbeingprocessedinthearrayindex:Thisparametergivestheindexofthecurrentelementinthearrayarray:Thisparametergivesthearraybeingprocessed
initialValue():Thisfunctionisoptional.Objecttouseasthefirstargumenttothefirstcallofthecallback.
Examples:
varnumbers=[1,2,3,4];
//sumupallthevaluesofanarray
console.log([1,2,3,4,5].reduce(function(x,y){returnx+y},0));
//sumupallthevaluesofanarray
console.log([1,2,3,4,5].reduce(function(x,y){returnx+y},0));
//findthelargestnumber
console.log(numbers.reduce(function(a,b){
returnMath.max(a,b)})//maxtakestwoarguments
);
HonorablementionsThemap(),filter(),andreduce()functionsarenotaloneinourtoolboxofhelperfunctions.Thereexistmanymorefunctionsthatcanbepluggedintonearlyanyfunctionalapplication.
Array.prototype.forEachEssentiallythenon-pureversionofmap(),forEach()iteratesoveranarrayandappliesacallback()functionovereachitem.However,itdoesn’treturnanything.It’sacleanerwayofperformingaforloop.
NoteSyntax:arr.forEach(callback[,thisArg]);
Parameters:
callback():Thisfunctionistobeperformedforeachvalueofthearray.Withtheseparameters:
currentValue:Thisparametergivesthecurrentelementbeingprocessedinthearrayindex:Thisparametergivestheindexofthecurrentelementinthearrayarray:Thisparametergivesthearraybeingprocessed
thisArg:Thisfunctionisoptional.Valueisusedasthiswhenexecutingcallback.
Examples:
vararr=[1,2,3];
varnodes=arr.map(function(x){
varelem=document.createElement("div");
elem.textContent=x;
returnelem;
});
//logthevalueofeachitem
arr.forEach(function(x){console.log(x)});
//appendnodestotheDOM
nodes.forEach(function(x){document.body.appendChild(x)});
Array.prototype.concatWhenworkingwitharraysinsteadofforandwhileloops,oftenyouwillneedtojoinmultiplearraystogether.Anotherbuilt-inJavaScriptfunction,concat(),takescareofthisforus.Theconcat()functionreturnsanewarrayandleavestheoldarraysuntouched.Itcanjoinasmanyarraysasyoupasstoit.
console.log([1,2,3].concat(['a','b','c'])//concatenatetwoarrays);
//Output:[1,2,3,'a','b','c']
Theoriginalarrayisuntouched.Itreturnsanewarraywithbotharraysconcatenated
together.Thisalsomeansthattheconcat()functioncanbechainedtogether.
vararr1=[1,2,3];
vararr2=[4,5,6];
vararr3=[7,8,9];
varx=arr1.concat(arr2,arr3);
vary=arr1.concat(arr2).concat(arr3));
varz=arr1.concat(arr2.concat(arr3)));
console.log(x);
console.log(y);
console.log(z);
Variablesx,yandzallcontain[1,2,3,4,5,6,7,8,9].
Array.prototype.reverseAnothernativeJavaScriptfunctionhelpswitharraytransformations.Thereverse()functioninvertsanarray,suchthatthefirstelementisnowthelastandthelastisnowthefirst.
However,itdoesnotreturnanewarray;insteaditmutatesthearrayinplace.Wecandobetter.Here’sanimplementationofapuremethodforreversinganarray:
varinvert=function(arr){
returnarr.map(function(x,i,a){
returna[a.length-(i+1)];
});
};
varq=invert([1,2,3,4]);
console.log(q);
Array.prototype.sortMuchlikeourmap(),filter(),andreduce()methods,thesort()methodtakesacallback()functionthatdefineshowtheobjectswithinanarrayshouldbesorted.But,likethereverse()function,itmutatesthearrayinplace.Andthat’snobueno.
arr=[200,12,56,7,344];
console.log(arr.sort(function(a,b){returna–b}));
//arrisnow:[7,12,56,200,344];
Wecouldwriteapuresort()functionthatdoesn’tmutatethearray,butsortingalgorithmsisthesourceofmuchgrief.Significantlylargearraysthatneedtobesortedreallyshouldbeorganizedindatastructuresthataredesignedjustforthat:quickStort,mergeSort,bubbleSort,andsoon.
Array.prototype.everyandArray.prototype.someTheArray.prototype.every()andArray.prototype.some()functionsarebothpureandhigh-orderfunctionsthataremethodsoftheArrayobjectandareusedtotesttheelementsofanarrayagainstacallback()functionthatmustreturnaBooleanrepresentingtherespectiveinput.Theevery()functionreturnsTrueifthecallback()functionreturnsTrueforeveryelementinthearray,andthesome()functionreturnsTrueifsomeelementsinthearrayareTrue.
Example:
functionisNumber(n){
return!isNaN(parseFloat(n))&&isFinite(n);
}
console.log([1,2,3,4].every(isNumber));//Return:true
console.log([1,2,'a'].every(isNumber));//Return:false
console.log([1,2,'a'].some(isNumber));//Return:true
SummaryInordertodevelopanunderstandingoffunctionalprogramming,thischaptercoveredafairlybroadrangeoftopics.Firstweanalyzedwhatitmeansforaprogramminglanguagetobefunctional,thenweevaluatedJavaScriptforitsfunctionalprogrammingcapabilities.Next,weappliedthecoreconceptsoffunctionalprogrammingusingJavaScriptandshowcasedsomeofJavaScript’sbuilt-infunctionsforfunctionalprogramming.
AlthoughJavaScriptdoeshaveafewtoolsforfunctionalprogramming,itsfunctionalcoreremainsmostlyhiddenandmuchistobedesired.Inthenextchapter,wewillexploreseverallibrariesforJavaScriptthatexposeitsfunctionalunderbelly.
IntroductionDoweneedtoknowadvancedmath—categorytheory,Lambdacalculus,polymorphisms—justtowriteapplicationswithfunctionalprogramming?Doweneedtoreinventthewheel?Theshortanswertoboththesequestionsisno.
Inthischapter,wewilldoourbesttosurveyeverythingthatcanimpactthewaywewriteourfunctionalapplicationsinJavaScript.
LibrariesToolkitsDevelopmentenvironmentsFunctionallanguagethatcompilestoJavaScriptAndmore
PleaseunderstandthatthecurrentlandscapeoffunctionallibrariesforJavaScriptisaveryfluidone.Likeallaspectsofcomputerprogramming,thecommunitycanchangeinaheartbeat;newlibrariescanbeadoptedandoldonescanbeabandoned.Forinstance,duringthewritingprocessofthisverybook,thepopularandstableNode.jsplatformforI/Ohasbeenforkedbyitsopensourcecommunity.Itsfutureisvague.
Therefore,themostimportantconcepttobegainedfromthischapterisnothowtousethecurrentlibrariesforfunctionalprogramming,buthowtouseanylibrarythatenhancesJavaScript’sfunctionalprogrammingmethod.Thischapterwillnotfocusonjustoneortwolibraries,butwillexploreasmanyaspossiblewiththegoalofsurveyingallthemanystylesoffunctionalprogrammingthatexistwithinJavaScript.
FunctionallibrariesforJavaScriptIt’sbeensaidthateveryfunctionalprogrammerwritestheirownlibraryoffunctions,andfunctionalJavaScriptprogrammersarenoexception.Withtoday’sopensourcecode-sharingplatformssuchasGitHub,Bower,andNPM,it’seasiertoshare,collaborate,andgrowtheselibraries.ManylibrariesexistforfunctionalprogrammingwithJavaScript,rangingfromtinytoolkitstomonolithicmodulelibraries.
Eachlibrarypromotesitsownstyleoffunctionalprogramming.Fromarigid,math-basedstyletoarelaxed,informalstyle,eachlibraryisdifferentbuttheyallshareonecommonfeature:theyallhaveabstractJavaScriptfunctionalcapabilitiestoincreasecodere-use,readability,androbustness.
Atthetimeofwriting,however,asinglelibraryhasnotestablisheditselfasthede-factostandard.Somemightarguethatunderscore.jsistheonebut,asyou’llseeinthefollowingsection,itmightbeadvisabletoavoidunderscore.js.
Underscore.jsUnderscorehasbecomethestandardfunctionalJavaScriptlibraryintheeyesofmany.Itismature,stable,andwascreatedbyJeremyAshkenas,themanbehindtheBackbone.jsandCoffeeScriptlibraries.UnderscoreisactuallyareimplementationofRuby’sEnumerablemodule,whichexplainswhyCoffeeScriptwasalsoinfluencedbyRuby.
SimilartojQuery,Underscoredoesn’tmodifynativeJavaScriptobjectsandinsteadusesasymboltodefineitsownobject:theunderscorecharacter“_“.So,usingUnderscorewouldworklikethis:
varx=_.map([1,2,3],Math.sqrt);//Underscore'smapfunction
console.log(x.toString());
We’vealreadyseenJavaScrip’snativemap()methodfortheArrayobject,whichworkslikethis:
varx=[1,2,3].map(Math.sqrt);
Thedifferenceisthat,inUnderscore,boththeArrayobjectandthecallback()functionarepassedasparameterstotheUnderscoreobject’smap()method(_.map),asopposedtopassingonlythecallbacktothearray’snativemap()method(Array.prototype.map).
Butthere’swaymorethanjustmap()andotherbuilt-infunctionstoUnderscore.It’sfullofsuperhandyfunctionssuchasfind(),invoke(),pluck(),sortyBy(),groupBy(),andmore.
vargreetings=[{origin:'spanish',value:'hola'},
{origin:'english',value:'hello'}];
console.log(_.pluck(greetings,'value'));
//Grabsanobject'sproperty.
//Returns:['hola','hello']
console.log(_.find(greetings,function(s){returns.origin==
'spanish';}));
//Looksforthefirstobjthatpassesthetruthtest
//Returns:{origin:'spanish',value:'hola'}
greetings=greetings.concat(_.object(['origin','value'],
['french','bonjour']));
console.log(greetings);
//_.objectcreatesanobjectliteralfromtwomergedarrays
//Returns:[{origin:'spanish',value:'hola'},
//{origin:'english',value:'hello'},
//{origin:'french',value:'bonjour'}]
Anditprovidesawayofchainingmethodstogether:
varg=_.chain(greetings)
.sortBy(function(x){returnx.value.length})
.pluck('origin')
.map(function(x){returnx.charAt(0).toUpperCase()+x.slice(1)})
.reduce(function(x,y){returnx+''+y},'')
.value();
//Appliesthefunctions
//Returns:'SpanishEnglishFrench'
console.log(g);
NoteThe_.chain()methodreturnsawrappedobjectthatholdsalltheUnderscorefunctions.The_.valuemethodisthenusedtoextractthevalueofthewrappedobject.WrappedobjectsarealsoveryusefulformixingUnderscorewithobject-orientedprogramming.
Despiteitseaseofuseandadaptationbythecommunity,theunderscore.jslibraryhasbeencriticizedforforcingyoutowriteoverlyverbosecodeandforencouragingthewrongpatterns.Underscore’sstructuremaynotbeidealorevenfunction!
Untilversion1.7.0,releasedshortlyafterBrianLonsdorf’stalkentitledHeyUnderscore,you’redoingitwrong!,landedonYouTube,Underscoreexplicitlypreventedusfromextendingfunctionssuchasmap(),reduce(),filter(),andmore.
_.prototype.map=function(obj,iterate,[context]){
if(Array.prototype.map&&obj.map===Array.prototype.map)return
obj.map(iterate,context);
//...
};
NoteYoucanwatchthevideoofBrianLonsdorf’stalkatwww.youtube.com/watch?v=m3svKOdZij.
Map,intermsofcategorytheory,isahomomorphicfunctorinterface(moreonthisinChapter5,CategoryTheory).Andweshouldbeabletodefinemapasafunctorforwhateverweneeditfor.Sothat’snotveryfunctionalofUnderscore.
AndbecauseJavaScriptdoesn’thavebuilt-inimmutabledata,afunctionallibraryshouldbecarefultonotallowitshelperfunctionstomutatetheobjectspassedtoit.Agoodexampleofthisproblemisshownbelow.Theintentionofthesnippetistoreturnanewselectedlistwithoneoptionsetasthedefault.Butwhatactuallyhappensisthattheselectedlistismutatedinplace.
functiongetSelectedOptions(id,value){
options=document.querySelectorAll('#'+id+'option');
varnewOptions=_.map(options,function(opt){
if(opt.text==value){
opt.selected=true;
opt.text+='(thisisthedefault)';
}
else{
opt.selected=false;
}
returnopt;
});
returnnewOptions;
}
varoptionsHelp=getSelectedOptions('timezones','Chicago');
Wewouldhavetoinsertthelineopt=opt.cloneNode();tothecallback()functionto
makeacopyofeachobjectwithinthelistbeingpassedtothefunction.Underscore’smap()functioncheatstoboostperformance,butitisatthecostoffunctionalfengshui.ThenativeArray.prototype.map()functionwouldn’trequirethisbecauseitmakesacopy,butitalsodoesn’tworkonnodelistcollections.
Underscoremaybelessthanidealformathematically-correct,functionalprogramming,butitwasneverintendedtoextendortransformJavaScriptintoapurefunctionallanguage.ItdefinesitselfasaJavaScriptlibrarythatprovidesawholemessofusefulfunctionalprogramminghelpers.Itmaybealittlemorethanaspuriouscollectionoffunctional-likehelpers,butit’snoseriousfunctionallibraryeither.
Isthereabetterlibraryoutthere?Perhapsonethatisbasedonmathematics?
FantasyLandSometimes,thetruthisstrangerthanfiction.
FantasyLandisacollectionoffunctionalbaselibrariesandaformalspecificationforhowtoimplement“algebraicstructures”inJavaScript.Morespecifically,FantasyLandspecifiestheinteroperabilityofcommonalgebraicstructures,oralgebrasforshort:monads,monoids,setoids,functors,chains,andmore.Theirnamesmaysoundscary,butthey’rejustasetofvalues,asetofoperators,andsomelawsitmustobey.Inotherwords,they’rejustobjects.
Here’showitworks.EachalgebraisaseparateFantasyLandspecificationandmayhavedependenciesonotheralgebrasthatneedtobeimplemented.
Someofthealgebraspecificationsare:
Setoids:
Implementthereflexivity,symmetryandtransitivitylawsDefinetheequals()method
Semigroups
ImplementtheassociativitylawDefinetheconcat()method
Monoid
ImplementrightidentityandleftidentityDefinetheempty()method
Functor
ImplementtheidentityandcompositionlawsDefinethemap()method
Thelistgoesonandon.
Wedon’tnecessarilyneedtoknowexactlywhateachalgebraisforbutitcertainlyhelps,
especiallyifyou’rewritingyourownlibrarythatconformstothespecifications.It’snotjustabstractnonsense,itoutlinesameansofimplementingahigh-levelabstractioncalledcategorytheory.AfullexplanationofcategorytheorycanbefoundinChapter5,CategoryTheory.
FantasyLanddoesn’tjusttellushowtoimplementfunctionalprogramming,itdoesprovideasetoffunctionalmodulesforJavaScript.However,manyareincompleteanddocumentationisprettysparse.ButFantasyLandisn’ttheonlylibraryouttheretoimplementitsopensourcespecifications.Othershavetoo,namely:Bilby.js.
Bilby.jsWhattheheckisabilby?No,it’snotamythicalcreaturethatmightexistinFantasyLand.ItexistshereonEarthasafreaky/cutecrossbetweenamouseandarabbit.Nonetheless,bibly.jslibraryiscompliantwithFantasyLandspecifications.
Infact,bilby.jsisaseriousfunctionallibrary.Asitsdocumentationstates,itis,Serious,meaningitappliescategorytheorytoenablehighlyabstractcode.Functional,meaningitenablesreferentiallytransparentprograms.Wow,thatisprettyserious.Thedocumentationlocatedathttp://bilby.brianmckenna.org/goesontosaythatitprovides:
Immutablemulti-methodsforad-hocpolymorphismFunctionaldatastructuresOperatoroverloadingforfunctionalsyntaxAutomatedspecificationtesting(ScalaCheck,QuickCheck)
ByfarthemostmaturelibrarythatconformstotheFantasyLandspecificationsforalgebraicstructures,Bilby.jsisagreatresourceforfullycommittingtothefunctionalstyle.
Let’stryanexample:
//environmentsinbilbyareimmutablestructureformultimethods
varshapes1=bilby.environment()
//candefinemethods
.method(
'area',//methodstakeaname
function(a){returntypeof(a)=='rect'},//apredicate
function(a){returna.x*a.y}//andanimplementation
)
//andproperties,likemethodswithpredicatesthatalways
//returntrue
.property(
'name',//takesaname
'shape');//andafunction
//nowwecanoverloadit
varshapes2=shapes1
.method(
'area',function(a){returntypeof(a)=='circle'},
function(a){returna.r*a.r*Math.PI});
varshapes3=shapes2
.method(
'area',function(a){returntypeof(a)=='triangle'},
function(a){returna.height*a.base/2});
//andnowwecandosomethinglikethis
varobjs=[{type:'circle',r:5},{type:'rect',x:2,y:3}];
varareas=objs.map(shapes3.area);
//andthis
vartotalArea=objs.map(shapes3.area).reduce(add);
Thisiscategorytheoryandad-hocpolymorphisminaction.Again,categorytheorywillbe
coveredinfullinChapter5,CategoryTheory.
NoteCategorytheoryisarecentlyinvigoratedbranchofmathematicsthatfunctionalprogrammersusetomaximizetheabstractionandusefulnessoftheircode.Butthereisamajordrawback:it’sdifficulttoconceptualizeandquicklygetstartedwith.
ThetruthisthatBilbyandFantasyLandarereallystretchingthepossibilitiesoffunctionalprogramminginJavaScript.Althoughit’sexcitingtoseetheevolutionofcomputerscience,theworldmayjustnotbereadyforthekindofhard-corefunctionalstylethatBiblyandFantasyLandarepushing.
Maybesuchagrandioselibraryonthebleeding-edgeoffunctionalJavaScriptisnotourthing.Afterall,wesetouttoexplorethefunctionaltechniquesthatcomplementJavaScript,nottobuildfunctionalprogrammingdogma.Let’sturnourattentiontoanothernewlibrary,Lazy.js.
Lazy.jsLazyisautilitylibrarymorealongthelinesoftheunderscore.jslibrarybutwithalazyevaluationstrategy.Becauseofthis,Lazymakestheimpossiblepossiblebyfunctionallycomputingresultsofseriesthatwon’tbeavailablewithimmediateinterpretation.Italsoboastsasignificantperformanceboost.
TheLazy.jslibraryisstillveryyoung.Butithasalotofmomentumandcommunityenthusiasmbehindit.
Theideaisthat,inLazy,everythingisasequencethatwecaniterateover.Owingtothewaythelibrarycontrolstheorderinwhichmethodsareapplied,manyreallycoolthingscanbeachieved:asynchronousiteration(parallelprogramming),infinitesequences,functionalreactiveprogramming,andmore.
Thefollowingexamplesshowoffabitofeverything:
//Getthefirsteightlinesofasong'slyrics
varlyrics="Loremipsumdolorsitamet,consecteturadipiscingeli
//WithoutLazy,theentirestringisfirstsplitintolines
console.log(lyrics.split('\n').slice(0,3));
//WithLazy,thetextisonlysplitintothefirst8lines
//Thelyricscanevenbeinfinitelylong!
console.log(Lazy(lyrics).split('\n').take(3));
//First10squaresthatareevenlydivisibleby3
varoneTo1000=Lazy.range(1,1000).toArray();
varsequence=Lazy(oneTo1000)
.map(function(x){returnx*x;})
.filter(function(x){returnx%3===0;})
.take(10)
.each(function(x){console.log(x);});
//asynchronousiterationoveraninfinitesequence
varasyncSequence=Lazy.generate(function(x){returnx++})
.async(100)//0.100sintervalsbetweenelements
.take(20)//onlycomputethefirst20
.each(function(e){//beginiteratingoverthesequence
console.log(newDate().getMilliseconds()+":"+e);
});
Moreexamplesanduse-casesarecoveredinChapter4,ImplementingFunctionalProgrammingTechniquesinJavaScript.
ButitsnotentirelycorrecttofullycredittheLazy.jslibrarywiththisidea.Oneofitspredecessors,theBacon.jslibrary,worksinmuchthesameway.
Bacon.jsThelogoofBacon.jslibraryisasfollows:
Themustachioedhipsteroffunctionalprogramminglibraries,Bacon.jsisitselfalibraryforfunctionalreactiveprogramming.Functionalreactiveprogrammingjustmeansthatfunctionaldesignpatternsareusedtorepresentvaluesthatarereactiveandalwayschanging,likethepositionofthemouseonthescreen,orthepriceofacompany’sstock.InthesamewaythatLazycangetawaywithcreatinginfinitesequencesbynotcalculatingthevalueuntilit’sneeded,Baconcanavoidhavingtocalculateever-changingvaluesuntiltheverylastsecond.
WhatarecalledsequencesinLazyareknownasEventStreamsandPropertiesinBaconbecausethey’remoresuitedforworkingwithevents(onmouseover,onkeydown,andsoon)andreactiveproperties(scrollposition,mouseposition,toggles,andsoon).
Bacon.fromEventTarget(document.body,"click")
.onValue(function(){alert("Bacon!")});
BaconisalittlebitolderthanLazybutitsfeaturesetisabouthalfthesizeanditscommunityenthusiasmisaboutequal.
HonorablementionsTherearesimplytoomanylibrariesouttheretodothemalljusticewithinthescopeofthisbook.Let’slookatafewmorelibrariesforfunctionalprogramminginJavaScript.
Functional
PossiblythefirstlibraryforfunctionalprogramminginJavaScript,Functionalisalibrarythatincludescomprehensivehigher-orderfunctionsupportaswellasstringlambdas
wu.js
Especiallyprizedforitscurryable()function,wu.jslibraryisaveryniceLibraryforfunctionalprogramming.Itwasthefirstlibrary(thatIknowof)toimplementlazyevaluation,gettingtheballrollingforBacon.js,Lazy.jsandotherlibrariesYes,itisnamedaftertheinfamousrapgroupWuTangClan
sloth.js
VerysimilartotheLazy.jslibraries,butmuchsmaller
stream.js
Thestream.jslibrarysupportsinfinitestreamsandnotmuchelseAbsolutelytinyinsize
Lo-Dash.js
Asthenamemightimply,thelo-dash.jslibrarywasinspiredbytheunderscore.jslibraryHighlyoptimized
Sugar
SugarisasupportlibraryforfunctionalprogrammingtechniquesinJavaScript,likeUnderscore,butwithsomekeydifferencesinhowit’simplemented.Insteadofdoing_.pluck(myObjs,'value')inUnderscore,it’sjustmyObjs.map('value')inSugar.ThismeansthatitmodifiesnativeJavaScriptobjects,sothereisasmallriskofitnotplayingnicelywithotherlibrariesthatdothesamesuchasPrototype.Verygooddocumentation,unittests,analyzers,andmore.
from.js
AnewfunctionallibraryandLINQ(LanguageIntegratedQuery)engineforJavaScriptthatsupportsmostofthesameLINQfunctionsthat.NETprovides100%lazyevaluationandsupportslambdaexpressionsVeryyoungbutdocumentationisexcellent
JSLINQ
AnotherfunctionalLINQengineforJavaScriptMucholderandmorematurethanfrom.jslibrary
Boiler.js
AnotherutilitylibrarythatextendsJavaScript’sfunctionalmethodstomoreprimitives:strings,numbers,objects,collectionsandarrays
Folktale
LiketheBilby.jslibrary,FolktaleisanothernewlibrarythatimplementstheFantasyLandspecifications.Andlikeitsforefather,FolktaleisalsoacollectionoflibrariesforfunctionalprogramminginJavaScript.It’sveryyoungbutcouldhaveabrightfuture.
jQuery
SurprisedtoseejQuerymentionedhere?AlthoughjQueryisnotatoolusedtoperformfunctionalprogramming,itneverthelessisfunctionalitself.jQuerymightbeoneofthemostwidelyusedlibrariesthathasitsrootsinfunctionalprogramming.ThejQueryobjectisactuallyamonad.jQueryusesthemonadiclawstoenablemethodchaining:
$('#mydiv').fadeIn().css('left':50).alert('hi!');
AfullexplanationofthiscanbefoundinChapter7,FunctionalandObject-orientedProgramminginJavaScript.
Andsomeofitsmethodsarehigher-order:
$('li').css('left':function(index){returnindex*50});
AsofjQuery1.8,thedeferred.thenparameterimplementsafunctionalconceptknownasPromises.jQueryisanabstractionlayer,mainlyfortheDOM.It’snotaframeworkoratoolkit,justawaytouseabstractiontoincreasecode-reuseandreduceuglycode.Andisn’tthatwhatfunctionalprogrammingisallabout?
DevelopmentandproductionenvironmentsItdoesnotmatterintermsofprogrammingstylewhattypeofenvironmenttheapplicationisbeingdevelopedinandwillbedeployedin.Butitdoesmattertothelibrariesalot.
BrowsersThemajorityofJavaScriptapplicationsaredesignedtorunontheclientside,thatis,intheclient’sbrowser.Browser-basedenvironmentsareexcellentfordevelopmentbecausebrowsersareubiquitous,youcanworkonthecoderightonyourlocalmachine,theinterpreteristhebrowser’sJavaScriptengine,andallbrowsershaveadeveloperconsole.Firefox’sFireBugprovidesveryusefulerrormessagesandallowsforbreak-pointsandmore,butit’softenhelpfultorunthesamecodeinChromeandSafaritocross-referencetheerroroutput.EvenInternetExplorercontainsdevelopertools.
TheproblemwithbrowsersisthattheyevaluateJavaScriptdifferently!Thoughit’snotcommon,itispossibletowritecodethatreturnsverydifferentresultsindifferentbrowsers.Butusuallythedifferencesareinthewaytheytreatthedocumentobjectmodelandnothowprototypesandfunctionswork.Obviously,Math.sqrt(4)methodreturns2toallbrowsersandshells.ButthescrollLeftmethoddependsonthebrowser’slayoutpolicies.
Writingbrowser-specificcodeisawasteoftime,andthat’sanotherreasonwhylibrariesshouldbeused.
Server-sideJavaScriptTheNode.jslibraryhasbecomethestandardplatformforcreatingserver-sideandnetwork-basedapplications.Canfunctionalprogrammingbeusedforserver-sideapplicationprogramming?Yes!Ok,butdothereexistanyfunctionallibrariesthataredesignedforthisperformance-criticalenvironment?Theanswertothatisalso:yes.
AllthefunctionallibrariesoutlinedinthischapterwillworkintheNode.jslibrary,andmanydependonthebrowserify.jsmoduletoworkwithbrowserelements.
Afunctionalusecaseintheserver-sideenvironmentInourbravenewworldofnetworksystems,server-sideapplicationdevelopersareoftenconcernedwithconcurrency,andrightlyso.Theclassicexampleisanapplicationthatallowsmultipleuserstomodifythesamefile.Butiftheytrytomodifyitatthesametime,youwillgetintoanuglymess.Thisisthemaintenanceofstateproblemthathasplaguedprogrammersfordecades.
Assumethefollowingscenario:
1. Onemorning,Adamopensareportforeditingbuthedoesn’tsaveitbeforeleavingforlunch.
2. Billyopensthesamereport,addshisnotes,andthensavesit.3. Adamcomesbackfromlunch,addshisnotestothereport,andthensavesit,
unknowinglyoverwritingBilly’snotes.4. Thenextday,Billyfindsoutthathisnotesaremissing.Hisbossyellsathim;
everybodygetsmadandtheyganguponthemisguidedapplicationdeveloperwhounfairlyloseshisjob.
Foralongtime,thesolutiontothisproblemwastocreateastateaboutthefile.Togglealockstatustoonwhensomeonebeginseditingit,whichpreventsothersfrombeingabletoeditit,andthentoggleittooffoncetheysaveit.Inourscenario,BillywouldnotbeabletodohisworkuntilAdamgetsbackfromlunch.Andifit’sneversaved(if,say,Adamdecidedtoquithisjobinthemiddleofthelunchbreak),thennoonewilleverbeabletoeditit.
Thisiswherefunctionalprogramming’sideasaboutimmutabledataandstate(orlackthereof)canreallybeputtowork.Insteadofhavingusersmodifythefiledirectly,withafunctionalapproachtheywouldmodifyacopyofthefile,whichisanewrevision.Iftheygotosavetherevisionandanewrevisionalreadyexists,thenweknowthatsomeoneelsehasalreadymodifiedtheoldone.Crisisaverted.
Nowthescenariofrombeforewouldunfoldlikethis:
1. Onemorning,Adamopensareportforediting.Buthedoesn’tsaveitbeforegoingtolunch.
2. Billyopensthesamereport,addshisnotes,andsavesitasanewrevision.3. Adamreturnsfromlunchtoaddhisnotes.Whenheattemptstosavethenew
revision,theapplicationtellshimthatanewerrevisionnowexists.4. Adamopensthenewrevisions,addshisnotestoit,andsavesanothernewrevision.5. Bylookingattherevisionhistory,thebossseesthateverythingisworkingsmoothly.
Everyoneishappyandtheapplicationdevelopergetsapromotionandaraise.
Thisisknownaseventsourcing.Thereisnoexplicitstatetobemaintained,onlyevents.Theprocessismuchcleanerandthereisaclearhistoryofeventsthatcanbereviewed.
Thisideaandmanyothersarewhyfunctionalprogramminginserver-sideenvironmentsisontherise.
CLIAlthoughwebandthenode.jslibraryarethetwomainJavaScriptenvironments,somepragmaticandadventuroususersarefindingwaystouseJavaScriptinthecommandline.
UsingJavaScriptasaCommandLineInterface(CLI)scriptinglanguagemightbeoneofthebestopportunitiestoapplyfunctionprogramming.ImaginebeingabletouselazyevaluationwhensearchingforlocalfilesortorewriteanentirebashscriptintoafunctionalJavaScriptone-liner.
UsingfunctionallibrarieswithotherJavaScriptmodulesWebapplicationsaremadeupofallsortsofthings:frameworks,libraries,APIsandmore.Theycanworkalongsideeachotherasdependents,plugins,orjustascoexistingobjects.
Backbone.js
AnMVP(model-view-provider)frameworkwithaRESTfulJSONinterfaceRequirestheunderscore.jslibrary,Backbone’sonlyharddependency
jQuery
TheBacon.jslibraryhasbindingsformixingwithjQueryUnderscoreandjQuerycomplementeachotherverywell
PrototypeJavaScriptFramework
ProvidesJavaScriptwithcollectionfunctionsinthemannerclosesttoRuby’sEnumerable
Sugar.js
ModifiesnativeobjectsandtheirmethodsMustbecarefulwhenmixingwithotherlibraries,especiallyPrototype
FunctionallanguagesthatcompileintoJavaScriptSometimesthethickveneerofC-likesyntaxoverJavaScript’sinnerfunctionalitycanbeenoughtomakeyouwanttoswitchtoanotherfunctionallanguage.Well,youcan!
ClojureandClojureScript
ClosureisamodernLispimplementationandafull-featuredfunctionallanguageClojureScripttrans-compilesClojureintoJavaScript
CoffeeScript
CoffeeScriptisthenameofbothafunctionallanguageandacompilerfortrans-compilingthelanguageintoJavaScript1-to-1mappingbetweenexpressionsinCoffeeScriptandexpressioninJavaScript
Therearemanymoreoutthere,includingPyjs,Roy,TypeScript,UHCandmore.
SummaryWhichlibraryyouchoosetousedependsonwhatyourneedsare.Needfunctionalreactiveprogrammingtohandleeventsanddynamicvalues?UsetheBacon.jslibrary.Onlyneedinfinitestreamsandnothingelse?Usethestream.jslibrary.WanttocomplementjQuerywithfunctionalhelpers?Trytheunderscore.jslibrary.Needastructuredenvironmentforseriousadhocpolymorphism?Checkoutthebilby.jslibrary.Needawell-roundedtoolforfunctionalprogramming?UsetheLazy.jslibrary.Nothappywithanyoftheseoptions?Writeyourown!
Anylibraryisonlyasgoodasthewayit’sused.Althoughafewofthelibrariesoutlinedinthischapterhaveafewflaws,mostfaultsoccursomewherebetweenthekeyboardandthechair.It’suptoyoutousethelibrariescorrectlyandtosuityourneeds.
Andifwe’reimportingcodelibrariesintoourJavaScriptenvironment,thenmaybewecanimportideasandprinciplestoo.MaybewecanchannelTheZenofPython,byTimPeter:
Beautifulisbetterthanugly
Explicitisbetterthanimplicit.
Simpleisbetterthancomplex.
Complexisbetterthancomplicated.
Flatisbetterthannested.
Sparseisbetterthandense.
Readabilitycounts.
Specialcasesaren’tspecialenoughtobreaktherules.
Althoughpracticalitybeatspurity.
Errorsshouldneverpasssilently.
Unlessexplicitlysilenced.
Inthefaceofambiguity,refusethetemptationtoguess.
Thereshouldbeone—andpreferablyonlyone—obviouswaytodoit.
Althoughthatwaymaynotbeobviousatfirstunlessyou’reDutch.
Nowisbetterthannever.
Althoughneverisoftenbetterthan“right”now.
Iftheimplementationishardtoexplain,it’sabadidea.
Iftheimplementationiseasytoexplain,itmaybeagoodidea.
Namespacesareonehonkinggreatidea—let’sdomoreofthose!
Chapter4.ImplementingFunctionalProgrammingTechniquesinJavaScriptHoldontoyourhatsbecausewe’rereallygoingtogetintothefunctionalmind-setnow.
Inthischapter,we’regoingtodothefollowing:
PutallthecoreconceptstogetherintoacohesiveparadigmExplorethebeautythatfunctionalprogramminghastoofferwhenwefullycommittothestyleStepthroughthelogicalprogressionoffunctionalpatternsastheybuilduponeachotherAllthewhile,wewillbuildupasimpleapplicationthatdoessomeprettycoolstuff
YoumayhavenoticedafewconceptsthatwerebroughtupinthelastchapterwhendealingwithfunctionallibrariesforJavaScript,butnotinChapter2,FundamentalsofFunctionalProgramming.Well,thatwasforareason!Compositions,currying,partialapplication,andmore.Let’sexplorewhyandhowtheselibrariesimplementedthoseconcepts.
Functionalprogrammingcancomeinavarietyofflavorsandpatterns.Thischapterwillcovermanydifferentstylesoffunctionalprogramming:
DatagenericprogrammingMostlyfunctionalprogrammingFunctionalreactiveprogrammingandmore
Thischapter,however,willbeasstyle-unbiasedaspossible.Withoutleaningtoohardononestyleoffunctionalprogrammingoveranother,theoverallgoalistoshowthattherearebetterwaystowritecodethanwhatisoftenacceptedasthecorrectandonlyway.Onceyoufreeyourmindaboutthepreconceptionsofwhatistherightwayandwhatisnottherightwaytowritecode,youcandowhateveryouwant.Whenyoujustwritecodewithchildlikeabandonfornoreasonotherthanthefactthatyoulikeitandwhenyou’renotconcernedaboutconformingtothetraditionalwayofdoingthings,thenthepossibilitiesareendless.
PartialfunctionapplicationandcurryingManylanguagessupportoptionalarguments,butnotinJavaScript.JavaScriptusesadifferentpatternentirelythatallowsforanynumberofargumentstobepassedtoafunction.Thisleavesthedooropenforsomeveryinterestingandunusualdesignpatterns.Functionscanbeappliedinpartorinwhole.
PartialapplicationinJavaScriptistheprocessofbindingvaluestooneormoreargumentsofafunctionthatreturnsanotherfunctionthatacceptstheremaining,unboundarguments.Similarly,curryingistheprocessoftransformingafunctionwithmanyargumentsintoafunctionwithoneargumentthatreturnsanotherfunctionthattakesmoreargumentsasneeded.
Thedifferencebetweenthetwomaynotbeclearnow,butitwillbeobviousintheend.
FunctionmanipulationActually,beforewegoanyfurtherandexplainjusthowtoimplementpartialapplicationandcurrying,weneedareview.Ifwe’regoingtotearJavaScript’sthickveneerofC-likesyntaxrightoffandexposeit’sfunctionalunderbelly,thenwe’regoingtoneedtounderstandhowprimitives,functions,andprototypesinJavaScriptwork;wewouldneverneedtoconsidertheseifwejustwantedtosetsomecookiesorvalidatesomeformfields.
Apply,call,andthethiskeywordInpurefunctionallanguages,functionsarenotinvoked;they’reapplied.JavaScriptworksthesamewayandevenprovidesutilitiesformanuallycallingandapplyingfunctions.Andit’sallaboutthethiskeyword,which,ofcourse,istheobjectthatthefunctionisamemberof.
Thecall()functionletsyoudefinethethiskeywordasthefirstargument.Itworksasfollows:
console.log(['Hello','world'].join(''))//normalway
console.log(Array.prototype.join.call(['Hello','world'],''));//using
call
Thecall()functioncanbeused,forexample,toinvokeanonymousfunctions:
console.log((function(){console.log(this.length)}).call([1,2,3]));
Theapply()functionisverysimilartothecall()function,butalittlemoreuseful:
console.log(Math.max(1,2,3));//returns3
console.log(Math.max([1,2,3]));//won'tworkforarraysthough
console.log(Math.max.apply(null,[1,2,3]));//butthiswillwork
Thefundamentaldifferenceisthat,whilethecall()functionacceptsalistofarguments,theapply()functionacceptsanarrayofarguments.
Thecall()andapply()functionsallowyoutowriteafunctiononceandtheninherititinotherobjectswithoutwritingthefunctionoveragain.AndtheyarebothmembersthemselvesoftheFunctionargument.
NoteThisisbonusmaterial,butwhenyouusethecall()functiononitself,somereallycoolthingscanhappen:
//thesetwolinesareequivalent
func.call(thisValue);
Function.prototype.call.call(func,thisValue);
BindingargumentsThebind()functionallowsyoutoapplyamethodtooneobjectwiththethiskeywordassignedtoanother.Internally,it’sthesameasthecall()function,butit’schainedtothemethodandreturnsanewboundedfunction.
It’sespeciallyusefulforcallbacks,asshowninthefollowingcodesnippet:
functionDrum(){
this.noise='boom';
this.duration=1000;
this.goBoom=function(){console.log(this.noise)};
}
vardrum=newDrum();
setInterval(drum.goBoom.bind(drum),drum.duration);
Thissolvesalotofproblemsinobject-orientedframeworks,suchasDojo,specificallytheproblemsofmaintainingthestatewhenusingclassesthatdefinetheirownhandlerfunctions.Butwecanusethebind()functionforfunctionalprogrammingtoo.
TipThebind()functionactuallydoespartialapplicationonitsown,thoughinaverylimitedway.
FunctionfactoriesRememberoursectiononclosuresinChapter2,FundamentalsofFunctionalProgramming?ClosuresaretheconstructsthatmakesitpossibletocreateausefulJavaScriptprogrammingpatternknownasfunctionfactories.Theyallowustomanuallybindargumentstofunctions.
First,we’llneedafunctionthatbindsanargumenttoanotherfunction:
functionbindFirstArg(func,a){
returnfunction(b){
returnfunc(a,b);
};
}
Thenwecanusethistocreatemoregenericfunctions:
varpowersOfTwo=bindFirstArg(Math.pow,2);
console.log(powersOfTwo(3));//8
console.log(powersOfTwo(5));//32
Anditcanworkontheotherargumenttoo:
functionbindSecondArg(func,b){
returnfunction(a){
returnfunc(a,b);
};
}
varsquareOf=bindSecondArg(Math.pow,2);
varcubeOf=bindSecondArg(Math.pow,3);
console.log(squareOf(3));//9
console.log(squareOf(4));//16
console.log(cubeOf(3));//27
console.log(cubeOf(4));//64
Theabilitytocreategenericfunctionsisveryimportantinfunctionalprogramming.Butthere’saclevertricktomakingthisprocessevenmoregeneralized.ThebindFirstArg()
functionitselftakestwoarguments,thefirstbeingafunction.IfwepassthebindFirstArgfunctionasafunctiontoitself,wecancreatebindablefunctions.Thiscanbebestdescribedwiththefollowingexample:
varmakePowersOf=bindFirstArg(bindFirstArg,Math.pow);
varpowersOfThree=makePowersOf(3);
console.log(powersOfThree(2));//9
console.log(powersOfThree(3));//27
Thisiswhythey’recalledfunctionfactories.
PartialapplicationNoticethatourfunctionfactoryexample’sbindFirstArg()andbindSecondArg()functionsonlyworkforfunctionsthathaveexactlytwoarguments.Wecouldwritenewonesthatworkfordifferentnumbersofarguments,butthatwouldworkawayfromourmodelofgeneralization.
Whatweneedispartialapplication.
NotePartialapplicationistheprocessofbindingvaluestooneormoreargumentsofafunctionthatreturnsapartially-appliedfunctionthatacceptstheremaining,unboundarguments.
Unlikethebind()functionandotherbuilt-inmethodsoftheFunctionobject,we’llhavetocreateourownfunctionsforpartialapplicationandcurrying.Therearetwodistinctwaystodothis.
Asastand-alonefunction,thatis,varpartial=function(func){...Asapolyfill,thatis,Function.prototype.partial=function(){...
Polyfillsareusedtoaugmentprototypeswithnewfunctionsandwillallowustocallournewfunctionsasmethodsofthefunctionthatwewanttopartiallyapply.Justlikethis:myfunction.partial(arg1,arg2,…);
PartialapplicationfromtheleftHere’swhereJavaScript’sapply()andcall()utilitiesbecomeusefulforus.Let’slookatapossiblepolyfillfortheFunctionobject:
Function.prototype.partialApply=function(){
varfunc=this;
args=Array.prototype.slice.call(arguments);
returnfunction(){
returnfunc.apply(this,args.concat(
Array.prototype.slice.call(arguments)
));
};
};
Asyoucansee,itworksbyslicingtheargumentsspecialvariable.
NoteEveryfunctionhasaspeciallocalvariablecalledargumentsthatisanarray-likeobjectoftheargumentspassedtoit.It’stechnicallynotanarray.ThereforeitdoesnothaveanyoftheArraymethodssuchassliceandforEach.That’swhyweneedtouseArray’sslice.callmethodtoslicethearguments.
Andnowlet’sseewhathappenswhenweuseitinanexample.Thistime,let’sgetawayfromthemathandgoforsomethingalittlemoreuseful.We’llcreatealittleapplicationthatconvertsnumberstohexadecimalvalues.
functionnums2hex(){
functioncomponentToHex(component){
varhex=component.toString(16);
//makesurethereturnvalueis2digits,i.e.0cor12
if(hex.length==1){
return"0"+hex;
}
else{
returnhex;
}
}
returnArray.prototype.map.call(arguments,componentToHex).join('');
}
//thefunctionworksonanynumberofinputs
console.log(nums2hex());//''
console.log(nums2hex(100,200));//'64c8'
console.log(nums2hex(100,200,255,0,123));//'64c8ff007b'
//butwecanusethepartialfunctiontopartiallyapply
//arguments,suchastheOUIofamacaddress
varmyOUI=123;
vargetMacAddress=nums2hex.partialApply(myOUI);
console.log(getMacAddress());//'7b'
console.log(getMacAddress(100,200,2,123,66,0,1));//
'7b64c8027b420001'
//orwecanconvertrgbvaluesofredonlytohexadecimal
varshadesOfRed=nums2hex.partialApply(255);
console.log(shadesOfRed(123,0));//'ff7b00'
console.log(shadesOfRed(100,200));//'ff64c8'
Thisexampleshowsthatwecanpartiallyapplyargumentstoagenericfunctionandgetanewfunctioninreturn.Thisfirstexampleisleft-to-right,whichmeansthatwecanonlypartiallyapplythefirst,left-mostarguments.
PartialapplicationfromtherightInordertoapplyargumentsfromtheright,wecandefineanotherpolyfill.
Function.prototype.partialApplyRight=function(){
varfunc=this;
args=Array.prototype.slice.call(arguments);
returnfunction(){
returnfunc.apply(
this,
[].slice.call(arguments,0)
.concat(args));
};
};
varshadesOfBlue=nums2hex.partialApplyRight(255);
console.log(shadesOfBlue(123,0));//'7b00ff'
console.log(shadesOfBlue(100,200));//'64c8ff'
varsomeShadesOfGreen=nums2hex.partialApplyRight(255,0);
console.log(shadesOfGreen(123));//'7bff00'
console.log(shadesOfGreen(100));//'64ff00'
Partialapplicationhasallowedustotakeaverygenericfunctionandextractmorespecificfunctionsoutofit.Butthebiggestflawinthismethodisthatthewayinwhichtheargumentsarepassed,asinhowmanyandinwhatorder,canbeambiguous.Andambiguityisneveragoodthinginprogramming.There’sabetterwaytodothis:currying.
CurryingCurryingistheprocessoftransformingafunctionwithmanyargumentsintoafunctionwithoneargumentthatreturnsanotherfunctionthattakesmoreargumentsasneeded.Formally,afunctionwithNargumentscanbetransformedintoafunctionchainofNfunctions,eachwithonlyoneargument.
Acommonquestionis:whatisthedifferencebetweenpartialapplicationandcurrying?Whileit’struethatpartialapplicationreturnsavaluerightawayandcurryingonlyreturnsanothercurriedfunctionthattakesthenextargument,thefundamentaldifferenceisthatcurryingallowsformuchbettercontrolofhowargumentsarepassedtothefunction.We’llseejusthowthat’strue,butfirstweneedtocreatefunctiontoperformthecurrying.
Here’sourpolyfillforaddingcurryingtotheFunctionprototype:
Function.prototype.curry=function(numArgs){
varfunc=this;
numArgs=numArgs||func.length;
//recursivelyacquirethearguments
functionsubCurry(prev){
returnfunction(arg){
varargs=prev.concat(arg);
if(args.length<numArgs){
//recursivecase:westillneedmoreargs
returnsubCurry(args);
}
else{
//basecase:applythefunction
returnfunc.apply(this,args);
}
};
}
returnsubCurry([]);
};
ThenumArgsargumentletsusoptionallyspecifythenumberofargumentsthefunctionbeingcurriedneedsifit’snotexplicitlydefined.
Let’slookathowtouseitwithinourhexadecimalapplication.We’llwriteafunctionthatconvertsRGBvaluestoahexadecimalstringthatisappropriateforHTML:
functionrgb2hex(r,g,b){
//nums2hexispreviouslydefinedinthischapter
return'#'+nums2hex(r)+nums2hex(g)+nums2hex(b);
}
varhexColors=rgb2hex.curry();
console.log(hexColors(11))//returnsacurriedfunction
console.log(hexColors(11,12,123))//returnsacurriedfunction
console.log(hexColors(11)(12)(123))//returns#0b0c7b
console.log(hexColors(210)(12)(0))//returns#d20c00
Itwillreturnthecurriedfunctionuntilallneededargumentsarepassedin.Andthey’repassedinthesameleft-to-rightorderasdefinedbythefunctionbeingcurried.
Butwecanstepitupanotchanddefinethemorespecificfunctionsthatweneedasfollows:
varreds=function(g,b){returnhexColors(255)(g)(b)};
vargreens=function(r,b){returnhexColors(r)(255)(b)};
varblues=function(r,g){returnhexColors(r)(g)(255)};
console.log(reds(11,12))//returns#ff0b0c
console.log(greens(11,12))//returns#0bff0c
console.log(blues(11,12))//returns#0b0cff
Sothat’sanicewaytousecurrying.Butifwejustwanttocurryournums2hex()functiondirectly,werunintoalittlebitoftrouble.Andthat’sbecausethefunctiondoesn’tdefineanyarguments,itjustletsyoupassasmanyargumentsinasyouwant.Sowehavetodefinethenumberofarguments.Wedothatwiththeoptionalparametertothecurryfunctionthatallowsustosetthenumberofargumentsofthefunctionbeingcurried.
varhexs=nums2hex.curry(2);
console.log(hexs(11)(12));//returns0b0c
console.log(hexs(11));//returnsfunction
console.log(hexs(110)(12)(0));//incorrect
Thereforecurryingdoesnotworkwellwithfunctionsthatacceptvariablenumbersofarguments.Forsomethinglikethat,partialapplicationispreferred.
Allofthisisn’tjustforthebenefitoffunctionfactoriesandcodereuse.Curryingandpartialapplicationplayintoabiggerpatternknownascomposition.
FunctioncompositionFinally,wehavearrivedatfunctioncomposition.
Infunctionalprogramming,wewanteverythingtobeafunction.Weespeciallywantunaryfunctionsifpossible.Ifwecanconvertallfunctionstounaryfunctions,thenmagicalthingscanhappen.
NoteUnaryfunctionsarefunctionsthattakeonlyasingleinput.Functionswithmultipleinputsarepolyadic,butweusuallysaybinaryforfunctionsthataccepttwoinputsandternaryforthreeinputs.Somefunctionsdon’tacceptaspecificnumberofinputs;wecallthosevariadic.
Manipulatingfunctionsandtheiracceptablenumberofinputscanbeextremelyexpressive.Inthissection,wewillexplorehowtocomposenewfunctionsfromsmallerfunctions:littleunitsoflogicthatcombineintowholeprogramsthataregreaterthanthesumofthefunctionsontheirown.
ComposeComposingfunctionsallowsustobuildcomplexfunctionsfrommanysimple,genericfunctions.Bytreatingfunctionsasbuildingblocksforotherfunctions,wecanbuildtrulymodularapplicationswithexcellentreadabilityandmaintainability.
Beforewedefinethecompose()polyfill,youcanseehowitallworkswiththesefollowingexamples:
varroundedSqrt=Math.round.compose(Math.sqrt)
console.log(roundedSqrt(5));//Returns:2
varsquaredDate=roundedSqrt.compose(Date.parse)
console.log(squaredDate("January1,2014"));//Returns:1178370
Inmath,thecompositionofthefandgvariablesisdefinedasf(g(x)).InJavaScript,thiscanbewrittenas:
varcompose=function(f,g){
returnfunction(x){
returnf(g(x));
};
};
Butifweleftitatthat,wewouldlosetrackofthethiskeyword,amongotherproblems.Thesolutionistousetheapply()andcall()utilities.Comparedtocurry,thecompose()polyfillisquitesimple.
Function.prototype.compose=function(prevFunc){
varnextFunc=this;
returnfunction(){
returnnextFunc.call(this,prevFunc.apply(this,arguments));
}
}
Toshowhowit’sused,let’sbuildacompletelycontrivedexample,asfollows:
functionfunction1(a){returna+'1';}
functionfunction2(b){returnb+'2';}
functionfunction3(c){returnc+'3';}
varcomposition=function3.compose(function2).compose(function1);
console.log(composition('count'));//returns'count123'
Didyounoticethatthefunction3parameterwasappliedfirst?Thisisveryimportant.Functionsareappliedfromrighttoleft.
Sequence–composeinreverseBecausemanypeopleliketoreadthingsfromthelefttotheright,itmightmakesensetoapplythefunctionsinthatordertoo.We’llcallthisasequenceinsteadofacomposition.
Toreversetheorder,allweneedtodoisswapthenextFuncandprevFuncparameters.
Function.prototype.sequence=function(prevFunc){
varnextFunc=this;
returnfunction(){
returnprevFunc.call(this,nextFunc.apply(this,arguments));
}
}
Thisallowsustonowcallthefunctionsinamorenaturalorder.
varsequences=function1.sequence(function2).sequence(function3);
console.log(sequences('count'));//returns'count123'
CompositionsversuschainsHerearefivedifferentimplementationsofthesamefloorSqrt()functionalcomposition.Theyseemtobeidentical,buttheydeservescrutiny.
functionfloorSqrt1(num){
varsqrtNum=Math.sqrt(num);
varfloorSqrt=Math.floor(sqrtNum);
varstringNum=String(floorSqrt);
returnstringNum;
}
functionfloorSqrt2(num){
returnString(Math.floor(Math.sqrt(num)));
}
functionfloorSqrt3(num){
return[num].map(Math.sqrt).map(Math.floor).toString();
}
varfloorSqrt4=String.compose(Math.floor).compose(Math.sqrt);
varfloorSqrt5=Math.sqrt.sequence(Math.floor).sequence(String);
//allfunctionscanbecalledlikethis:
floorSqrt<N>(17);//Returns:4
Butthereareafewkeydifferencesweshouldgoover:
Obviouslythefirstmethodisverboseandinefficient.Thesecondmethodisaniceone-liner,butthisapproachbecomesveryunreadableafteronlyafewfunctionsareapplied.
NoteTosaythatlesscodeisbetterismissingthepoint.Codeismoremaintainablewhentheeffectiveinstructionsaremoreconcise.Ifyoureducethenumberofcharactersonthescreenwithoutchangingtheeffectiveinstructionscarriedout,thishasthecompleteoppositeeffect—codebecomeshardertounderstand,anddecidedlylessmaintainable;forexample,whenweusenestedternaryoperators,orwechainseveralcommandstogetheronasingleline.Theseapproachesreducetheamountof‘codeonthescreen’,buttheydon’treducethenumberofstepsactuallybeingspecifiedbythatcode.Sotheeffectistoobfuscateandmakethecodehardertounderstand.Thekindofconcisenessthatmakescodeeasiertomaintainisthatwhicheffectivelyreducesthespecifiedinstructions(forexample,byusingasimpleralgorithmthataccomplishesthesameresultwithfewerand/orsimplersteps),orwhenwesimplyreplacecodewithamessage,forinstance,invokingathird-partylibrarywithawell-documentedAPI.
Thethirdapproachisachainofarrayfunctions,notablythemapfunction.Thisworksfairlywell,butitisnotmathematicallycorrect.Here’sourcompose()functioninaction.Allmethodsareforcedtobeunary,purefunctionsthatencouragetheuseofbetter,simpler,andsmallerfunctionsthatdoone
ProgrammingwithcomposeThemostimportantaspectofcomposeisthat,asidefromthefirstfunctionthatisapplied,itworksbestwithpure,unaryfunctions:functionsthattakeonlyoneargument.
Theoutputofthefirstfunctionthatisappliedissenttothenextfunction.Thismeansthatthefunctionmustacceptwhatthepreviousfunctionpassedtoit.Thisisthemaininfluencebehindtypesignatures.
NoteTypeSignaturesareusedtoexplicitlydeclarewhattypesofinputthefunctionacceptsandwhattypeitoutputs.TheywerefirstusedbyHaskell,whichactuallyusedtheminthefunctiondefinitionstobeusedbythecompiler.But,inJavaScript,wejustputtheminacodecomment.Theylooksomethinglikethis:foo::arg1->argN->output
Examples:
//getStringLength::String->IntfunctiongetStringLength(s){return
s.length};
//concatDates::Date->Date->[Date]functionconcatDates(d1,d2){return
[d1,d2]};
//pureFunc::(int->Bool)->[int]->[int]pureFunc(func,arr){return
arr.filter(func)}
Inordertotrulyreapthebenefitsofcompose,anyapplicationwillneedaheftycollectionofunary,purefunctions.Thesearethebuildingblocksthatarecomposedintolargerfunctionsthat,inturn,areusedtomakeapplicationsthatareverymodular,reliable,andmaintainable.
Let’sgothroughanexample.Firstwe’llneedmanybuilding-blockfunctions.Someofthembuildupontheothersasfollows:
//stringToArray::String->[Char]
functionstringToArray(s){returns.split('');}
//arrayToString::[Char]->String
functionarrayToString(a){returna.join('');}
//nextChar::Char->Char
functionnextChar(c){
returnString.fromCharCode(c.charCodeAt(0)+1);}
//previousChar::Char->Char
functionpreviousChar(c){
returnString.fromCharCode(c.charCodeAt(0)-1);}
//higherColorHex::Char->Char
functionhigherColorHex(c){returnc>='f'?'f':
c=='9'?'a':
nextChar(c)}
//lowerColorHex::Char->Char
functionlowerColorHex(c){returnc<='0'?'0':
c=='a'?'9':
previousChar(c);}
//raiseColorHexes::String->String
functionraiseColorHexes(arr){returnarr.map(higherColorHex);}
//lowerColorHexes::String->String
functionlowerColorHexes(arr){returnarr.map(lowerColorHex);}
Nowlet’scomposesomeofthemtogether.
varlighterColor=arrayToString
.compose(raiseColorHexes)
.compose(stringToArray)
vardarkerColor=arrayToString
.compose(lowerColorHexes)
.compose(stringToArray)
console.log(lighterColor('af0189'));//Returns:'bf129a'
console.log(darkerColor('af0189'));//Returns:'9e0078'
Wecanevenusecompose()andcurry()functionstogether.Infact,theyworkverywelltogether.Let’sforgetogetherthecurryexamplewithourcomposeexample.Firstwe’llneedourhelperfunctionsfrombefore.
//component2hex::Ints->Int
functioncomponentToHex(c){
varhex=c.toString(16);
returnhex.length==1?"0"+hex:hex;
}
//nums2hex::Ints*->Int
functionnums2hex(){
returnArray.prototype.map.call(arguments,componentToHex).join('');
}
Firstweneedtomakethecurriedandpartial-appliedfunctions,thenwecancomposethemtoourothercomposedfunctions.
varlighterColors=lighterColor
.compose(nums2hex.curry());
vardarkerRed=darkerColor
.compose(nums2hex.partialApply(255));
VarlighterRgb2hex=lighterColor
.compose(nums2hex.partialApply());
console.log(lighterColors(123,0,22));//Returns:8cff11
console.log(darkerRed(123,0));//Returns:ee6a00
console.log(lighterRgb2hex(123,200,100));//Returns:8cd975
Therewehaveit!Thefunctionsreadreallywellandmakealotofsense.Wewereforcedtobeginwithlittlefunctionsthatjustdidonething.Thenwewereabletoputtogetherfunctionswithmoreutility.
Let’slookatonelastexample.Here’safunctionthatlightensanRBGvaluebyavariableamount.Thenwecanusecompositiontocreatenewfunctionsfromit.
//lighterColorNumSteps::string->num->string
functionlighterColorNumSteps(color,n){
for(vari=0;i<n;i++){
color=lighterColor(color);
}
returncolor;
}
//nowwecancreatefunctionslikethis:
varlighterRedNumSteps=lighterColorNumSteps.curry().compose(reds)(0,0);
//andusethemlikethis:
console.log(lighterRedNumSteps(5));//Return:'ff5555'
console.log(lighterRedNumSteps(2));//Return:'ff2222'
Inthesameway,wecouldeasilycreatemorefunctionsforcreatinglighteranddarkerblues,greens,grays,purples,anythingyouwant.ThisisareallygreatwaytoconstructanAPI.
Wejustbarelyscratchedthesurfaceofwhatfunctioncompositioncando.WhatcomposedoesistakecontrolawayfromJavaScript.NormallyJavaScriptwillevaluatelefttoright,butnowtheinterpreterissaying“OK,somethingelseisgoingtotakecareofthis,I’lljustmoveontothenext.”Andnowthecompose()functionhascontrolovertheevaluationsequence!
ThisishowLazy.js,Bacon.jsandothershavebeenabletoimplementthingssuchaslazyevaluationandinfinitesequences.Upnext,we’lllookintohowthoselibrariesareused.
MostlyfunctionalprogrammingWhatisaprogramwithoutsideeffects?Aprogramthatdoesnothing.
Complementingourcodewithfunctionalcodewithunavoidableside-effectscanbecalled“mostlyfunctionalprogramming.”Usingmultipleparadigmsinthesamecodebaseandapplyingthemwheretheyaremostoptimalisthebestapproach.Mostlyfunctionalprogrammingishoweventhepure,traditionalfunctionalprogramsaremodelled:keepmostofthelogicinpurefunctionsandinterfacewithimperativecode.
Andthisishowwe’regoingtowritealittleapplicationofourown.
Inthisexample,wehaveabossthattellsusthatweneedawebapplicationforourcompanythattracksthestatusoftheemployees’availability.Alltheemployeesatthisfictionalcompanyonlyhaveonejob:usingourwebsite.Staffwillsigninwhentheygettoworkandsignoutwhentheyleave.Butthat’snotenough,italsoneedstoautomaticallyupdatethecontentasitchanges,soourbossdoesn’thavetokeeprefreshingthepages.
We’regoingtouseLazy.jsasourfunctionallibrary.Andwe’realsogoingtobelazy:insteadofworryingabouthandlingalltheuserslogginginandout,WebSockets,databases,andmore,we’lljustpretendthere’sagenericapplicationobjectthatdoesthisforusandjusthappenstohavetheperfectAPI.
Sofornow,let’sjustgettheuglypartsoutoftheway,thepartsthatinterfaceandcreateside-effects.
functionReceptor(name,available){
this.name=name;
this.available=available;//mutablestate
this.render=function(){
output='<li>';
output+=this.available?
this.name+'isavailable':
this.name+'isnotavailable';
output+='</li>';
returnoutput;
}
}
varme=newReceptor;
varreceptors=app.getReceptors().push(me);
app.container.innerHTML=receptors.map(function(r){
returnr.render();
}).join('');
Thiswouldbesufficientforjustdisplayingalistofavailabilities,butwewantittobereactive,whichbringsustoourfirstobstacle.
ByusingtheLazy.jslibrarytostoretheobjectsinasequence,whichwon’tactuallycomputeanythinguntilthetoArray()methodiscalled,wecantakeadvantageofitslazinesstoprovideasortoffunctionalreactiveprogramming.
varlazyReceptors=Lazy(receptors).map(function(r){
returnr.render();
});
app.container.innerHTML=lazyReceptors.toArray().join('');
BecausetheReceptor.render()methodreturnsnewHTMLinsteadofmodifyingthecurrentHTML,allwehavetodoissettheinnerHTMLparametertoitsoutput.
We’llalsohavetotrustthatourgenericapplicationforusermanagementwillprovidecallbackmethodsforustouse.
app.onUserLogin=function(){
this.available=true;
app.container.innerHTML=lazyReceptors.toArray().join('');
};
app.onUserLogout=function(){
this.available=false;
app.container.innerHTML=lazyReceptors.toArray().join('');
};
Thisway,anytimeauserlogsinorout,thelazyReceptorsparameterwillbecomputedagainandtheavailabilitylistwillbeprintedwiththemostrecentvalues.
HandlingeventsButwhatiftheapplicationdoesn’tprovidecallbacksforwhentheuserlogsinandout?Callbacksaremessyandcanquicklyturnaprogramintospaghetticode.Instead,wecandetermineitourselvesbyobservingtheuserdirectly.Iftheuserhasthewebpageinfocus,thenhe/shemustbeactiveandavailable.WecanuseJavaScript’sfocusandblureventsforthis.
window.addEventListener('focus',function(event){
me.available=true;
app.setReceptor(me.name,me.available);//justgowithit
container.innerHTML=lazyReceptors.toArray().join('');
});
window.addEventListener('blur',function(event){
me.available=false;
app.setReceptor(me.name,me.available);
container.innerHTML=lazyReceptors.toArray().join('');
});
Waitasecond,aren’teventsreactivetoo?Cantheybelazilycomputedaswell?TheycanintheLazy.jslibrary,wherethere’sevenahandymethodforthis.
varfocusedReceptors=Lazy.events(window,"focus").each(function(e){
me.available=true;
app.setReceptor(me.name,me.available);
container.innerHTML=lazyReceptors.toArray().join('');
});
varblurredReceptors=Lazy.events(window,"blur").each(function(e){
me.available=false;
app.setReceptor(me.name,me.available);
container.innerHTML=lazyReceptors.toArray().join('');
});
Easyaspie.
NoteByusingtheLazy.jslibrarytohandleevents,wecancreateaninfinitesequenceofevents.Eachtimetheeventisfired,theLazy.each()functionisabletoiterateonemoretime.
Ourbosslikestheapplicationsofar,butshepointsoutthatifanemployeeneverlogsoutbeforeleavingforthedaywithoutclosingthepage,thentheapplicationsaystheemployeeisstillavailable.
Tofigureoutifanemployeeisactiveonthewebsite,wecanmonitorthekeyboardandmouseevents.Let’ssaythey’reconsideredtobeunavailableafter30minutesofnoactivity.
vartimeout=null;
varinputs=Lazy.events(window,"mousemove").each(function(e){
me.available=true;
container.innerHTML=lazyReceptors.toArray().join('');
clearTimeout(timeout);
timeout=setTimeout(function(){
me.available=false;
container.innerHTML=lazyReceptors.toArray().join('');
},1800000);//30minutes
});
TheLazy.jslibraryhasmadeitveryeasyforustohandleeventsasaninfinitestreamthatwecanmapover.Itmakesthispossiblebecauseitusesfunctioncompositiontotakecontroloftheorderofexecution.
Butthere’salittleproblemwithallofthis.Whatiftherearenouserinputeventsthatwecanlatchonto?Whatif,instead,thereisapropertyvaluethatchangesallthetime?Inthenextsection,we’llinvestigateexactlythisissue.
FunctionalreactiveprogrammingLet’sbuildanotherkindofapplicationthatworksinmuchthesameway;onethatusesfunctionalprogrammingtoreacttochangesinstate.But,thistime,theapplicationwon’tbeabletorelyoneventlisteners.
ImagineforamomentthatyouworkforanewsmediacompanyandyourbosstellsyoutocreateawebapplicationthattracksgovernmentelectionresultsonElectionDay.Dataiscontinuouslyflowinginaslocalprecinctsturnintheirresults,sotheresultstodisplayonthepageareveryreactive.Butwealsoneedtotracktheresultsbyeachregion,sotherewillbemultipleobjectstotrack.
Ratherthancreatingabigobject-orientedhierarchytomodeltheinterface,wecandescribeitdeclarativelyasimmutabledata.Wecantransformitwithchainsofpureandsemi-purefunctionswhoseonlyultimatesideeffectsareupdatingwhateverbitsofstateabsolutelymustbeheldonto(ideally,notmany).
Andwe’llusetheBacon.jslibrary,whichwillallowustoquicklydevelopFunctionalReactiveProgramming(FRP)applications.Theapplicationwillonlybeusedonedayoutoftheyear(ElectionDay),andourbossthinksitshouldtakeaproportionalamountoftime.WithfunctionalprogrammingandalibrarysuchasBacon.js,we’llgetitdoneinhalfthetime.
Butfirst,we’regoingtoneedsomeobjectstorepresentthevotingregions,suchasstates,provinces,districts,andsoon.
functionRegion(name,percent,parties){
//mutableproperties:
this.name=name;
this.percent=percent;//%ofprecinctsreported
this.parties=parties;//politicalparties
//returnanHTMLrepresentation
this.render=function(){
varlis=this.parties.map(function(p){
return'<li>'+p.name+':'+p.votes+'</li>';
});
varoutput='<h2>'+this.name+'</h2>';
output+='<ul>'+lis.join('')+'</ul>';
output+='Percentreported:'+this.percent;
returnoutput;
}
}
functiongetRegions(data){
returnJSON.parse(data).map(function(obj){
returnnewRegion(obj.name,obj.percent,obj.parties);
});
}
varurl='http://api.server.com/election-data?format=json';
vardata=jQuery.ajax(url);
varregions=getRegions(data);
app.container.innerHTML=regions.map(function(r){
returnr.render();
}).join('');
Whiletheabovewouldbesufficientforjustdisplayingastaticlistofelectionresults,weneedawaytoupdatetheregionsdynamically.It’stimetocookupsomeBaconandFRP.
ReactivityBaconhasafunction,Bacon.fromPoll(),thatletsuscreateaneventstream,wheretheeventisjustafunctionthatiscalledonthegiveninterval.Andthestream.subscribe()functionletsussubscribeahandlerfunctiontothestream.Becauseit’slazy,thestreamwillnotactuallydoanythingwithoutasubscriber.
vareventStream=Bacon.fromPoll(10000,function(){
returnBacon.Next;
});
varsubscriber=eventStream.subscribe(function(){
varurl='http://api.server.com/election-data?format=json';
vardata=jQuery.ajax(url);
varnewRegions=getRegions(data);
container.innerHTML=newRegions.map(function(r){
returnr.render();
}).join('');
});
Byessentiallyputtingitinaloopthatrunsevery10seconds,wecouldgetthejobdone.Butthismethodwouldhammer-pingthenetworkandisincrediblyinefficient.Thatwouldnotbeveryfunctional.Instead,let’sdigalittledeeperintotheBacon.jslibrary.
InBacon,thereareEventStreamsandPropertiesparameters.Propertiescanbethoughtofas“magic”variablesthatchangeovertimeinresponsetoevents.They’renotreallymagicbecausetheystillrelyonastreamofevents.ThePropertychangesovertimeinrelationtoitsEventStream.
TheBacon.jslibraryhasanothertrickupitssleeve.TheBacon.fromPromise()functionisawaytoemiteventsintoastreambyusingpromises.AndasofjQueryversion1.5.0,jQueryAJAXimplementsthepromisesinterface.SoallweneedtodoiswriteanAJAXsearchfunctionthatemitseventswhentheasynchronouscalliscomplete.Everytimethepromiseisresolved,itcallstheEvenStream’ssubscribers.
varurl='http://api.server.com/election-data?format=json';
vareventStream=Bacon.fromPromise(jQuery.ajax(url));
varsubscriber=eventStream.onValue(function(data){
newRegions=getRegions(data);
container.innerHTML=newRegions.map(function(r){
returnr.render();
}).join('');
}
Apromisecanbethoughtofasaneventualvalue;withtheBacon.jslibrary,wecanlazilywaitontheeventualvalues.
PuttingitalltogetherNowthatwehavethereactivitycovered,wecanfinallyplaywithsomecode.
Wecanmodifythesubscriberwithchainsofpurefunctionstodothingssuchasaddingupatotalandfilteringoutunwantedresults,andwedoitallwithinonclick()handlerfunctionsforbuttonsthatwecreate.
//createtheeventStreamoutsideofthefunctions
vareventStream=Bacon.onPromise(jQuery.ajax(url));
varsubscribe=null;
varurl='http://api.server.com/election-data?format=json';
//ourun-modifiedsubscriber
$('button#showAll').click(function(){
varsubscriber=eventStream.onValue(function(data){
varnewRegions=getRegions(data).map(function(r){
returnnewRegion(r.name,r.percent,r.parties);
});
container.innerHTML=newRegions.map(function(r){
returnr.render();
}).join('');
});
});
//abuttonforshowingthetotalvotes
$('button#showTotal').click(function(){
varsubscriber=eventStream.onValue(function(data){
varemptyRegion=newRegion('empty',0,[{
name:'Republican',votes:0
},{
name:'Democrat',votes:0
}]);
vartotalRegions=getRegions(data).reduce(function(r1,r2){
newParties=r1.parties.map(function(x,i){
return{
name:r1.parties[i].name,
votes:r1.parties[i].votes+r2.parties[i].votes
};
});
newRegion=newRegion('Total',(r1.percent+r2.percent)/2,
newParties);
returnnewRegion;
},emptyRegion);
container.innerHTML=totalRegions.render();
});
});
//abuttonforonlydisplayingregionsthatarereporting>50%
$('button#showMostlyReported').click(function(){
varsubscriber=eventStream.onValue(function(data){
varnewRegions=getRegions(data).map(function(r){
if(r.percent>50)returnr;
elsereturnnull;
}).filter(function(r){returnr!=null;});
container.innerHTML=newRegions.map(function(r){
returnr.render();
}).join('');
});
});
Thebeautyofthisisthat,whenusersclickbetweenthebuttons,theeventstreamdoesn’tchangebutthesubscriberdoes,whichmakesitallworksmoothly.
SummaryJavaScriptisabeautifullanguage.
Itsinnerbeautyreallyshineswithfunctionalprogramming.It’swhatempowersitsexcellentextendibility.Justthefactthatitallowsfirst-classfunctionsthatcandosomanythingsiswhatopensthefunctionalfloodgates.Conceptsbuildontopofeachother,stackinguphigherandhigher.
Inthischapter,wedovehead-firstintothefunctionalparadigminJavaScript.Wecoveredfunctionfactories,currying,functioncompositionandeverythingrequiredtomakeitwork.Webuiltanextremelymodularapplicationthatusedtheseconcepts.Andthenweshowedhowtousesomefunctionallibrariesthatusethesesameconceptsthemselves,namelyfunctioncomposition,tomanipulatetheorderofexecution.
Throughoutthechapter,wecoveredseveralstylesoffunctionalprogramming:datagenericprogramming,mostly-functionalprogramming,andfunctionalreactiveprogramming.They’reallnotthatdifferentfromeachother,they’rejustdifferentpatternsforapplyingfunctionalprogramingindifferentsituations.
Inthepreviouschapter,somethingcalledCategoryTheorywasbrieflymentioned.Inthenextchapter,we’regoingtolearnalotmoreaboutwhatitisandhowtouseit.
Chapter5.CategoryTheoryThomasWatsonwasfamouslyquotedassaying,“Ithinkthereisaworldmarketformaybefivecomputers”.Thatwasin1948.Backthen,everybodyknewthatcomputerswouldonlybeusedfortwothings:mathandengineering.Noteventhebiggestmindsintechcouldpredictthat,oneday,computerswouldbeabletotranslateSpanishtoEnglish,orsimulateentireweathersystems.Atthetime,thefastestmachinewasIBM’sSSEC,clockinginat50multiplicationspersecond,thedisplayterminalwasn’tdueuntil15yearslaterandmultiple-processingmeantmultipleuserterminalssharingasingleprocessor.Thetransistorchangedeverything,buttech’svisionariesstillmissedthemark.KenOlsonmadeanotherfamouslyfoolishpredictionwhen,in1977,hesaid“Thereisnoreasonanyonewouldwantacomputerintheirhome”.
Itseamsobvioustousnowthatcomputersarenotjustforscientistsandengineers,butthat’shindsight.Theideathatmachinescandomorethanjustmathwasanythingbutintuitive70yearsago.Watsondidn’tjustfailtorealizehowcomputerscouldtransformasociety,hefailedtorealizethetransformativeandevolvingpowersofmathematics.
Butthepotentialofcomputersandmathwasnotlostoneverybody.JohnMcCarthyinventedLispin1958,arevolutionaryalgorithm-basedlanguagethatusheredinaneweraincomputing.Sinceitsinception,Lispwasinstrumentalintheideaofusingabstractionlayers—compilers,interpreters,virtualization—topushforwardtheprogressionofcomputersfromhardcoremathmachinestowhattheyaretoday.
FromLispcameScheme,adirectancestorofJavaScript.Nowthatbringsusfullcircle.Ifcomputersare,attheircore,machinesthatjustdomath,thenitstandstoreasonthatamath-basedprogrammingparadigmwouldexcel.
Theterm“math”isbeingusedherenottodescribethe“numbercrunching”thatcomputerscanobviouslydo,buttodescribediscretemathematics:thestudyofdiscrete,mathematicalstructuressuchasstatementsinlogicortheinstructionsofacomputerlanguage.Bytreatingcodeasadiscretemathematicalstructure,wecanapplyconceptsandideasinmathtoit.Thisiswhathasmadefunctionalprogrammingsoinstrumentalinartificialintelligence,graphsearch,patternrecognitionandotherbigchallengesincomputerscience.
Inthischapter,wewillexperimentwithsomeoftheseconceptsandtheirapplicationsineverydayprogrammingchallenges.Theywillinclude:
CategorytheoryMorphismsFunctorsMaybesPromisesLensesFunctioncomposition
Withtheseconcepts,we’llbeabletowriteentirelibrariesandAPIsveryeasilyandsafely.
CategorytheoryCategorytheoryisthetheoreticalconceptthatempowersfunctioncomposition.Categorytheoryandfunctioncompositiongotogetherlikeenginedisplacementandhorsepower,likeNASAandthespaceshuttle,likegoodbeerandamugtopouritin.Basically,youcan’thaveonewithouttheother.
CategorytheoryinanutshellCategorytheoryreallyisn’ttoodifficultaconcept.Itsplaceinmathislargeenoughtofillupanentiregraduate-levelcollegecourse,butitsplaceincomputerprogrammingcanbesummedupquiteeasily.
Einsteinoncesaid,“Ifyoucan’texplainittoa6-year-old,youdon’tknowityourself”.Thus,inthespiritofexplainingittoa6-year-old,categorytheoryisjustconnectingthedots.Althoughitmaybegrosslyover-simplifyingcategorytheory,itdoesdoagoodjobofexplainingwhatweneedtoknowinastraightforwardmanner.
Firstyou’llneedtoknowsometerminology.Categoriesarejustsetswiththesametype.InJavaScript,they’rearraysorobjectsthatcontainvariablesthatareexplicitlydeclaredasnumbers,strings,Booleans,dates,nodes,andsoon.Morphismsarepurefunctionsthat,whengivenaspecificsetofinputs,alwaysreturnthesameoutput.Homomorphicoperationsarerestrictedtoasinglecategory,whilepolymorphicoperationscanoperateonmultiplecategories.Forexample,thehomomorphicfunctionmultiplicationonlyworksonnumbers,butthepolymorphicfunctionadditioncanworkonstringstoo.
Thefollowingdiagramshowsthreecategories—A,B,andC—andtwomorphisms—ƒandɡ.
Categorytheorytellsusthat,whenwehavetwomorphismswherethecategoryofthefirstoneistheexpectedinputoftheother,thentheycanbecomposedtothefollowing:
Theƒogsymbolisthecompositionofmorphismsƒandg.Nowwecanjustconnectthedots.
Andthat’sallitreallyis,justconnectingdots.
TypesafetyLet’sconnectsomedots.Categoriescontaintwothings:
1. Objects(inJavaScript,types).2. Morphisms(inJavaScript,purefunctionsthatonlyworkontypes).
Thesearethetermsgiventocategorytheorybymathematicians,sothereissomeunfortunatenomenclatureoverloadingwithourJavaScriptterminology.ObjectsincategorytheoryaremorelikevariableswithanexplicitdatatypeandnotcollectionsofpropertiesandvalueslikeintheJavaScriptdefinitionofobjects.Morphismsarejustpurefunctionsthatusethosetypes.
SoapplyingtheideaofcategorytheorytoJavaScriptisprettyeasy.UsingcategorytheoryinJavaScriptmeansworkingwithonecertaindatatypepercategory.Datatypesarenumbers,strings,arrays,dates,objects,Booleans,andsoon.But,withnostricttypesysteminJavaScript,thingscangoawry.Sowe’llhavetoimplementourownmethodofensuringthatthedataiscorrect.
TherearefourprimitivedatatypesinJavaScript:numbers,strings,Booleans,andfunctions.Wecancreatetypesafetyfunctionsthateitherreturnthevariableorthrowanerror.Thisfulfilstheobjectaxiomofcategories.
varstr=function(s){
if(typeofs==="string"){
returns;
}
else{
thrownewTypeError("Error:Stringexpected,"+typeofs+"given.");
}
}
varnum=function(n){
if(typeofn==="number"){
returnn;
}
else{
thrownewTypeError("Error:Numberexpected,"+typeofn+"given.");
}
}
varbool=function(b){
if(typeofb==="boolean"){
returnb;
}
else{
thrownewTypeError("Error:Booleanexpected,"+typeofb+"
given.");
}
}
varfunc=function(f){
if(typeoff==="function"){
returnf;
}
else{
thrownewTypeError("Error:Functionexpected,"+typeoff+"
given.");
}
}
However,there’salotofrepeatedcodehereandthatisn’tveryfunctional.Instead,wecancreateafunctionthatreturnsanotherfunctionthatisthetypesafetyfunction.
vartypeOf=function(type){
returnfunction(x){
if(typeofx===type){
returnx;
}
else{
thrownewTypeError("Error:"+type+"expected,"+typeofx+"given.");
}
}
}
varstr=typeOf('string'),
num=typeOf('number'),
func=typeOf('function'),
bool=typeOf('boolean');
Now,wecanusethemtoensurethatourfunctionsbehaveasexpected.
//unprotectedmethod:
varx='24';
x+1;//willreturn'241',not25
//protectedmethod
//plusplus::Int->Int
functionplusplus(n){
returnnum(n)+1;
}
plusplus(x);//throwserror,preferredoverunexpectedoutput
Let’slookatameatierexample.IfwewanttocheckthelengthofaUnixtimestampthatisreturnedbytheJavaScriptfunctionDate.parse(),notasastringbutasanumber,thenwe’llhavetouseourstr()function.
//timestampLength::String->Int
functiontimestampLength(t){returnnum(str(t).length);}
timestampLength(Date.parse('12/31/1999'));//throwserror
timestampLength(Date.parse('12/31/1999')
.toString());//returns12
Functionslikethisthatexplicitlytransformonetypetoanother(ortothesametype)arecalledmorphisms.Thisfulfilsthemorphismaxiomofcategorytheory.TheseforcedtypedeclarationsviathetypesafetyfunctionsandthemorphismsthatusethemareeverythingweneedtorepresentthenotionofacategoryinJavaScript.
ObjectidentitiesThere’soneotherimportantdatatype:objects.
varobj=typeOf('object');
obj(123);//throwserror
obj({x:'a'});//returns{x:'a'}
However,objectsaredifferent.Theycanbeinherited.Everythingthatisnotaprimitive—numbers,strings,Booleans,andfunctions—isanobject,includingarrays,dates,elements,andmore.
There’snowaytoknowwhattypeofobjectsomethingis,asintoknowwhatsub-typeaJavaScript‘object’is,fromthetypeofkeyword,sowe’llhavetoimprovise.ObjectshaveatoString()functionthatwecanhijackforthispurpose.
varobj=function(o){
if(Object.prototype.toString.call(o)==="[objectObject]"){
returno;
}
else{
thrownewTypeError("Error:Objectexpected,somethingelsegiven.");
}
}
Again,withalltheobjectsoutthere,weshouldimplementsomecodere-use.
varobjectTypeOf=function(name){
returnfunction(o){
if(Object.prototype.toString.call(o)==="[object"+name+"]"){
returno;
}
else{
thrownewTypeError("Error:'+name+'expected,somethingelse
given.");
}
}
}
varobj=objectTypeOf('Object');
vararr=objectTypeOf('Array');
vardate=objectTypeOf('Date');
vardiv=objectTypeOf('HTMLDivElement');
Thesewillbeveryusefulforournexttopic:functors.
FunctorsWhilemorphismsaremappingsbetweentypes,functorsaremappingsbetweencategories.Theycanbethoughtofasfunctionsthatliftvaluesoutofacontainer,morphthem,andthenputthemintoanewcontainer.Thefirstinputisamorphismforthetypeandthesecondinputisthecontainer.
NoteThetypesignatureforfunctorslookslikethis:
//myFunctor::(a->b)->fa->fb
Thissays,“givemeafunctionthattakesaandreturnsbandaboxthatcontainsa(s),andI’llreturnaboxthatcontainsb(s).
CreatingfunctorsItturnsoutwealreadyhaveonefunctor:map().Itgrabsthevalueswithinthecontainer,anarray,andappliesafunctiontoit.
[1,4,9].map(Math.sqrt);//Returns:[1,2,3]
However,we’llneedtowriteitasaglobalfunctionandnotasamethodofthearrayobject.Thiswillallowustowritecleaner,safercodelateron.
//map::(a->b)->[a]->[b]
varmap=function(f,a){
returnarr(a).map(func(f));
}
Thisexampleseemslikeacontrivedwrapperbecausewe’rejustpiggybackingontothemap()function.Butitservesapurpose.Itprovidesatemplateformapsofothertypes.
//strmap::(str->str)->str->str
varstrmap=function(f,s){
returnstr(s).split('').map(func(f)).join('');
}
//MyObject#map::(myValue->a)->a
MyObject.prototype.map(f{
returnfunc(f)(this.myValue);
}
ArraysandfunctorsArraysarethepreferredwaytoworkwithdatainfunctionalJavaScript.
Isthereaneasierwaytocreatefunctorsthatarealreadyassignedtoamorphism?Yes,andit’scalledarrayOf.Whenyoupassinamorphismthatexpectsanintegerandreturnsanarray,yougetbackamorphismthatexpectsanarrayofintegersandreturnsanarrayofarrays.
Itisnotafunctoritself,butitallowsustocreatefunctorsfrommorphisms.
//arrayOf::(a->b)->([a]->[b])
vararrayOf=function(f){
returnfunction(a){
returnmap(func(f),arr(a));
}
}
Here’showtocreatefunctorsbyusingmorphism:
varplusplusall=arrayOf(plusplus);//plusplusisourmorphism
console.log(plusplusall([1,2,3]));//returns[2,3,4]
console.log(plusplusall([1,'2',3]));//erroristhrown
TheinterestingpropertyofthearrayOffunctoristhatitworksontypesafetiesaswell.Whenyoupassinthetypesafetyfunctionforstrings,yougetbackatypesafetyfunctionforanarrayofstrings.Thetypesafetiesaretreatedliketheidentityfunctionmorphism.Thiscanbeveryusefulforensuringthatanarraycontainsallthecorrecttypes.
varstrs=arrayOf(str);
console.log(strs(['a','b','c']));//returns['a','b','c']
console.log(strs(['a',2,'c']));//throwserror
Functioncompositions,revisitedFunctionsareanothertypeofprimitivethatwecancreateafunctorfor.Andthatfunctoriscalledfcompose.Wedefinedfunctorsassomethingthattakesavaluefromacontainerandappliesafunctiontoit.Whenthatcontainerisafunction,wejustcallittogetitsinnervalue.
Wealreadyknowwhatfunctioncompositionsare,butlet’slookatwhattheycandoinacategorytheory-drivenenvironment.
Functioncompositionsareassociative.Ifyourhighschoolalgebrateacherwaslikemine,shetaughtyouwhatthepropertyisbutnotwhatitcando.Inpractice,composeiswhattheassociativepropertycando.
Wecandoanyinner-compose,itdoesn’tmatterhowit’sgrouped.Thisisnottobeconfusedwiththecommutativeproperty.ƒogdoesnotalwaysequalgoƒ.Inotherwords,thereverseofthefirstwordofastringisnotthesameasthefirstwordofthereverseofastring.
Whatthisallmeansisthatitdoesn’tmatterwhichfunctionsareappliedandinwhatorder,aslongastheinputofeachfunctionscomesfromtheoutputofthepreviousfunction.Butwait,ifthefunctionontherightreliesonthefunctionontheleft,thencan’ttherebeonlyoneorderofevaluation?Lefttoright?True,butifit’sencapsulated,thenwecancontrolithoweverwefeelfit.ThisiswhatempoweredlazyevaluationinJavaScript.
Let’srewritefunctioncomposition,notasanextensionofthefunctionprototype,butasastand-alonefunctionthatwillallowustogetmoreoutofit.Thebasicformisasfollows:
varfcompose=function(f,g){
returnfunction(){
returnf.call(this,g.apply(this,arguments));
};
};
Butwe’llneedittoworkonanynumberofinputs.
varfcompose=function(){
//firstmakesureallargumentsarefunctions
varfuncs=arrayOf(func)(arguments);
//returnafunctionthatappliesallthefunctions
returnfunction(){
varargsOfFuncs=arguments;
for(vari=funcs.length;i>0;i-=1){
argsOfFuncs=[funcs[i].apply(this,args)];
}
returnargs[0];
};
};
//example:
varf=fcompose(negate,square,mult2,add1);
f(2);//Returns:-36
Nowthatwe’veencapsulatedthefunctions,wehavecontroloverthem.Wecouldrewritethecomposefunctionsuchthateachfunctionacceptsanotherfunctionasinput,storesit,andgivesbackanobjectthatdoesthesame.Insteadofacceptinganarrayasaninput,doingsomethingwithit,andthengivingbackanewarrayforeachoperation,wecanacceptasinglearrayforeachelementinthesource,performalloperationscombined(everymap(),filter(),andsoon,composedtogether),andfinallystoretheresultsinanewarray.Thisislazyevaluationviafunctioncomposition.Noreasontoreinventthewheelhere.Manylibrarieshaveaniceimplementationofthisconcept,includingtheLazy.js,Bacon.jsandwu.jslibraries.
There’salotmorewecandoasaresultofthisdifferentmodel:asynchronousiteration,asynchronouseventhandling,lazyevaluation,andevenautomaticparallelization.
NoteAutomaticparallelization?There’sawordforthatinthecomputerscienceindustry:IMPOSSIBLE.Butisitreallyimpossible?ThenextevolutionaryleapinMoore’slawmightbeacompilerthatparallelizesourcodeforus,andcouldfunctioncompositionbeit?
No,itdoesn’tquiteworkthatway.TheJavaScriptengineiswhatisreallydoingtheparallelization,notautomaticallybutwithwellthought-outcode.Composejustgivestheenginethechancetosplititintoparallelprocesses.Butthatinitselfisprettycool.
MonadsMonadsaretoolsthathelpyoucomposefunctions.
Likeprimitivetypes,monadsarestructuresthatcanbeusedasthecontainersthatfunctors“reachinto”.Thefunctorsgrabthedata,dosomethingtoit,putitintoanewmonad,andreturnit.
Therearethreemonadswe’llfocuson:
MaybesPromisesLenses
Soinadditiontoarrays(map)andfunctions(compose),we’llhavefivefunctors(map,compose,maybe,promiseandlens).Thesearejustsomeofthemanyotherfunctorsandmonadsthatareoutthere.
MaybesMaybesallowustogracefullyworkwithdatathatmightbenullandtohavedefaults.Amaybeisavariablethateitherhassomevalueoritdoesn’t.Anditdoesn’tmattertothecaller.
Onitsown,itmightseemlikethisisnotthatbigadeal.Everybodyknowsthatnull-checksareeasilyaccomplishedwithanif-elsestatement:
if(getUsername()==null){
username='Anonymous'){
else{
username=getUsername();
}
Butwithfunctionalprogramming,we’rebreakingawayfromtheprocedural,line-by-linewayofdoingthingsandinsteadworkingwithpipelinesoffunctionsanddata.Ifwehadtobreakthechaininthemiddlejusttocheckifthevalueexistedornot,wewouldhavetocreatetemporaryvariablesandwritemorecode.Maybesarejusttoolstohelpuskeepthelogicflowingthroughthepipeline.
Toimplementmaybes,we’llfirstneedtocreatesomeconstructors.
//theMaybemonadconstructor,emptyfornow
varMaybe=function(){};
//theNoneinstance,awrapperforanobjectwithnovalue
varNone=function(){};
None.prototype=Object.create(Maybe.prototype);
None.prototype.toString=function(){return'None';};
//nowwecanwritethe`none`function
//savesusfromhavingtowrite`newNone()`allthetime
varnone=function(){returnnewNone()};
//andtheJustinstance,awrapperforanobjectwithavalue
varJust=function(x){returnthis.x=x;};
Just.prototype=Object.create(Maybe.prototype);
Just.prototype.toString=function(){return"Just"+this.x;};
varjust=function(x){returnnewJust(x)};
Finally,wecanwritethemaybefunction.Itreturnsanewfunctionthateitherreturnsnothingoramaybe.Itisafunctor.
varmaybe=function(m){
if(minstanceofNone){
returnm;
}
elseif(minstanceofJust){
returnjust(m.x);
}
else{
thrownewTypeError("Error:JustorNoneexpected,"+m.toString()+"
given.");
}
}
Andwecanalsocreateafunctorgeneratorjustlikewedidwitharrays.
varmaybeOf=function(f){
returnfunction(m){
if(minstanceofNone){
returnm;
}
elseif(minstanceofJust){
returnjust(f(m.x));
}
else{
thrownewTypeError("Error:JustorNoneexpected,"+m.toString()+
"given.");
}
}
}
SoMaybeisamonad,maybeisafunctor,andmaybeOfreturnsafunctorthatisalreadyassignedtoamorphism.
We’llneedonemorethingbeforewecanmoveforward.We’llneedtoaddamethodtotheMaybemonadobjectthathelpsususeitmoreintuitively.
Maybe.prototype.orElse=function(y){
if(thisinstanceofJust){
returnthis.x;
}
else{
returny;
}
}
Initsrawform,maybescanbeuseddirectly.
maybe(just(123)).x;//Returns123
maybeOf(plusplus)(just(123)).x;//Returns124
maybe(plusplus)(none()).orElse('none');//returns'none'
Anythingthatreturnsamethodthatisthenexecutediscomplicatedenoughtobebeggingfortrouble.Sowecanmakeitalittlecleanerbycallingonourcurry()function.
maybePlusPlus=maybeOf.curry()(plusplus);
maybePlusPlus(just(123)).x;//returns123
maybePlusPlus(none()).orElse('none');//returnsnone
Buttherealpowerofmaybeswillbecomeclearwhenthedirtybusinessofdirectlycallingthenone()andjust()functionsisabstracted.We’lldothiswithanexampleobjectUser,thatusesmaybesfortheusername.
varUser=function(){
this.username=none();//initiallysetto`none`
};
User.prototype.setUsername=function(name){
this.username=just(str(name));//it'snowa`just
};
User.prototype.getUsernameMaybe=function(){
varusernameMaybe=maybeOf.curry()(str);
returnusernameMaybe(this.username).orElse('anonymous');
};
varuser=newUser();
user.getUsernameMaybe();//Returns'anonymous'
user.setUsername('Laura');
user.getUsernameMaybe();//Returns'Laura'
Andnowwehaveapowerfulandsafewaytodefinedefaults.KeepthisUserobjectinmindbecausewe’llbeusingitlateroninthischapter.
PromisesThenatureofpromisesisthattheyremainimmunetochangingcircumstances.
-FrankUnderwood,HouseofCards
Infunctionalprogramming,we’reoftenworkingwithpipelinesanddataflows:chainsoffunctionswhereeachfunctionproducesadatatypethatisconsumedbythenext.However,manyofthesefunctionsareasynchronous:readFile,events,AJAX,andsoon.Insteadofusingacontinuation-passingstyleanddeeplynestedcallbacks,howcanwemodifythereturntypesofthesefunctionstoindicatetheresult?Bywrappingtheminpromises.
Promisesarelikethefunctionalequivalentofcallbacks.Obviously,callbacksarenotallthatfunctionalbecause,ifmorethanonefunctionismutatingthesamedata,thentherecanberaceconditionsandbugs.Promisessolvethatproblem.
Youshouldusepromisestoturnthis:
fs.readFile("file.json",function(err,val){
if(err){
console.error("unabletoreadfile");
}
else{
try{
val=JSON.parse(val);
console.log(val.success);
}
catch(e){
console.error("invalidjsoninfile");
}
}
});
Intothefollowingcodesnippet:
fs.readFileAsync("file.json").then(JSON.parse)
.then(function(val){
console.log(val.success);
})
.catch(SyntaxError,function(e){
console.error("invalidjsoninfile");
})
.catch(function(e){
console.error("unabletoreadfile")
});
TheprecedingcodeisfromtheREADMEforbluebird:afullfeaturedPromises/A+implementationwithexceptionallygoodperformance.Promises/A+isaspecificationforimplementingpromisesinJavaScript.GivenitscurrentdebatewithintheJavaScriptcommunity,we’llleavetheimplementationsuptothePromises/A+team,asitismuchmorecomplexthanmaybes.
Buthere’sapartialimplementation:
//thePromisemonad
varPromise=require('bluebird');
//thepromisefunctor
varpromise=function(fn,receiver){
returnfunction(){
varslice=Array.prototype.slice,
args=slice.call(arguments,0,fn.length-1),
promise=newPromise();
args.push(function(){
varresults=slice.call(arguments),
error=results.shift();
if(error)promise.reject(error);
elsepromise.resolve.apply(promise,results);
});
fn.apply(receiver,args);
returnpromise;
};
};
Nowwecanusethepromise()functortotransformfunctionsthattakecallbacksintofunctionsthatreturnpromises.
varfiles=['a.json','b.json','c.json'];
readFileAsync=promise(fs.readFile);
vardata=files
.map(function(f){
readFileAsync(f).then(JSON.parse)
})
.reduce(function(a,b){
return$.extend({},a,b)
});
LensesAnotherreasonwhyprogrammersreallylikemonadsisthattheymakewritinglibrariesveryeasy.Toexplorethis,let’sextendourUserobjectwithmorefunctionsforgettingandsettingvaluesbut,insteadofusinggettersandsetters,we’lluselenses.
Lensesarefirst-classgettersandsetters.Theyallowustonotjustgetandsetvariables,butalsotorunfunctionsoverit.Butinsteadofmutatingthedata,theycloneandreturnthenewdatamodifiedbythefunction.Theyforcedatatobeimmutable,whichisgreatforsecurityandconsistencyaswellforlibraries.They’regreatforelegantcodenomatterwhattheapplication,solongastheperformance-hitofintroducingadditionalarraycopiesisnotacriticalissue.
Beforewewritethelens()function,let’slookathowitworks.
varfirst=lens(
function(a){returnarr(a)[0];},//get
function(a,b){return[b].concat(arr(a).slice(1));}//set
);
first([1,2,3]);//outputs1
first.set([1,2,3],5);//outputs[5,2,3]
functiontenTimes(x){returnx*10}
first.modify(tenTimes,[1,2,3]);//outputs[10,2,3]
Andhere’showthelens()functionworks.Itreturnsafunctionwithget,setandmoddefined.Thelens()functionitselfisafunctor.
varlens=fuction(get,set){
varf=function(a){returnget(a)};
f.get=function(a){returnget(a)};
f.set=set;
f.mod=function(f,a){returnset(a,f(get(a)))};
returnf;
};
Let’stryanexample.We’llextendourUserobjectfromthepreviousexample.
//userName::User->str
varuserName=lens(
function(u){returnu.getUsernameMaybe()},//get
function(u,v){//set
u.setUsername(v);
returnu.getUsernameMaybe();
}
);
varbob=newUser();
bob.setUsername('Bob');
userName.get(bob);//returns'Bob'
userName.set(bob,'Bobby');//return'Bobby'
userName.get(bob);//returns'Bobby'
userName.mod(strToUpper,bob);//returns'BOBBY'
strToUpper.compose(userName.set)(bob,'robert');//returns'ROBERT'
userName.get(bob);//returns'robert'
jQueryisamonadIfyouthinkallthisabstractbabbleaboutcategories,functors,andmonadshasnoreal-worldapplication,thinkagain.jQuery,thepopularJavaScriptlibrarythatprovidesanenhancedinterfaceforworkingwithHTMLis,in-fact,amonadiclibrary.
ThejQueryobjectisamonadanditsmethodsarefunctors.Really,they’reaspecialtypeoffunctorcalledendofunctors.Endofunctorsarefunctorsthatreturnthesamecategoryastheinput,thatis,F::X->X.EachjQuerymethodtakesajQueryobjectandreturnsajQueryobject,whichallowsmethodstobechained,andtheywillhavethetypesignaturejFunc::jquery-obj->jquery-obj.
$('li').add('p.me-too').css('color','red').attr({id:'foo'});
ThisisalsowhatempowersjQuery’spluginframework.IftheplugintakesajQueryobjectasinputandreturnsoneasoutput,thenitcanbeinsertedintothechain.
Let’slookathowjQuerywasabletoimplementthis.
Monadsarethecontainersthatthefunctors“reachinto”togetthedata.Inthisway,thedatacanbeprotectedandcontrolledbythelibrary.jQueryprovidesaccesstotheunderlyingdata,awrappedsetofHTMLelements,viaitsmanymethods.
ThejQueryobjectitselfiswrittenastheresultofananonymousfunctioncall.
varjQuery=(function(){
varj=function(selector,context){
varjq-obj=newj.fn.init(selector,context);
returnjq-obj;
};
j.fn=j.prototype={
init:function(selector,context){
if(!selector){
returnthis;
}
}
};
j.fn.init.prototype=j.fn;
returnj;
})();
InthishighlysimplifiedversionofjQuery,itreturnsafunctionthatdefinesthejobject,whichisactuallyjustanenhancedinitconstructor.
var$=jQuery();//thefunctionisreturnedandassignedto`$`
varx=$('#select-me');//jQueryobjectisreturned
Inthesamewaythatfunctorsliftvaluesoutofacontainer,jQuerywrapstheHTMLelementsandprovidesaccesstothemasopposedtomodifyingtheHTMLelementsdirectly.
jQuerydoesn’tadvertisethisoften,butithasitsownmap()methodforliftingtheHTMLelementobjectsoutofthewrapper.Justlikethefmap()method,theelementsarelifted,
somethingisdonewiththem,andthenthey’replacedbackintothecontainer.ThisishowmanyofjQuery’scommandsworkinthebackend.
$('li').map(function(index,element){
//dosomethingtotheelement
returnelement
});
AnotherlibraryforworkingwithHTMLelements,Prototype,doesnotworklikethis.PrototypealterstheHTMLelementsdirectlyviahelpers.Consequently,ithasnotfairedaswellintheJavaScriptcommunity.
ImplementingcategoriesIt’sabouttimeweformallydefinedcategorytheoryasJavaScriptobjects.Categoriesareobjects(types)andmorphisms(functionsthatonlyworkonthosetypes).It’sanextremelyhigh-level,totally-declarativewaytoprogram,butitensuresthatthecodeisextremelysafeandreliable—perfectforAPIsandlibrariesthatareworriedaboutconcurrencyandtypesafety.
First,we’llneedafunctionthathelpsuscreatemorphisms.We’llcallithomoMorph()becausethey’llbehomomorphisms.Itwillreturnafunctionthatexpectsafunctiontobepassedinandproducesthecompositionofit,basedontheinputs.Theinputsarethetypesthatthemorphismacceptsasinputandgivesasoutput.Justlikeourtypesignatures,thatis,//morph::num->num->[num],onlythelastoneistheoutput.
varhomoMorph=function(/*input1,input2,...,inputN,output*/){
varbefore=checkTypes(arrayOf(func)
(Array.prototype.slice.call(arguments,0,arguments.length-1)));
varafter=func(arguments[arguments.length-1])
returnfunction(middle){
returnfunction(args){
returnafter(middle.apply(this,before([].slice.apply(arguments))));
}
}
}
//nowwedon'tneedtoaddtypesignaturecomments
//becausenowthey'rebuiltrightintothefunctiondeclaration
add=homoMorph(num,num,num)(function(a,b){returna+b})
add(12,24);//returns36
add('a','b');//throwserror
homoMorph(num,num,num)(function(a,b){
returna+b;
})(18,24);//returns42
ThehomoMorph()functionisfairlycomplex.Itusesaclosure(seeChapter2,FundamentalsofFunctionalProgramming)toreturnafunctionthatacceptsafunctionandchecksitsinputandoutputvaluesfortypesafety.Andforthat,itreliesonahelperfunction:checkTypes,whichisdefinedasfollows:
varcheckTypes=function(typeSafeties){
arrayOf(func)(arr(typeSafeties));
varargLength=typeSafeties.length;
returnfunction(args){
arr(args);
if(args.length!=argLength){
thrownewTypeError('Expected'+argLength+'arguments');
}
varresults=[];
for(vari=0;i<argLength;i++){
results[i]=typeSafeties[i](args[i]);
}
returnresults;
}
}
Nowlet’sformallydefinesomehomomorphisms.
varlensHM=homoMorph(func,func,func)(lens);
varuserNameHM=lensHM(
function(u){returnu.getUsernameMaybe()},//get
function(u,v){//set
u.setUsername(v);
returnu.getUsernameMaybe();
}
)
varstrToUpperCase=homoMorph(str,str)(function(s){
returns.toUpperCase();
});
varmorphFirstLetter=homoMorph(func,str,str)(function(f,s){
returnf(s[0]).concat(s.slice(1));
});
varcapFirstLetter=homoMorph(str,str)(function(s){
returnmorphFirstLetter(strToUpperCase,s)
});
Finally,wecanbringitonhome.Thefollowingexampleincludesfunctioncomposition,lenses,homomorphisms,andmore.
//homomorphiclenses
varbill=newUser();
userNameHM.set(bill,'William');//Returns:'William'
userNameHM.get(bill);//Returns:'William'
//compose
varcapatolizedUsername=fcompose(capFirstLetter,userNameHM.get);
capatolizedUsername(bill,'bill');//Returns:'Bill'
//it'sagoodideatousehomoMorphon.setand.gettoo
vargetUserName=homoMorph(obj,str)(userNameHM.get);
varsetUserName=homoMorph(obj,str,str)(userNameHM.set);
getUserName(bill);//Returns:'Bill'
setUserName(bill,'Billy');//Returns:'Billy'
//nowwecanrewritecapatolizeUsernamewiththenewsetter
capatolizedUsername=fcompose(capFirstLetter,setUserName);
capatolizedUsername(bill,'will');//Returns:'Will'
getUserName(bill);//Returns:'will'
Theprecedingcodeisextremelydeclarative,safe,reliable,anddependable.
NoteWhatdoesitmeanforcodetobedeclarative?Inimperativeprogramming,wewritesequencesofinstructionsthattellthemachinehowtodowhatwewant.Infunctionalprogramming,wedescriberelationshipsbetweenvaluesthattellthemachinewhatwewantittocompute,andthemachinefiguresouttheinstructionsequencestomakeithappen.Functionalprogrammingisdeclarative.
EntirelibrariesandAPIscanbeconstructedthiswaythatallowprogrammerstowritecodefreelywithoutworryingaboutconcurrencyandtypesafetybecausethoseworriesarehandledinthebackend.
SummaryAboutoneinevery2,000peoplehasaconditionknownassynesthesia,aneurologicalphenomenoninwhichonesensoryinputbleedsintoanother.Themostcommonforminvolvesassigningcolorswithletters.However,thereisanevenrarerformwheresentencesandparagraphsareassociatedwithtastesandfeelings.
Forthesepeople,theydon’treadwordbyword,sentencebysentence.Theylookatthewholepage/document/programandgetasenseforhowittastes—notinthemouthbutinthemind.Thentheyputthepartsofthetexttogetherlikethepiecesofapuzzle.
Thisiswhatitisliketowritefullydeclarativecode:codethatdescribestherelationshipsbetweenvaluesthattellsthemachinewhatwewantittocompute.Thepartsoftheprogramarenotinstructionsinline-by-lineorder.Synestheticsmaybeabletodoitnaturally,butwithalittlepracticeanyonecanlearnhowtoputtherelationalpuzzlepiecestogether.
Inthischapter,welookedatseveralmathematicalconceptsthatapplytofunctionalprogrammingandhowtheyallowustobuildrelationshipsbetweendata.Next,we’llexplorerecursionandotheradvancedtopicsinJavaScript.
Chapter6.AdvancedTopicsandPitfallsinJavaScriptJavaScripthasbeencalledthe“assemblylanguageoftheweb”.Theanalogy(itisn’tperfect,butwhichanalogyis?)drawsfromthefactthatJavaSciptisoftenatargetforcompilation,namelyfromClojureandCoffeeScript,butalsofrommanyothersourcessuchaspyjamas(pythontoJS)andGoogleWebKit(JavatoJS).
ButtheanalogyalsoreferencesthefoolishideathatJavaScriptisasexpressiveandlow-levelasx86assembly.PerhapsthisnotionstemsfromthefactthatJavaScripthasbeenbashedforitsdesignflawsandoversightseversinceitwasfirstshippedwithNetscapebackin1995.Itwasdevelopedandreleasedinahurry,beforeitcouldbefullydeveloped.Andbecauseofthat,somequestionabledesignchoicesmadeitswayintoJavaScript,thelanguagethatsoonbecamethede-factoscriptinglanguageoftheweb.Semicolonswereabigmistake.Sowereitsambiguousmethodsfordefiningfunctions.Isitvarfoo=function();orfunctionfoo();?
Functionalprogrammingisanexcellentwaytoside-stepsomeofthesemistakes.ByfocusingonthefactthatJavaScriptistrulyafunctionallanguage,itbecomesclearthat,intheprecedingexampleaboutthedifferentwaystodeclareafunction,it’sbesttodeclarefunctionsasvariables.AndthatsemicolonsaremostlyjustsyntacticsugartomakeJavaScriptappearmoreC-like.
Butalwaysrememberthelanguageyouareworkingwith.JavaScript,likeanyotherlanguage,hasitspitfalls.And,whenprogramminginastylethatoftenskirtsthebleedingedgeofwhat’spossible,thoseminorstumblescanbecomenon-recoverablegotchas.Someofthesegotchasinclude:
RecursionVariablescopeandclosuresFunctiondeclarationsvs.functionexpressions
However,theseissuescanbeovercomewithalittleattention.
RecursionRecursionisveryimportanttofunctionalprogramminginanylanguage.Manyfunctionallanguagesgosofarastorequirerecursionforiterationbynotprovidingforandwhileloopstatements;thisisonlypossiblewhentail-calleliminationisguaranteedbythelanguage,whichisnotthecaseforJavaScript.AquickprimeronrecursionwasgiveninChapter2,FundamentalsofFunctionalProgramming.Butinthissection,we’lldigdeeperintoexactlyhowrecursionworksinJavaScript.
TailrecursionJavaScript’sroutineforhandlingrecursionisknownastailrecursion,astack-basedimplementationofrecursion.Thismeansthat,foreveryrecursivecall,thereisanewframeinthestack.
Toillustratetheproblemsthatcanarisefromthismethod,let’susetheclassicrecursivealgorithmforfactorials.
varfactorial=function(n){
if(n==0){
//basecase
return1;
}
else{
//recursivecase
returnn*factorial(n-1);
}
}
Thealgorithmwillcallitselfntimestogettheanswer.It’sliterallycomputing(1x1x2x3x…xN).ThatmeansthetimecomplexityisO(n).
NoteO(n),pronounced“bigohtothen,”meansthatthecomplexityofthealgorithmwillgrowatarateofnasthesizeoftheinputgrows,whichisleanergrowth.O(n2)isexponentialgrowth,O(log(n))islogarithmicgrowth,andsoon.Thisnotationcanbeusedfortimecomplexityaswellasspacecomplexity.
But,becauseanewframeinthememorystackisallocatedforeachiteration,thespacecomplexityisalsoO(n).Thisisaproblem.Thismeansthatmemorywillbeconsumedatsucharatethememorylimitwillbeexceededfartooeasily.Onmylaptop,factorial(23456)returnsUncaughtError:RangeError:Maximumcallstacksizeexceeded.
Whilecalculatingthefactorialof23,456isafrivolousendeavor,youcanbeassuredthatmanyproblemsthataresolvedwithrecursionwillgrowtothatsizewithouttoomuchtrouble.Considerthecaseofdatatrees.Thetreecouldbeanything:searchapplications,filesystems,routingtables,andsoon.Belowisaverysimpleimplementationofthetreetraversalfunction:
vartraverse=function(node){
node.doSomething();//whateverworkneedstobedone
node.childern.forEach(traverse);//manyrecursivecalls
}
Withjusttwochildrenpernode,bothtimecomplexityandspacecomplexity,(intheworstcase,wheretheentiretreemustbetraversedtofindtheanswer),wouldbeO(n2)becausetherewouldbetworecursivecallseach.Withmanychildrenpernode,thecomplexitywouldbeO(nm)wheremisthenumberofchildren.Andrecursionisthepreferredalgorithmfortreetraversal;awhileloopwouldbemuchmorecomplexandwouldrequire
themaintenanceofastack.
ExponentialgrowthlikethiswouldmeanthatitwouldnottakeaverylargetreetothrowaRangeErrorexception.Theremustbeabetterway.
TheTail-calleliminationWeneedawaytoeliminatetheallocationofnewstackframesforeveryrecursivecall.Thisisknownastail-callelimination.
Withtail-callelimination,whenafunctionreturnstheresultofcallingitself,thelanguagedoesn’tactuallyperformanotherfunctioncall.Itturnsthewholethingintoaloopforyou.
OK,sohowdowedothis?Withlazyevaluation.Ifwecouldrewriteittofoldoveralazysequence,suchthatthefunctionreturnsavalueoritreturnstheresultofcallinganotherfunctionwithoutdoinganythingwiththatresult,thennewstackframesdon’tneedtobeallocated.
Toputitin“tailrecursionform”,thefactorialfunctionwouldhavetoberewrittensuchthattheinnerprocedurefactcallsitselflastinthecontrolflow,asshowninthefollowingcodesnippet:
varfactorial=function(n){
var_fact=function(x,n){
if(n==0){
//basecase
returnx;
}
else{
//recursivecase
return_fact(n*x,n-1);
}
}
returnfact(1,n);
}
NoteInsteadofhavingtheresultproducedbythefirstfunctionintherecursiontail(likeinn*factorial(n-1)),theresultiscomputedgoingdowntherecursiontail(withthecallto_fact(r*n,n-1))andisproducedbythelastfunctioninthistail(withreturnr;).Thecomputationgoesonlyonewaydown,notonitswayup.It’srelativelyeasytoprocessitasaniterationfortheinterpreter.
However,tail-calleliminationdoesnotworkinJavaScript.PuttheabovecodeintoyourfavoriteJavaScriptengineandfactorial(24567)stillreturnsUncaughtError:RangeError:Maximumcallstacksizeexceededexception.Tail-calleliminationislistedasanewfeaturetobeincludedinthenextreleaseofECMAScript,butitwillbesometimebeforeallbrowsersimplementit.
JavaScriptcannotoptimizefunctionsthatareputintotailrecursionform.It’safeatureofthelanguagespecificationandruntimeinterpreter,plainandsimple.Ithastodowithhowtheinterpreteracquiresresourcesforstackframes.Somelanguageswillreusethesame
stackframewhenitdoesn’tneedtorememberanythingnew,likeintheprecedingfunction.Thisishowtail-calleliminationreducesbothtimeandspacecomplexity.
Unfortunately,JavaScriptdoesnotdothis.Butifitdid,itwouldreorganizethestackframesfromthis:
callfactorial(3)
callfact(31)
callfact(23)
callfact(16)
callfact(06)
return6
return6
return6
return6
return6
intothefollowing:
callfactorial(3)
callfact(31)
callfact(23)
callfact(16)
callfact(06)
return6
return6
TrampoliningThesolution?Aprocessknownastrampolining.It’sawayto“hack”theconceptoftail-calleliminationintoaprogrambyusingthunks.
NoteThunksare,forthispurpose,expressionswithargumentsthatwrapanonymousfunctionswithnoargumentsoftheirown.Forexample:function(str){returnfunction(){console.log(str)}}.Thispreventstheexpressionfrombeingevaluateduntilareceivingfunctioncallstheanonymousfunction.
Atrampolineisafunctionthattakesafunctionasinputandrepeatedlyexecutesitsreturnedvalueuntilsomethingotherthanafunctionisreturned.Asimpleimplementationisshowninthefollowingcodesnippet:
vartrampoline=function(f){
while(f&&finstanceofFunction){
f=f.apply(f.context,f.args);
}
returnf;
}
Toactuallyimplementtail-callelimination,weneedtousethunks.Forthis,wecanusethebind()functionthatallowsustoapplyamethodtooneobjectwiththethiskeywordassignedtoanother.Internally,it’sthesameasthecallkeyword,butit’schainedtothemethodandreturnsanewboundfunction.Thebind()functionactuallydoespartialapplication,thoughinaverylimitedway.
varfactorial=function(n){
var_fact=function(x,n){
if(n==0){
//basecase
returnx;
}
else{
//recursivecase
return_fact.bind(null,n*x,n-1);
}
}
returntrampoline(_fact.bind(null,1,n));
}
Butwritingthefact.bind(null,...)methodiscumbersomeandwouldconfuseanybodyreadingthecode.Instead,let’swriteourownfunctionforcreatingthunks.Thereareafewthingsthethunk()functionmustdo:
thunk()functionmustemulatethe_fact.bind(null,n*x,n-1)methodthatreturnsanon-evaluatedfunctionThethunk()functionshouldenclosetwomorefunctions:
Forprocessingthegivefunction,andForprocessingthefunctionargumentsthatwillbeusedwhenthegivenfunction
isinvoked
Withthat,we’rereadytowritethefunction.Weonlyneedafewlinesofcodetowriteit.
varthunk=function(fn){
returnfunction(){
varargs=Array.prototype.slice.apply(arguments);
returnfunction(){returnfn.apply(this,args);};
};
};
Nowwecanusethethunk()functioninourfactorialalgorithmlikethis:
varfactorial=function(n){
varfact=function(x,n){
if(n==0){
returnx;
}
else{
returnthunk(fact)(n*x,n-1);
}
}
returntrampoline(thunk(fact)(1,n));
}
Butagain,wecansimplifyitjustabitfurtherbydefiningthe_fact()functionasathunk()function.Bydefiningtheinnerfunctionasathunk()function,we’rerelievedofhavingtousethethunk()functionbothinsidetheinnerfunctiondefinitionandinthereturnstatement.
varfactorial=function(n){
var_fact=thunk(function(x,n){
if(n==0){
//basecase
returnx;
}
else{
//recursivecase
return_fact(n*x,n-1);
}
});
returntrampoline(_fact(1,n));
}
Theresultisbeautiful.Whatseemslikethefunction_fact()beingrecursivelycalledforatail-freerecursionisalmosttransparentlyprocessedasaniteration!
Finally,let’sseehowthetrampoline()andthunk()functionsworkwithourmoremeaningfulexampleoftreetraversal.Thefollowingisacrudeexampleofhowadatatreecouldbetraversedusingtrampoliningandthunks:
vartreeTraverse=function(trunk){
var_traverse=thunk(function(node){
node.doSomething();
node.children.forEach(_traverse);
}
trampoline(_traverse(trunk));
}
We’vesolvedtheissueoftailrecursion.Butisthereanevenbetterway?Whatifwecouldsimplyconverttherecursivefunctiontoanon-recursivefunction?Upnext,we’lllookathowtodojustthat.
TheY-combinatorTheY-combinatorisoneofthosethingsincomputersciencethatamazeeventhedeftestofprogrammingmasterminds.Itsabilitytoautomaticallyconvertrecursivefunctionstonon-recursivefunctionsiswhyDouglasCrockfordcallsit“oneofthemoststrangeandwonderfulartifactsofcomputerscience”,andSussmanandSteeleoncesaid,“Thatthismanagestoworkistrulyremarkable”.
Soatruly-remarkable,wonderfullystrangeartifactofcomputersciencethatbringsrecursivefunctionstotheirkneesmustbemassiveandcomplex,right?No,notexactly.ItsimplementationinJavaScriptisonlynine,veryodd,linesofcode.Theyareasfollows:
varY=function(F){
return(function(f){
returnf(f);
}(function(f){
returnF(function(x){
returnf(f)(x);
});
}));
}
Here’showitworks:itfindsthe“fixedpoint”ofthefunctionpassedinasanargument.Fixedpointsofferanotherwaytothinkaboutfunctionsratherthanrecursionanditerationinthetheoryofcomputerprogramming.Anditdoesthiswithonlytheuseofanonymousfunctionexpressions,functionapplications,andvariablereferences.NotethatYdoesnotreferenceitself.Infact,allthosefunctionsareanonymous.
Asyoumighthaveguessed,theY-combinatorcameoutoflambdacalculus.It’sactuallyderivedwiththehelpofanothercombinatorcalledtheU-combinator.Combinatorsarespecialhigher-orderfunctionsthatonlyusefunctionapplicationandearlierdefinedcombinatorstodefinearesultfromitsinput.
TodemonstratetheY-combinator,we’llagainturntothefactorialproblem,butweneedtodefinethefactorialfunctionalittledifferently.Insteadofwritingarecursivefunction,wewriteafunctionthatreturnsafunctionthatisthemathematicaldefinitionoffactorials.ThenwecanpassthisintotheY-combinator.
varFactorialGen=function(factorial){
return(function(n){
if(n==0){
//basecase
return1;
}
else{
//recursivecase
returnn*factorial(n–1);
}
});
};
Factorial=Y(FactorialGen);
Factorial(10);//3628800
However,whenwegiveitasignificantlylargenumber,thestackoverflowsjustasiftailrecursionwithouttrampoliningwasused.
Factorial(23456);//RangeError:Maximumcallstacksizeexceeded
ButwecanusetrampoliningwiththeY-combinatorasinthefollowing:
varFactorialGen2=function(factorial){
returnfunction(n){
varfactorial=thunk(function(x,n){
if(n==0){
returnx;
}
else{
returnfactorial(n*x,n-1);
}
});
returntrampoline(factorial(1,n));
}
};
varFactorial2=Y(FactorialGen2)
Factorial2(10);//3628800
Factorial2(23456);//Infinity
WecanalsorearrangetheY-combinatortoperformsomethingcalledmemoization.
MemoizationMemoizationisthetechniqueofstoringtheresultofexpensivefunctioncalls.Whenthefunctionislatercalledwiththesamearguments,thestoredresultisreturnedratherthancomputingtheresultagain.
AlthoughtheY-combinatorismuchfasterthanrecursion,itisstillrelativelyslow.Tospeeditup,wecancreateamemoizingfixed-pointcombinator:aY-likecombinatorthatcachestheresultsofintermediatefunctioncalls.
varYmem=function(F,cache){
if(!cache){
cache={};//Createanewcache.
}
returnfunction(arg){
if(cache[arg]){
//Answerincache
returncache[arg];
}
//elsecomputetheanswer
varanswer=(F(function(n){
return(Ymem(F,cache))(n);
}))(arg);//Computetheanswer.
cache[arg]=answer;//Cachetheanswer.
returnanswer;
};
}
Sohowmuchfasterisit?Byusinghttp://jsperf.com/,wecancomparetheperformance.
Thefollowingresultsarewithrandomnumbersbetween1and100.WecanseethatthememoizingY-combinatorismuch,muchfaster.Andaddingtrampoliningtoitdoesnotslowitdownbymuch.YoucanviewtheresultsandrunthetestsyourselfatthisURL:http://jsperf.com/memoizing-y-combinator-vs-tail-call-optimization/7.
Thebottomlineis:themostefficientandsafestmethodofperformingrecursioninJavaScriptistousethememoizingY-combinatorwithtail-calleliminationviatrampoliningandthunks.
VariablescopeThescopeofvariablesinJavaScriptisnotnatural.Infact,sometimesit’sdownrightcounter-intuitive.TheysaythatJavaScriptprogrammerscanbejudgedbyhowwelltheyunderstandscope.
ScoperesolutionsFirst,let’sgooverthedifferentscoperesolutionsinJavaScript.
JavaScriptusesscopechainstoestablishthescopeofvariables.Whenresolvingavariable,itstartsattheinnermostscopeandsearchesoutwards.
GlobalscopeVariables,functions,andobjectsdefinedatthislevelareavailabletoanycodeintheentireprogram.Thisistheoutermostscope.
varx='hi';
functiona(){
console.log(x);
}
a();//'hi'
LocalscopeEachfunctiondescribedhasitsownlocalscope.Anyfunctiondefinedwithinanotherfunctionhasanestedlocalscopethatislinkedtotheouterfunction.Almostalways,it’sthepositioninthesourcethatdefinesthescope.
varx='hi';
functiona(){
console.log(x);
}
functionb(){
varx='hello';
console.log(x);
}
b();//hello
a();//hi
Localscopeisonlyforfunctionsandnotforanyexpressionstatements(if,for,while,andsoon),whichisdifferentfromhowmostlanguagestreatscope.
functionc(){
vary='greetings';
if(true){
vary='gutentag';
}
console.log(y);
}
functiond(){
vary='greetings';
functione(){
vary='gutentag';
}
console.log(y)
}
c();//'gutentag'
d();//'greetings'
Infunctionalprogramming,thisisn’tasmuchofaconcernbecausefunctionsareusedmoreoftenandexpressionstatementslessoften.Forexample:
functione(){
varz='namaste';
[1,2,3].foreach(function(n){
varz='aloha';
}
isTrue(function(){
varz='goodmorning';
});
console.log(z);
}
e();//'namaste'
ObjectpropertiesObjectpropertieshavetheirownscopechainsaswell.
varx='hi';
varobj=function(){
this.x='hola';
};
varfoo=newobj();
console.log(foo.x);//'hola'
foo.x='bonjour';
console.log(foo.x);//'bonjour'
Theobject’sprototypeisfurtherdownthescopechain.
obj.prototype.x='greetings';
obj.prototype.y='konnichiha';
varbar=newobj();
console.log(bar.x);//stillprints'hola'
console.log(bar.y);//'konnichiha'
Thisisn’tevenclosetobeingcomprehensive,butthesethreetypesofscopeareenoughtogetstarted.
ClosuresOneproblemwiththisscopestructureisthatitleavesnoroomforprivatevariables.Considerthefollowingcodesnippet:
varname='FordFocus';
varyear='2006';
varmillage=123456;
functiongetMillage(){
returnmillage;
}
functionupdateMillage(n){
millage=n;
}
Thesevariablesandfunctionsareglobal,whichmeansitwouldbetooeasyforcodelaterdowntheprogramtoaccidentallyoverwritethem.Onesolutionwouldbetoencapsulatethemintoafunctionandcallthatfunctionimmediatelyafterdefiningit.
varcar=function(){
varname='FordFocus';
varyear='2006';
varmillage=123456;
functiongetMillage(){
returnMillage;
}
functionupdateMillage(n){
millage=n;
}
}();
Nothingishappeningoutsidethefunction,soweoughttodiscardthefunctionnamebymakingitanonymous.
(function(){
varname='FordFocus';
varyear='2006';
varmillage=123456;
functiongetMillage(){
returnmillage;
}
functionupdateMillage(n){
millage=n;
}
})();
TomakethefunctionsgetValue()andupdateMillage()availableoutsidetheanonymousfunction,we’llneedtoreturntheminanobjectliteralasshowninthefollowingcodesnippet:
varcar=function(){
varname='FordFocus';
varyear='2006';
varmillage=123456;
return{
getMillage:function(){
returnmillage;
},
updateMillage:function(n){
millage=n;
}
}
}();
console.log(car.getMillage());//works
console.log(car.updateMillage(n));//alsoworks
console.log(car.millage);//undefined
Thisgivesuspseudo-privatevariables,buttheproblemsdon’tstopthere.ThefollowingsectionexploresmoreissueswithvariablescopeinJavaScript.
GotchasManyvariablescopenuancescanbefoundthroughoutJavaScript.Thefollowingisbynomeansacomprehensivelist,butitcoversthemostcommoncases:
Thefollowingwilloutput4,not‘undefined’asonewouldexpect:
for(varn=4;false;){}console.log(n);
Thisisduetothefactthat,inJavaScript,variabledefinitionhappensatthebeginningofthecorrespondingscope,notjustwhenitisdeclared.
Ifyoudefineavariableintheouterscope,andthenhaveanifstatementdefineavariableinsidethefunctionwiththesamename,evenifthatifbranchisn’treached,itisredefined.Anexample:
varx=1;
functionfoo(){
if(false){
varx=2;
}
returnx;
}
foo();//Returnvalue:'undefined',expectedreturnvalue:
2
Again,thisiscausedbymovingthevariabledefinitionatthebeginningofthescopewiththeundefinedvalue.
Inthebrowser,globalvariablesarereallystoredinthewindowobject.
window.a=19;
console.log(a);//Output:19
aintheglobalscopemeansaasanattributeofthecurrentcontext,soa===this.aandwindowobjectinabrowseractasanequivalentofthethiskeywordintheglobalscope.
ThefirsttwoexamplesarearesultofafeatureofJavaScriptknownashoisting,whichwillbeacriticalconceptinthenextsectionaboutwritingfunctions.
FunctiondeclarationsversusfunctionexpressionsversusthefunctionconstructorWhatisthedifferencebetweenthesethreestatements?
functionfoo(n){returnn;}
varfoo=function(n){returnn;};
varfoo=newFunction('n','returnn');
Atfirstglance,they’remerelydifferentwaystowritethesamefunction.Butthere’salittlemoregoingonhere.Andifwe’retotakefulladvantageoffunctionsinJavaScriptinordertomanipulatethemintoafunctionalprogrammingstyle,thenwe’dbetterbeabletogetthisright.Ifthereisabetterwaytodosomethingincomputerprogramming,thenthatonewayshouldbetheonlyway.
FunctiondeclarationsFunctiondeclarations,sometimescalledfunctionstatements,defineafunctionbyusingthefunctionkeyword.
functionfoo(n){
returnn;
}
Functionsthataredeclaredwiththissyntaxarehoistedtothetopofthecurrentscope.Whatthisactuallymeansisthat,evenifthefunctionisdefinedseverallinesdown,JavaScriptknowsaboutitandcanuseitearlierinthescope.Forexample,thefollowingwillcorrectlyprint6totheconsole:
foo(2,3);
functionfoo(n,m){
console.log(n*m);
}
FunctionexpressionsNamedfunctionscanalsobedefinedasanexpressionbydefiningananonymousfunctionandassigningittoavariable.
varbar=function(n,m){
console.log(n*m);
};
Theyarenothoistedlikefunctiondeclarationsare.Thisisbecause,whilefunctiondeclarationsarehoisted,variabledeclarationsarenot.Forexample,thiswillnotworkandwillthrowanerror:
bar(2,3);
varbar=function(n,m){
console.log(n*m);
};
Infunctionalprogramming,we’regoingtowanttousefunctionexpressionssowecantreatthefunctionslikevariables,makingthemavailabletobeusedascallbacksandargumentstohigher-orderfunctionssuchasmap()functions.Definingfunctionsasexpressionsmakesitmoreobviousthatthey’revariablesassignedtoafunction.Also,ifwe’regoingtowritefunctionsinonestyle,weshouldwriteallfunctionsinthatstyleforthesakeofconsistencyandclarity.
ThefunctionconstructorJavaScriptactuallyhasathirdwaytocreatefunctions:withtheFunction()constructor.Justlikefunctionexpressions,functionsdefinedwiththeFunction()constructorarenothoisted.
varfunc=newFunction('n','m','returnn+m');
func(2,3);//returns5
ButtheFunction()constructorisnotonlyconfusing,itisalsohighlydangerous.Nosyntaxcorrectioncanhappen,nooptimizationispossible.It’sfareasier,safer,andlessconfusingtowritethesamefunctionasfollows:
varfunc=function(n,m){returnn+m};
func(2,3);//returns5
UnpredictablebehaviorSothedifferenceisthatfunctiondeclarationsarehoistedwhilefunctionexpressionsarenot.Thiscancauseunexpectedthingstohappen.Considerthefollowing:
functionfoo(){
return'hi';
}
console.log(foo());
functionfoo(){
return'hello';
}
What’sactuallyprintedtotheconsoleishello.Thisisduetothefactthattheseconddefinitionofthefoo()functionishoistedtothetop,makingitthedefinitionthatisactuallyusedbytheJavaScriptinterpreter.
Whileatfirstthismaynotseemlikeacriticaldifference,infunctionalprogrammingthiscancausemayhem.Considerthefollowingcodesnippet:
if(true){
functionfoo(){console.log('one')};
}
else{
functionfoo(){console.log('two')};
}
foo();
Whenthefoo()functioniscalled,twoisprintedtotheconsole,notone!
Finally,thereisawaytocombinebothfunctionexpressionsanddeclarations.Itworksasfollows:
varfoo=functionbar(){console.log('hi');};
foo();//'hi'
bar();//Error:barisnotdefined
Itmakesverylittlesensetousethismethodbecausethenameusedinthedeclaration(thebar()functionintheprecedingexample)isnotavailableoutsidethefunctionandcausesconfusion.Itwouldonlybeappropriateforrecursion,forexample:
varfoo=functionfactorial(n){
if(n==0){
return1;
}
else{
returnn*factorial(n-1);
}
};
foo(5);
SummaryJavaScripthasbeencalledthe“assemblylanguageoftheweb,”becauseit’sasubiquitousandunavoidableasx86assembly.It’stheonlylanguagethatrunsonallbrowsers.It’salsoflawed,yetreferringtoitasalow-levellanguageismissingthemark.
Instead,thinkofJavaScriptastherawcoffeebeansoftheweb.Sure,someofthebeansaredamagedandafewarerotten.Butifthegoodonesareselected,roasted,andbrewedbyaskilledbarista,thebeanscanbetransformedintoabrilliantjamochathatcannotbehadjustonceandforgotten.It’sconsumptionbecomesadailycustom,lifewithoutitwouldbestatic,hardertoperform,andmuchlessexciting.Someevenprefertoenhancethebrewwithplug-insandadd-onssuchascream,sugar,andcocoa,whichcomplementitverywell.
OneofJavaScript’sbiggestcritics,DouglasCrawford,wasquotedassaying“TherearecertainlyalotofpeoplewhorefusetoconsiderthepossibilitythatJavaScriptgotanythingright.Iusedtobeoneofthoseguys.ButnowIcontinuetobeamazedbythebrilliancethatisinthere”.
JavaScriptturnedouttobeprettyawesome.
Chapter7.FunctionalandObject-orientedProgramminginJavaScriptYouwilloftenhearthatJavaScriptisablanklanguage,whereblankiseitherobject-oriented,functional,orgeneral-purpose.ThisbookhasfocusedonJavaScriptasafunctionallanguageandhasgonetogreatlengthstoprovethatitis.ButthetruthisthatJavaScriptisageneral-purposelanguage,meaningit’sfullycapableofmultipleprogrammingstyles.LikePythonandF#,JavaScriptismulti-paradigm.Butunlikethoselanguages,JavaScript’sOOPsideisprototype-basedwhilemostothergeneral-purposelanguagesareclass-based.
Inthisfinalchapter,wewillrelatebothfunctionalandobject-orientedprogrammingtoJavaScript,andseehowthetwoparadigmscancomplementeachotherandcoexistside-by-side.Inthischapterthefollowingtopicswillbecovered:
HowcanJavaScriptbebothfunctionalandOOP?JavaScript’sOOP–usingprototypesHowtomixfunctionalandOOPinJavaScriptFunctionalinheritanceFunctionalmixins
Bettercodeisthegoal.Functionalandobject-orientedprogrammingarejustmeanstothisend.
JavaScript–themulti-paradigmlanguageIfobject-orientedprogrammingmeanstreatingallvariablesasobjects,andfunctionalprogrammingmeanstreatingallfunctionsasvariables,thencan’tfunctionsbetreatedlikeobjects?InJavaScript,theycan.
Butsayingthatfunctionalprogrammingmeanstreatingfunctionsasvariablesissomewhatinaccurate.Abetterwaytoputitis:functionalprogrammingmeanstreatingeverythingasavalue,especiallyfunctions.
Abetterwaystilltodescribefunctionalprogrammingmaybetocallitdeclarative.Independentoftheimperativebranchofprogrammingstyles,declarativeprogrammingexpressesthelogicofcomputationrequiredtosolvetheproblem.Thecomputeristoldwhattheproblemisratherthantheprocedureforhowtosolveit.
Meanwhile,object-orientedprogrammingisderivedfromtheimperativeprogrammingstyle:thecomputerisgivenstep-by-stepinstructionsforhowtosolvetheproblem.OOPmandatesthattheinstructionsforcomputation(methods)andthedatatheyworkon(membervariables)beorganizedintounitscalledobjects.Theonlywaytoaccessthatdataisthroughtheobject’smethods.
Sohowcanthesetwostylesbeintegratedtogether?
Thecodeinsidetheobject’smethodsistypicallywritteninanimperativestyle.Butwhatifitwasinafunctionalstyle?Afterall,OOPdoesn’texcludeimmutabledataandhigher-orderfunctions.Perhapsapurerwaytomixthetwowouldbetotreatobjectsbothasfunctionsandastraditional,class-basedobjectsatthesametime.Maybewecansimplyincludeseveralideasfromfunctionalprogramming—suchaspromisesandrecursion—intoourobject-orientedapplication.OOPcoverstopicssuchasencapsulation,polymorphism,andabstraction.Sodoesfunctionalprogramming,itjustgoesaboutitinadifferentway.Somaybewecanincludeseveralideasfromobject-orientedprogramminginourfunctional-orientedapplication.
Thepointis:OOPandFPcanbemixedtogetherandthereareseveralwaystodoit.They’renotexclusiveofeachother.
JavaScript’sobject-orientedimplementation–usingprototypesJavaScriptisaclass-lesslanguage.That’snottomeanitislessfashionableormoreblue-collarthanothercomputerlanguages;class-lessmeansitdoesn’thaveaclassstructureinthesamewaythatobject-orientedlanguagesdo.Instead,itusesprototypesforinheritance.
AlthoughthismaybebafflingtoprogrammerswithbackgroundsinC++andJava,prototype-basedinheritancecanbemuchmoreexpressivethantraditionalinheritance.ThefollowingisabriefcomparisonbetweenthedifferencesbetweenC++andJavaScript:
C++ JavaScript
Stronglytyped Looselytyped
Static Dynamic
Class-based Prototype-based
Classes Functions
Constructors Functions
Methods Functions
InheritanceBeforewegomuchfurther,let’smakesurewefullyunderstandtheconceptofinheritanceinobject-orientedprogramming.Class-basedinheritanceisdemonstratedinthefollowingpseudo-code:
classPolygon{
intnumSides;
functioninit(n){
numSides=n;
}
}
classRectangleinheritsPolygon{
intwidth;
intlength;
functioninit(w,l){
numSides=4;
width=w;
length=l;
}
functiongetArea(){
returnw*l;
}
}
classSquareinheritsRectangle{
functioninit(s){
numSides=4;
width=s;
length=s;
}
}
ThePolygonclassistheparentclasstheotherclassesinheritfrom.Itdefinesjustonemembervariable,thenumberofsides,whichissetintheinit()function.TheRectanglesubclassinheritsfromthePolygonclassandaddstwomoremembervariables,lengthandwidth,andamethod,getArea().Itdoesn’tneedtodefinethenumSidesvariablebecauseitwasalreadydefinedbytheclassitinheritsfrom,anditalsooverridestheinit()function.TheSquareclasscarriesonthischainofinheritanceevenfurtherbyinheritingfromtheRectangleclassforitsgetArea()method.Bysimplyoverridingtheinit()functionagainsuchthatthelengthandwidtharethesame,thegetArea()functioncanremainunchangedandlesscodeneedstobewritten.
InatraditionalOOPlanguage,thisiswhatinheritanceisallabout.Ifwewantedtoaddacolorpropertytoalltheobjects,allwewouldhavetodoisaddittothePolygonobjectwithouthavingtomodifyanyoftheobjectsthatinheritfromit.
JavaScript’sprototypechainInheritanceinJavaScriptcomesdowntoprototypes.Eachobjecthasaninternalpropertyknownasitsprototype,whichisalinktoanotherobject.Thatobjecthasaprototypeofitsown.Thispatterncanrepeatuntilanobjectisreachedthathasundefinedasitsprototype.Thisisknownastheprototypechain,andit’showinheritanceworksinJavaScript.ThefollowingdiagramexplaintheinheritanceinJavaScirpt:
Whenrunningasearchforanobject’sfunctiondefinition,JavaScript“walks”theprototypechainuntilitfindsthefirstdefinitionofafunctionwiththerightname.Therefore,overridingitisassimpleasprovidinganewdefinitionontheprototypeofthesubclass.
InheritanceinJavaScriptandtheObject.create()methodJustastherearemanywaystocreateobjectsinJavaScript,therearealsomanywaystoreplicateclass-based,classicalinheritance.ButtheonepreferredwaytodoitiswiththeObject.create()method.
varPolygon=function(n){
this.numSides=n;
}
varRectangle=function(w,l){
this.width=w;
this.length=l;
}
//theRectangle'sprototypeisredefinedwithObject.create
Rectangle.prototype=Object.create(Polygon.prototype);
//it'simportanttonowrestoretheconstructorattribute
//otherwiseitstayslinkedtothePolygon
Rectangle.prototype.constructor=Rectangle;
//nowwecancontinuetodefinetheRectangleclass
Rectangle.prototype.numSides=4;
Rectangle.prototype.getArea=function(){
returnthis.width*this.length;
}
varSquare=function(w){
this.width=w;
this.length=w;
}
Square.prototype=Object.create(Rectangle.prototype);
Square.prototype.constructor=Square;
vars=newSquare(5);
console.log(s.getArea());//25
Thissyntaxmayseemunusualtomanybut,withalittlepractice,itwillbecomefamiliar.Theprototypekeywordmustbeusedtogainaccesstotheinternalproperty,[[Prototype]],whichallobjectshave.TheObject.create()methoddeclaresanewobjectwithaspecifiedobjectforitsprototypetoinheritfrom.Inthisway,classicalinheritancecanbeachievedinJavaScript.
NoteTheObject.create()methodwasintroducedinECMAScript5.1in2011,anditwasbilledasthenewandpreferredwaytocreateobjects.ThiswasjustoneofmanyattemptstointegrateinheritanceintoJavaScript.Thankfully,thismethodworksprettywell.
WesawthisstructureofinheritancewhenbuildingtheMaybeclassesinChapter5,
CategoryTheory.HerearetheMaybe,None,andJustclasses,whichinheritfromeachotherjustliketheprecedingexample.
varMaybe=function(){};
varNone=function(){};
None.prototype=Object.create(Maybe.prototype);
None.prototype.constructor=None;
None.prototype.toString=function(){return'None';};
varJust=function(x){this.x=x;};
Just.prototype=Object.create(Maybe.prototype);
Just.prototype.constructor=Just;
Just.prototype.toString=function(){return"Just"+this.x;};
ThisshowsthatclassinheritanceinJavaScriptcanbeanenableroffunctionalprogramming.
AcommonmistakeistopassaconstructorintoObject.create()insteadofaprototypeobject.Thisproblemiscompoundedbythefactthatanerrorwillnotbethrownuntilthesubclasstriestouseaninheritedmemberfunction.
Foo.prototype=Object.create(Parent.prototype);//correct
Bar.prototype=Object.create(Parent);//incorrect
Bar.inheritedMethod();//Error:functionisundefined
Thefunctionwon’tbefoundiftheinheritedMethod()methodhasbeenattachedtotheFoo.prototypeclass.IftheinheritedMethod()methodisattacheddirectlytotheinstancewiththis.inheritedMethod=function(){...}intheBarconstructor,thenthisuseofParentasanargumentofObject.create()couldbecorrect.
Mixingfunctionalandobject-orientedprogramminginJavaScriptObject-orientedprogramminghasbeenthedominantprogrammingparadigmforseveraldecades.ItistaughtinComputerScience101classesaroundtheworld,whilefunctionalprogrammingisnot.Itiswhatsoftwarearchitectsusetodesignapplications,whilefunctionalprogrammingisnot.Anditmakessensetoo:OOPmakesiteasytoconceptualizeabstractideas.Itmakesiteasiertowritecode.
So,unlessyoucanconvinceyourbossthattheapplicationneedstobeallfunctional,we’regoingtobeusingfunctionalprogramminginanobject-orientedworld.Thissectionwillexplorewaystodothis.
FunctionalinheritancePerhapsthemostaccessiblewaytoapplyfunctionalprogrammingtoJavaScriptapplicationsistouseamostlyfunctionalstylewithinOOPprinciples,suchasinheritance.
Toexplorehowthismightwork,let’sbuildasimpleapplicationthatcalculatesthepriceofaproduct.First,we’llneedsomeproductclasses:
varShirt=function(size){
this.size=size;
};
varTShirt=function(size){
this.size=size;
};
TShirt.prototype=Object.create(Shirt.prototype);
TShirt.prototype.constructor=TShirt;
TShirt.prototype.getPrice=function(){
if(this.size=='small'){
return5;
}
else{
return10;
}
}
varExpensiveShirt=function(size){
this.size=size;
}
ExpensiveShirt.prototype=Object.create(Shirt.prototype);
ExpensiveShirt.prototype.constructor=ExpensiveShirt;
ExpensiveShirt.prototype.getPrice=function(){
if(this.size=='small'){
return20;
}
else{
return30;
}
}
WecanthenorganizethemwithinaStoreclassasfollows:
varStore=function(products){
this.products=products;
}
Store.prototype.calculateTotal=function(){
returnthis.products.reduce(function(sum,product){
returnsum+product.getPrice();
},10)*TAX;//startwith$10markup,timesglobalTAXvar
};
varTAX=1.08;
varp1=newTShirt('small');
varp2=newExpensiveShirt('large');
vars=newStore([p1,p2]);
console.log(s.calculateTotal());//Output:35
ThecalculateTotal()methodusesthearray’sreduce()functiontocleanlysumtogetherthepricesoftheproducts.
Thisworksjustfine,butwhatifweneedadynamicwaytocalculatethemarkupvalue?Forthis,wecanturntoaconceptcalledStrategyPattern.
StrategyPatternStrategyPatternisamethodfordefiningafamilyofinterchangeablealgorithms.ItisusedbyOOPprogrammerstomanipulatebehavioratruntime,butitisbasedonafewfunctionalprogrammingprinciples:
SeparationoflogicanddataCompositionoffunctionsFunctionsasfirst-classobjects
AndacoupleofOOPprinciplesaswell:
EncapsulationInheritance
Inourexampleapplicationforcalculatingproductcost,explainedpreviously,let’ssaywewanttogivepreferentialtreatmenttocertaincustomers,andthatthemarkupwillhavetobeadjustedtoreflectthis.
Solet’screatesomecustomerclasses:
varCustomer=function(){};
Customer.prototype.calculateTotal=function(products){
returnproducts.reduce(function(total,product){
returntotal+product.getPrice();
},10)*TAX;
};
varRepeatCustomer=function(){};
RepeatCustomer.prototype=Object.create(Customer.prototype);
RepeatCustomer.prototype.constructor=RepeatCustomer;
RepeatCustomer.prototype.calculateTotal=function(products){
returnproducts.reduce(function(total,product){
returntotal+product.getPrice();
},5)*TAX;
};
varTaxExemptCustomer=function(){};
TaxExemptCustomer.prototype=Object.create(Customer.prototype);
TaxExemptCustomer.prototype.constructor=TaxExemptCustomer;
TaxExemptCustomer.prototype.calculateTotal=function(products){
returnproducts.reduce(function(total,product){
returntotal+product.getPrice();
},10);
};
EachCustomerclassencapsulatesthealgorithm.NowwejustneedtheStoreclasstocall
theCustomerclass’scalculateTotal()method.
varStore=function(products){
this.products=products;
this.customer=newCustomer();
//bonusexercise:useMaybesfromChapter5insteadofadefault
customerinstance
}
Store.prototype.setCustomer=function(customer){
this.customer=customer;
}
Store.prototype.getTotal=function(){
returnthis.customer.calculateTotal(this.products);
};
varp1=newTShirt('small');
varp2=newExpensiveShirt('large');
vars=newStore([p1,p2]);
varc=newTaxExemptCustomer();
s.setCustomer(c);
s.getTotal();//Output:45
TheCustomerclassesdothecalculating,theProductclassesholdthedata(theprices),andtheStoreclassmaintainsthecontext.Thisachievesaveryhighlevelofcohesionandaverygoodmixtureofobject-orientedprogrammingandfunctionalprogramming.JavaScript’shighlevelofexpressivenessmakesthispossibleandquiteeasy.
MixinsInanutshell,mixinsareclassesthatcanallowotherclassestousetheirmethods.Themethodsareintendedtobeusedsolelybyotherclasses,andthemixinclassitselfisnevertobeinstantiated.Thishelpstoavoidinheritanceambiguity.Andthey’reagreatmeansofmixingfunctionalprogrammingwithobject-orientedprogramming.
Mixinsareimplementeddifferentlyineachlanguage.ThankstoJavaScript’sflexibilityandexpressiveness,mixinsareimplementedasobjectswithonlymethods.Whiletheycanbedefinedasfunctionobjects(thatis,varmixin=function(){...};),itwouldbebetterforthestructuraldisciplineofthecodetodefinethemasobjectliterals(thatis,varmixin={...};).Thiswillhelpustodistinguishbetweenclassesandmixins.Afterall,mixinsshouldbetreatedasprocesses,notobjects.
Let’sstartwithdeclaringsomemixins.We’llextendourStoreapplicationfromtheprevioussection,usingmixinstoexpandontheclasses.
varsmall={
getPrice:function(){
returnthis.basePrice+6;
},
getDimensions:function(){
return[44,63]
}
}
varlarge={
getPrice:function(){
returnthis.basePrice+10;
},
getDimensions:function(){
return[64,83]
}
};
We’renotlimitedtojustthis.Manymoremixinscanbeadded,likecolorsorfabricmaterial.We’llhavetorewriteourShirtclassesalittlebit,asshowninthefollowingcodesnippet:
varShirt=function(){
this.basePrice=1;
};
Shirt.getPrice=function(){
returnthis.basePrice;
}
varTShirt=function(){
this.basePrice=5;
};
TShirt.prototype=Object.create(Shirt.prototype);
TShirt..prototype.constructor=TShirt;
Nowwe’rereadytousemixins.
Classicalmixins
You’reprobablywonderingjusthowthesemixinsgetmixedwiththeclasses.Theclassicalwaytodothisisbycopyingthemixin’sfunctionsintothereceivingobject.ThiscanbedonewiththefollowingextensiontotheShirtprototype:
Shirt.prototype.addMixin=function(mixin){
for(varpropinmixin){
if(mixin.hasOwnProperty(prop)){
this.prototype[prop]=mixin[prop];
}
}
};
Andnowthemixinscanbeaddedasfollows:
TShirt.addMixin(small);
varp1=newTShirt();
console.log(p1.getPrice());//Output:11
TShirt.addMixin(large);
varp2=newTShirt();
console.log(p2.getPrice());//Output:15
However,thereisamajorproblem.Whenthepriceofp1iscalculatedagain,itcomesbackas15,thepriceofalargeitem.Itshouldbethevalueforasmallone!
console.log(p1.getPrice());//Output:15
TheproblemisthattheShirtobject’sprototype.getPrice()methodisgettingrewritteneverytimeamixinisaddedtoit;thisisnotveryfunctionalatallandnotwhatwewant.
FunctionalmixinsThere’sanotherwaytousemixins,onethatismorealignedwithfunctionalprogramming.
Insteadofcopyingthemethodsofthemixintothetargetobject,weneedtocreateanewobjectthatisacloneofthetargetobjectwiththemixin’smethodsaddedin.Theobjectmustbeclonedfirst,andthisisachievedbycreatinganewobjectthatinheritsfromit.We’llcallthisvariationplusMixin.
Shirt.prototype.plusMixin=function(mixin){
//createanewobjectthatinheritsfromtheold
varnewObj=this;
newObj.prototype=Object.create(this.prototype);
for(varpropinmixin){
if(mixin.hasOwnProperty(prop)){
newObj.prototype[prop]=mixin[prop];
}
}
returnnewObj;
};
varSmallTShirt=Tshirt.plusMixin(small);//createsanewclass
varsmallT=newSmallTShirt();
console.log(smallT.getPrice());//Output:11
varLargeTShirt=Tshirt.plusMixin(large);
varlargeT=newLargeTShirt();
console.log(largeT.getPrice());//Output:15
console.log(smallT.getPrice());//Output:11(noteffectedby2ndmixin
call)
Herecomesthefunpart!Nowwecangetreallyfunctionalwiththemixins.Wecancreateeverypossiblecombinationofproductsandmixins.
//intherealworldtherewouldbewaymoreproductsandmixins!
varproductClasses=[ExpensiveShirt,Tshirt];
varmixins=[small,medium,large];
//mixthemalltogether
products=productClasses.reduce(function(previous,current){
varnewProduct=mixins.map(function(mxn){
varmixedClass=current.plusMixin(mxn);
vartemp=newmixedClass();
returntemp;
});
returnprevious.concat(newProduct);
},[]);
products.forEach(function(o){console.log(o.getPrice())});
Tomakeitmoreobject-oriented,wecanrewritetheStoreobjectwiththisfunctionality.We’llalsoaddadisplayfunctiontotheStoreobject,nottheproducts,tokeeptheinterfacelogicandthedataseparated.
//thestore
varStore=function(){
productClasses=[ExpensiveShirt,TShirt];
productMixins=[small,medium,large];
this.products=productClasses.reduce(function(previous,current){
varnewObjs=productMixins.map(function(mxn){
varmixedClass=current.plusMixin(mxn);
vartemp=newmixedClass();
returntemp;
});
returnprevious.concat(newObjs);
},[]);
}
Store.prototype.displayProducts=function(){
this.products.forEach(function(p){
$('ul#products').append('<li>'+p.getTitle()+':
$'+p.getPrice()+'</li>');
});
}
AndallwehavetodoiscreateaStoreobjectandcallitsdisplayProducts()methodtogeneratealistofproductsandprices!
<ulid="products">
<li>smallpremiumshirt:$16</li>
<li>mediumpremiumshirt:$18</li>
<li>largepremiumshirt:$20</li>
<li>smallt-shirt:$11</li>
<li>mediumt-shirt:$13</li>
<li>larget-shirt:$15</li>
</ul>
Theselinesneedtobeaddedtotheproductclassesandmixinstogettheprecedingoutputtowork:
Shirt.prototype.title='shirt';
TShirt.prototype.title='t-shirt';
ExpensiveShirt.prototype.title='premiumshirt';
//thenthemixinsgottheextra'getTitle'function:
varsmall={
...
getTitle:function(){
return'small'+this.title;//smallormediumorlarge
}
}
And,justlikethat,wehaveane-commerceapplicationthatishighlymodularandextendable.Newshirtstylescanbeaddedabsurdlyeasily—justdefineanewShirtsubclassandaddtoittheStoreclass’sarrayproductclasses.Mixinsareaddedinjustthesameway.Sonowwhenourbosssays,“Hey,wehaveanewtypeofshirtandacoat,eachavailableinthestandardcolors,andweneedthemaddedtothewebsitebeforeyougohometoday”,wecanrestassuredthatwe’llnotbestayinglate!
SummaryJavaScripthasahighlevelofexpressiveness.Thismakesitpossibletomixfunctionalandobject-orientedprogramming.ModernJavaScriptisnotsolelyOOPorfunctional—itisamixtureofthetwo.ConceptssuchasStrategyPatternandmixinsareperfectforJavaScript’sprototypestructure,andtheyhelptoprovethattoday’sbestpracticesinJavaScriptshareequalamountsoffunctionalprogrammingandobject-orientedprogramming.
Ifyouweretotakeawayonlyonethingfromthisbook,Iwouldwantittobehowtoapplyfunctionalprogrammingtechniquestoreal-worldapplications.Andthischaptershowedyouhowtodoexactlythat.
AppendixA.CommonFunctionsforFunctionalProgramminginJavaScriptThisAppendixcoverscommonfunctionsforfunctionalprogramminginJavaScript:
ArrayFunctions:
varflatten=function(arrays){
returnarrays.reduce(function(p,n){
returnp.concat(n);
});
};
varinvert=function(arr){
returnarr.map(function(x,i,a){
returna[a.length-(i+1)];
});
};
BindingFunctions:
varbind=Function.prototype.call.bind(Function.prototype.bind);
varcall=bind(Function.prototype.call,Function.prototype.call);
varapply=bind(Function.prototype.call,Function.prototype.apply);
CategoryTheory:
varcheckTypes=function(typeSafeties){
arrayOf(func)(arr(typeSafeties));
varargLength=typeSafeties.length;
returnfunction(args){
arr(args);
if(args.length!=argLength){
thrownewTypeError('Expected'+argLength+'arguments');
}
varresults=[];
for(vari=0;i<argLength;i++){
results[i]=typeSafeties[i](args[i]);
}
returnresults;
};
};
varhomoMorph=function(/*arg1,arg2,...,argN,output*/){
varbefore=checkTypes(arrayOf(func)
(Array.prototype.slice.call(arguments,0,arguments.length-1)));
varafter=func(arguments[arguments.length-1])
returnfunction(middle){
returnfunction(args){
returnafter(middle.apply(this,
before([].slice.apply(arguments))));
};
};
};
Composition:
Function.prototype.compose=function(prevFunc){
varnextFunc=this;
returnfunction(){
returnnextFunc.call(this,prevFunc.apply(this,arguments));
};
};
Function.prototype.sequence=function(prevFunc){
varnextFunc=this;
returnfunction(){
returnprevFunc.call(this,nextFunc.apply(this,arguments));
};
};
Currying:
Function.prototype.curry=function(numArgs){
varfunc=this;
numArgs=numArgs||func.length;
//recursivelyacquirethearguments
functionsubCurry(prev){
returnfunction(arg){
varargs=prev.concat(arg);
if(args.length<numArgs){
//recursivecase:westillneedmoreargs
returnsubCurry(args);
}
else{
//basecase:applythefunction
returnfunc.apply(this,args);
}
};
};
returnsubCurry([]);
};
Functors:
//map::(a->b)->[a]->[b]
varmap=function(f,a){
returnarr(a).map(func(f));
}
//strmap::(str->str)->str->str
varstrmap=function(f,s){
returnstr(s).split('').map(func(f)).join('');
}
//fcompose::(a->b)*->(a->b)
varfcompose=function(){
varfuncs=arrayOf(func)(arguments);
returnfunction(){
varargsOfFuncs=arguments;
for(vari=funcs.length;i>0;i-=1){
argsOfFuncs=[funcs[i].apply(this,args)];
}
returnargs[0];
};
};
Lenses:
varlens=function(get,set){
varf=function(a){returnget(a)};
f.get=function(a){returnget(a)};
f.set=set;
f.mod=function(f,a){returnset(a,f(get(a)))};
returnf;
};
//usage:
varfirst=lens(
function(a){returnarr(a)[0];},//get
function(a,b){return[b].concat(arr(a).slice(1));}//set
);
Maybes:
varMaybe=function(){};
Maybe.prototype.orElse=function(y){
if(thisinstanceofJust){
returnthis.x;
}
else{
returny;
}
};
varNone=function(){};
None.prototype=Object.create(Maybe.prototype);
None.prototype.toString=function(){return'None';};
varnone=function(){returnnewNone()};
//andtheJustinstance,awrapperforanobjectwithavalue
varJust=function(x){returnthis.x=x;};
Just.prototype=Object.create(Maybe.prototype);
Just.prototype.toString=function(){return"Just"+this.x;};
varjust=function(x){returnnewJust(x)};
varmaybe=function(m){
if(minstanceofNone){
returnm;
}
elseif(minstanceofJust){
returnjust(m.x);
}
else{
thrownewTypeError("Error:JustorNoneexpected,"+m.toString()
+"given.");
}
};
varmaybeOf=function(f){
returnfunction(m){
if(minstanceofNone){
returnm;
}
elseif(minstanceofJust){
returnjust(f(m.x));
}
else{
thrownewTypeError("Error:JustorNoneexpected,"+
m.toString()+"given.");
}
};
};
Mixins:
Object.prototype.plusMixin=function(mixin){
varnewObj=this;
newObj.prototype=Object.create(this.prototype);
newObj.prototype.constructor=newObj;
for(varpropinmixin){
if(mixin.hasOwnProperty(prop)){
newObj.prototype[prop]=mixin[prop];
}
}
returnnewObj;
};
PartialApplication:
functionbindFirstArg(func,a){
returnfunction(b){
returnfunc(a,b);
};
};
Function.prototype.partialApply=function(){
varfunc=this;
args=Array.prototype.slice.call(arguments);
returnfunction(){
returnfunc.apply(this,args.concat(
Array.prototype.slice.call(arguments)
));
};
};
Function.prototype.partialApplyRight=function(){
varfunc=this;
args=Array.prototype.slice.call(arguments);
returnfunction(){
returnfunc.apply(
this,
Array.protype.slice.call(arguments,0)
.concat(args));
};
};
Trampolining:
vartrampoline=function(f){
while(f&&finstanceofFunction){
f=f.apply(f.context,f.args);
}
returnf;
};
varthunk=function(fn){
returnfunction(){
varargs=Array.prototype.slice.apply(arguments);
returnfunction(){returnfn.apply(this,args);};
};
};
TypeSafeties:
vartypeOf=function(type){
returnfunction(x){
if(typeofx===type){
returnx;
}
else{
thrownewTypeError("Error:"+type+"expected,"+typeofx+"
given.");
}
};
};
varstr=typeOf('string'),
num=typeOf('number'),
func=typeOf('function'),
bool=typeOf('boolean');
varobjectTypeOf=function(name){
returnfunction(o){
if(Object.prototype.toString.call(o)==="[object"+name+"]"){
returno;
}
else{
thrownewTypeError("Error:'+name+'expected,somethingelse
given.");
}
};
};
varobj=objectTypeOf('Object');
vararr=objectTypeOf('Array');
vardate=objectTypeOf('Date');
vardiv=objectTypeOf('HTMLDivElement');
//arrayOf::(a->b)->([a]->[b])
vararrayOf=function(f){
returnfunction(a){
returnmap(func(f),arr(a));
}
};
Y-combinator:
varY=function(F){
return(function(f){
returnf(f);
}(function(f){
returnF(function(x){
returnf(f)(x);
});
}));
};
//MemoizingY-Combinator:
varYmem=function(F,cache){
if(!cache){
cache={};//Createanewcache.
}
returnfunction(arg){
if(cache[arg]){
//Answerincache
returncache[arg];
}
//elsecomputetheanswer
varanswer=(F(function(n){
return(Ymem(F,cache))(n);
}))(arg);//Computetheanswer.
cache[arg]=answer;//Cachetheanswer.
returnanswer;
};
};
AppendixB.GlossaryofTermsThisappendixcoverssomeoftheimportanttermsthatareusedinthisbook:
Anonymousfunction:Afunctionthathasnonameandisnotboundtoanyvariables.ItisalsoknownasaLambdaExpression.Callback:Afunctionthatcanbepassedtoanotherfunctiontobeusedinalaterevent.Category:IntermsofCategoryTheory,acategoryisacollectionofobjectsofthesametype.InJavaScript,acategorycanbeanarrayorobjectthatcontainsobjectsthatareallexplicitlydeclaredasnumbers,strings,Booleans,dates,objects,andsoon.CategoryTheory:Aconceptthatorganizesmathematicalstructuresintocollectionsofobjectsandoperationsonthoseobjects.Thedatatypesandfunctionsusedincomputerprogramsformthecategoriesusedinthisbook.Closure:Anenvironmentsuchthatfunctionsdefinedwithinitcanaccesslocalvariablesthatarenotavailableoutsideit.Coupling:Thedegreetowhicheachprogrammodulereliesoneachoftheothermodules.Functionalprogrammingreducestheamountofcouplingwithinaprogram.Currying:Theprocessoftransformingafunctionwithmanyargumentsintoafunctionwithoneargumentthatreturnsanotherfunctionthatcantakemorearguments,asneeded.Formally,afunctionwithNargumentscanbetransformedintoafunctionchainofNfunctions,eachwithonlyoneargument.Declarativeprogramming:Aprogrammingstylethatexpressesthecomputationallogicrequiredtosolvetheproblem.Thecomputeristoldwhattheproblemisratherthantheprocedurerequiredtosolveit.Endofunctor:Afunctorthatmapsacategorytoitself.Functioncomposition:Theprocessofcombiningmanyfunctionsintoonefunction.Theresultofeachfunctionispassedasanargumenttothenext,andtheresultofthelastfunctionistheresultofthewholecomposition.Functionallanguage:Acomputerlanguagethatfacilitatesfunctionalprogramming.Functionalprogramming:Adeclarativeprogrammingparadigmthatfocusesontreatingfunctionsasmathematicalexpressionsandavoidsmutabledataandchangesinstate.Functionalreactiveprogramming:Astyleoffunctionalprogrammingthatfocusesonreactiveelementsandvariablesthatchangeovertimeinresponsetoevents.Functor:Amappingbetweencategories.Higher-orderfunction:Afunctionthattakeseitheroneormorefunctionsasinput,andreturnsafunctionasitsoutput.Inheritance:Anobject-orientedprogrammingcapabilitythatallowsoneclasstoinheritmembervariablesandmethodsfromanotherclass.Lambdaexpressions:SeeAnonymousfunction.Lazyevaluation:Acomputerlanguageevaluationstrategythatdelaystheevaluationofanexpressionuntilitsvalueisneeded.Theoppositeofthisstrategyiscalledeager
evaluationorgreedyevaluation.Lazyevaluationisalsoknownascallbyneed.Library:Asetofobjectsandfunctionsthathaveawell-definedinterfacethatallowsathird-partyprogramtoinvoketheirbehavior.Memoization:Thetechniqueofstoringtheresultsofexpensivefunctioncalls.Whenthefunctioniscalledlaterwiththesamearguments,thestoredresultisreturnedratherthancomputingtheresultagain.Methodchain:Apatterninwhichmanymethodsareinvokedsidebysidebydirectlypassingtheoutputofonemethodtotheinputofthenext.Thisavoidstheneedtoassigntheintermediaryvaluestotemporaryvariables.Mixin:Anobjectthatcanallowotherobjectstouseitsmethods.Themethodsareintendedtobeusedsolelybyotherobjects,andthemixinobjectitselfisnevertobeinstantiated.Modularity:Thedegreetowhichaprogramcanbebrokendownintoindependentmodulesofcode.Functionalprogrammingincreasesthemodularityofprograms.Monad:Astructurethatprovidestheencapsulationrequiredbyfunctors.Morphism:Apurefunctionthatonlyworksonacertaincategoryandalwaysreturnsthesameoutputwhengivenaspecificsetofinputs.Homomorphicoperationsarerestrictedtoasinglecategory,whilepolymorphicoperationscanoperateonmultiplecategories.Partialapplication:Theprocessofbindingvaluestooneormoreargumentsofafunction.Itreturnsapartiallyappliedfunction,whichinturnacceptstheremaining,unboundarguments.Polyfill:Afunctionusedtoaugmentprototypeswithnewfunctions.Itallowsustocallournewfunctionsasmethodsofthepreviousfunction.Purefunction:Afunctionwhoseoutputvaluedependsonlyontheargumentsthataretheinputtothefunction.Thus,callingafunction,f,twicewiththesamevalueofanargument,x,willproducethesameresult,f(x),everytime.Recursivefunction:Afunctionthatcallsitself.Suchfunctionsdependonsolutionstosmallerinstancesofthesameproblemtocomputethesolutiontothelargerproblem.Likeiteration,recursionisanotherwaytorepeatedlycallthesameblockofcode.But,unlikeiteration,recursionrequiresthatthecodeblockdefinethecaseinwhichtherepeatingcodecallsshouldterminate,knownasthebasecase.Reusability:Thedegreetowhichablockofcode,usuallyafunctioninJavaScript,canbereusedinotherpartsofthesameprogramorinotherprograms.Self-invokingfunction:Ananonymousfunctionthatisinvokedimmediatelyafterithasbeendefined.InJavaScript,thisisachievedbyplacingapairofparenthesesafterthefunctionexpression.Strategypattern:Amethodusedtodefineafamilyofinterchangeablealgorithms.Tailrecursion:Astack-basedimplementationofrecursion.Foreveryrecursivecall,thereisanewframeinthestack.Toolkit:Asmallsoftwarelibrarythatprovidesasetoffunctionsfortheprogrammertouse.Comparedtoalibrary,atoolkitissimplerandrequireslesscouplingwiththeprogramthatinvokesit.Trampolining:Astrategyforrecursionthatprovidestail-calleliminationin
programminglanguagesthatdonotprovidethisfeature,suchasJavaScript.Y-combinator:Afixed-pointcombinatorinLambdacalculusthateliminatesexplicitrecursion.Whenitisgivenasinputtoafunctionthatreturnsarecursivefunction,theY-combinatorreturnsthefixedpointofthatfunction,whichisthetransformationfromtherecursivefunctiontoanon-recursivefunction.
IndexA
anonymousfunctions/Anonymousfunctionsapply()function/Apply,call,andthethiskeywordarrayOffunctor/Arraysandfunctorsarrays
about/Arraysandfunctors
Bbackbone.js
about/IntroductionBacon.js/Bacon.jsBilby.js/Bilby.jsbind()function/Bindingargumentsbluebird/Promises
CC++
versusJavaScript/JavaScript’sobject-orientedimplementation–usingprototypes
call()function/Apply,call,andthethiskeywordcategories
about/Categorytheoryinanutshellimplementing/Implementingcategories
categorytheoryabout/Categorytheory,Categorytheoryinanutshelltypesafetyfunctions,creating/Typesafetyobjects/Objectidentities
classicalmixinsabout/Classicalmixins
Clojureabout/Introduction
closureabout/Self-invokingfunctionsandclosuresusing/Self-invokingfunctionsandclosures
CommandLineInterface(CLI)/CLIcompose
programmingwith/Programmingwithcomposecurrying
about/Partialfunctionapplicationandcurrying,Currying
Ee-commercewebsiteapplication
about/Theapplication–ane-commercewebsiteimperativemethods/Imperativemethods
ease.jsabout/IsJavaScriptafunctionalprogramminglanguage?
ECMAScriptabout/IsJavaScriptafunctionalprogramminglanguage?
endofunctorsabout/jQueryisamonad
enginesabout/IsJavaScriptafunctionalprogramminglanguage?
environments,JavaScriptapplicationsbrowsers/Browsersserver-sideJavaScript/Server-sideJavaScriptCommandLineInterface(CLI)/CLI
FFantasyLand/FantasyLandfilter()function
parameters/Array.prototype.filter()forEach()function
parameters/Array.prototype.forEachfunctionalinheritance
about/FunctionalinheritanceStrategyPattern/Functionalinheritance,StrategyPattern
functionallanguagecompiling,intoJavaScript/FunctionallanguagesthatcompileintoJavaScript
Functionallibrariesusing/UsingfunctionallibrarieswithotherJavaScriptmodules
Functionallibraries,forJavaScriptabout/FunctionallibrariesforJavaScriptUnderscore.js/Underscore.jsFantasyLand/FantasyLandBilby.js/Bilby.jsLazy.js/Lazy.jsBacon.js/Bacon.jsFFunctional/Honorablementionswwu.js/Honorablementionssloth.js/Honorablementionsstream.js/HonorablementionsLo-Dash.js/HonorablementionsSsugar/Honorablementionsfrom.js/HonorablementionsJSLINQ/HonorablementionsBoiler.js/HonorablementionsFFolktale/HonorablementionsjjQuery/Honorablementions
functionalmixinsabout/Functionalmixins
functionalprogrammingabout/Introduction,Functionalprogramming,Mostlyfunctionalprogrammingused,innonfunctionalprogramming/Functionalprogramminginanonfunctionalworldevents,handling/Handlingeventsandobject-orientedprogramming,mixing/Mixingfunctionalandobject-orientedprogramminginJavaScript
functionalprogramming,usingobject-orientedprogrammingfunctionalinheritance/Functionalinheritancemixins/Mixins
functionalprogramminglanguageJavaScript/IsJavaScriptafunctionalprogramminglanguage?
functionalprogramminglanguagesabout/Functionalprogramminglanguagesperforming/Whatmakesalanguagefunctional?characteristics/Whatmakesalanguagefunctional?advantages/Advantages,Modularity,Mathematicallycorrect
functionalreactiveprogrammingabout/Functionalreactiveprogrammingreactivity/Reactivitysubscriber,modifying/Puttingitalltogether
FunctionalReactiveProgramming(FRP)/Functionalreactiveprogrammingfunctioncomposition
about/Functioncomposition,Functioncompositions,revisitedcompose()/Composesequence,using/Sequence–composeinreversecompositions,versuschains/Compositionsversuschainsrewriting/Functioncompositions,revisited
functionconstructorabout/Thefunctionconstructor
functiondeclarationsabout/Functiondeclarationsversusfunctionexpressions/Functionexpressions,Unpredictablebehavior
functionexpressionsabout/Functionexpressionsversusfunctionconstructor/Thefunctionconstructor
functionfactories/Functionfactoriesfunctionmanipulation
about/Functionmanipulationapply()function/Apply,call,andthethiskeywordthiskeyword/Apply,call,andthethiskeywordcall()function/Apply,call,andthethiskeywordbind()function/Bindingargumentsfunctionfactories/Functionfactories
Functionsworkingwith/Workingwithfunctionsself-invokingfunction,using/Self-invokingfunctionsandclosuresclosures,using/Self-invokingfunctionsandclosureshigher-orderfunctions/Higher-orderfunctionspurefunctions/Purefunctionsanonymousfunctions/Anonymousfunctionsmethods,chaining/Methodchainsrecursivefunction/Recursionlazyevaluation/Lazyevaluation
HHaskell
about/Whatmakesalanguagefunctional?higher-orderfunctions/Higher-orderfunctionshomomorphicoperations
about/Categorytheoryinanutshell
Iidentityfunctionmorphism/Arraysandfunctorsinheritance
about/InheritancewithObject.create()method/InheritanceinJavaScriptandtheObject.create()method
JJavaScript
about/Introduction,IsJavaScriptafunctionalprogramminglanguage?recursion/Recursionvariablescope/Variablescopefunctiondeclarations/Functiondeclarationsversusfunctionexpressionsversusthefunctionconstructorfunctionexpressions/Functiondeclarationsversusfunctionexpressionsversusthefunctionconstructorfunctionconstructor/Functiondeclarationsversusfunctionexpressionsversusthefunctionconstructormulti-paradigmlanguage/JavaScript–themulti-paradigmlanguageobject-orientedimplementation/JavaScript’sobject-orientedimplementation–usingprototypesversusC++/JavaScript’sobject-orientedimplementation–usingprototypes
jQueryabout/Introduction
jQueryobjectabout/jQueryisamonadimplementing/jQueryisamonad
Juliaabout/Introduction
LLazy.js/Lazy.jslazyevaluation
about/Lazyevaluationbenefits/Lazyevaluation
lens()functionwriting/Lenses
lensesabout/Lenses
LINQ(LanguageIntegratedQuery)about/Honorablementions
Lispabout/Whatmakesalanguagefunctional?
localscope,variablesabout/Localscope
Mmap()function
parameters/Array.prototype.map()maybes
about/Maybeswriting/Maybes
memoizationabout/Memoizationreferencelink/Memoization
mixinsabout/Mixinsclassicalmixins/Classicalmixinsfunctionalmixins/Functionalmixins
monadsabout/Monadsmaybes/Maybespromises/Promiseslenses/LensesjQueryobject/jQueryisamonad
morphismsabout/Categorytheoryinanutshell,Typesafety
MVP(model-view-provider)/UsingfunctionallibrarieswithotherJavaScriptmodules
Oobject-orientedimplementation,JavaScript
prototypes,using/JavaScript’sobject-orientedimplementation–usingprototypesinheritance/Inheritanceprototypechain/JavaScript’sprototypechaininheritance,withObject.create()method/InheritanceinJavaScriptandtheObject.create()method
object-orientedprogrammingandfunctionalprogramming,mixing/Mixingfunctionalandobject-orientedprogramminginJavaScript
Object.create()methodusing/InheritanceinJavaScriptandtheObject.create()method
objectproperties,variablesabout/Objectproperties
objectsabout/Typesafety
ƒogsymbolabout/Categorytheoryinanutshell
Ppartialapplication
about/Partialfunctionapplicationandcurrying,Partialapplicationleftarguments,applying/Partialapplicationfromtheleftrightarguments,applying/Partialapplicationfromtheright
polyadicabout/Functioncomposition
polymorphicoperationsabout/Categorytheoryinanutshell
productionenvironmentabout/Developmentandproductionenvironments
promisesusing/Promises
Promises/A+implementation/Promisesprototypechain
about/JavaScript’sprototypechainprototypes
using,forinheritance/JavaScript’sobject-orientedimplementation–usingprototypes
purefunctions/PurefunctionsPyjs/FunctionallanguagesthatcompileintoJavaScriptPython
about/Introduction
Rrecursion
about/RecursionY-Combinator/TheY-combinator
recursivefunctionabout/RecursionDivideandConquer/Divideandconquer
reduce()functionparameters/Array.prototype.reduce()
Roy/FunctionallanguagesthatcompileintoJavaScriptRuby
about/Introduction
SScalaCheck
about/Bilby.jsScheme
about/Whatmakesalanguagefunctional?scoperesolutions
about/Scoperesolutionsglobalscope/Globalscopelocalscope/Localscopeobjectproperties/Objectproperties
self-invokingfunctionusing/Self-invokingfunctionsandclosures
server-sideJavaScriptfunctionalusecase/Afunctionalusecaseintheserver-sideenvironment
StrategyPatternabout/StrategyPattern
Ttail-callelimination
about/TheTail-calleliminationtrampolining/Trampolining
tailrecursionabout/Tailrecursiontail-callelimination/TheTail-callelimination
ternaryabout/Functioncomposition
thiskeyword/Apply,call,andthethiskeywordthunks
about/Trampoliningtoolkit,functionalprogrammer
about/Thefunctionalprogrammer’stoolkitcallbacks,using/CallbacksArray.prototype.map()/Array.prototype.map()Array.prototype.filter()/Array.prototype.filter()Array.prototype.reduce()/Array.prototype.reduce()Array.prototype.forEach/Array.prototype.forEachArray.prototype.concat/Array.prototype.concatArray.prototype.reverse/Array.prototype.reverseArray.prototype.sort/Array.prototype.sortArray.prototype.every/Array.prototype.everyandArray.prototype.someArray.prototype.some/Array.prototype.everyandArray.prototype.some
Trampoliningabout/IsJavaScriptafunctionalprogramminglanguage?
trampoliningabout/Trampolining
TypeScript/FunctionallanguagesthatcompileintoJavaScript
UUHC/FunctionallanguagesthatcompileintoJavaScriptUnaryfunctions
about/Functioncompositionunderscore.js
about/IntroductionUnderscore.js/Underscore.js
Vvariablescope
about/Variablescopescoperesolutions/Scoperesolutionsissues/Closuresfeatures/Gotchas
variadicabout/Functioncomposition