table of contents · 2015. 10. 22. · part 3 will start to dance the fine line between practical...
TRANSCRIPT
-
1. Introduction2. Chapter1:Whateverarewedoing?
i. Introductionsii. Abriefencounter
3. Chapter2:FirstClassFunctionsi. Aquickreviewii. Whyfavorfirstclass?
4. Chapter3:PureHappinesswithPureFunctionsi. Ohtobepureagainii. Sideeffectsmayinclude...iii. 8thgrademathiv. Thecaseforpurityv. InSummary
5. Chapter4:Curryingi. Can'tliveiflivin'iswithoutyouii. Morethanapun/Specialsauceiii. InSummary
6. Chapter5:CodingbyComposingi. FunctionalHusbandryii. Pointfreeiii. Debuggingiv. CategoryTheoryv. InSummary
7. Chapter6:ExampleApplicationi. DeclarativeCodingii. Aflickroffunctionalprogrammingiii. APrincipledRefactoriv. InSummary
8. Chapter7:Hindley-MilnerandMei. What'syourtype?ii. Talesfromthecrypticiii. Narrowingthepossibilityiv. Freeasintheoremv. InSummary
9. Chapter8:Tupperwarei. TheMightyContainerii. MyFirstFunctoriii. Schrödinger’sMaybeiv. PureErrorHandlingv. OldMcDonaldhadEffects…vi. AsynchronousTasksvii. ASpotofTheoryviii. InSummary
10. Chapter9:MonadicOnionsi. PointyFunctorFactoryii. MixingMetaphorsiii. Mychainhitsmychestiv. Theoryv. InSummary
TableofContents
mostly-adequate-guide
2
-
11. Chapter10:ApplicativeFunctorsi. ApplyingApplicativesii. Shipsinbottlesiii. CoordinationMotivationiv. Bro,doyouevenlift?v. Freecanopenersvi. Lawsvii. InSummary
mostly-adequate-guide
3
-
Thisisabookonthefunctionalparadigmingeneral.We'llusetheworld'smostpopularfunctionalprogramminglanguage:JavaScript.Somemayfeelthisisapoorchoiceasit'sagainstthegrainofthecurrentculturewhich,atthemoment,feelspredominatelyimperative.However,IbelieveitisthebestwaytolearnFPforseveralreasons:
Youlikelyuseiteverydayatwork.
ThismakesitpossibletopracticeandapplyyouracquiredknowledgeeachdayonrealworldprogramsratherthanpetprojectsonnightsandweekendsinanesotericFPlanguage.
Wedon'thavetolearneverythingupfronttostartwritingprograms.
Inapurefunctionallanguage,youcannotlogavariableorreadaDOMnodewithoutusingmonads.Herewecancheatalittleaswelearntopurifyourcodebase.It'salsoeasiertogetstartedinthislanguagesinceit'smixedparadigmandyoucanfallbackonyourcurrentpracticeswhiletherearegapsinyourknowledge.
Aboutthisbook
mostly-adequate-guide
4Introduction
-
Thelanguageisfullycapableofwritingtopnotchfunctionalcode.
WehaveallthefeaturesweneedtomimicalanguagelikeScalaorHaskellwiththehelpofatinylibraryortwo.Object-orientedprogrammingcurrentlydominatestheindustry,butit'sclearlyawkwardinJavaScript.It'sakintocampingoffofahighwayortapdancingingaloshes.Wehavetobindallovertheplacelestthischangeoutfromunderus,wedon'thaveclasses(Yet),wehavevariousworkaroundsforthequirkybehaviorwhenthenewkeywordisforgotten,privatemembersareonlyavailableviaclosures.Toalotofus,FPfeelsmorenaturalanyways.
Thatsaid,typedfunctionallanguageswill,withoutadoubt,bethebestplacetocodeinthestylepresentedbythisbook.JavaScriptwillbeourmeansoflearningaparadigm,whereyouapplyitisuptoyou.Luckily,theinterfacesaremathematicaland,assuch,ubiquitous.You'llfindyourselfathomewithswiftz,scalaz,haskell,purescript,andothermathematicallyinclinedenvironments.
ReaditonlineDownloadEPUBDownloadMobi(Kindle)
gitclonehttps://github.com/DrBoolean/mostly-adequate-guide.git
cdmostly-adequate-guide/npminstallgitbook-cli-ggitbookinit
brewupdatebrewcaskinstallcalibre
gitbookmobi../functional.mobi
SeeSUMMARY.md
SeeCONTRIBUTING.md
SeeTRANSLATIONS.md
Part1isaguidetothebasics.I'mupdatingasIfinderrorssincethisistheinitialdraft.Feelfreetohelp!Part2willaddresstypeclasseslikefunctorsandmonadsallthewaythroughtotraversable.Ihopetosqueezeintransformersandapureapplication.Part3willstarttodancethefinelinebetweenpracticalprogrammingandacademicabsurdity.We'lllookatcomonads,f-algebras,freemonads,yoneda,andothercategoricalconstructs.
Gitbook(forabetterreadingexperience)
Doityourself
TableofContents
Contributing
Translations
Plansforthefuture
mostly-adequate-guide
5Introduction
http://drboolean.gitbooks.io/mostly-adequate-guide/https://www.gitbook.com/download/epub/book/drboolean/mostly-adequate-guidehttps://www.gitbook.com/download/mobi/book/drboolean/mostly-adequate-guide
-
HiI'mProfessorFranklinRisby,pleasedtomakeyouracquaintance.We'llbespendingsometimetogetherasI'msupposedtoteachyouabitaboutfunctionalprogramming.Butenoughaboutme,whataboutyou?I'mhopingyou'refamiliarwiththeJavaScriptlanguage,haveateensybitofObject-Orientedexperience,andfancyyourselfaworkingclassprogrammer.Youdon'tneedtohaveaPh.DinEntomology,youjustneedtoknowhowtofindandkillsomebugs.
Iwon'tassumeanypreviousfunctionalprogrammingknowledgebecausewebothknowwhathappenswhenyouassume,butIwillexpectyoutohaverunintosomeoftheunfavorablesituationsthatarisefromworkingwithmutablestate,unrestrictedsideeffects,andunprincipleddesign.Nowthatwe'vebeenproperlyintroduced,let'sgetonwithit.
Thepurposeofthischapteristogiveyouafeelforwhatwe'reafterwhenwewritefunctionalprograms.Wemusthavesomeideaaboutwhatmakesaprogramfunctionalorwe'llfindourselvesscribblingaimlessly,avoidingobjectsatallcosts-aclumsyendeavorindeed.Weneedabullseyetohurlourcodetoward,somecelestialcompassforwhenthewatersgetrough.
Nowtherearesomegeneralprogrammingprinciples,variousacronymiccredosthatguideusthroughthedarktunnelsofanyapplication:DRY(don'trepeatyourself),loosecouplinghighcohesion,YAGNI(yaain'tgonnaneedit),principleofleastsurprise,singleresponsibility,andsoon.
Iwon'tbelaborlistingeachandeveryguidelineI'veheardthroughouttheyears...thepointisthattheyholdupinafunctionalsetting,thoughthey'remerelytangentialtoourgoal.WhatI'dlikeyoutogetafeelfornow,beforewegetanyfurther,isourintentionwhenwepokeandprodatthekeyboard;ourfunctionalXanadu.
Let'sstartwithatouchofinsanity.Hereisaseagullapplication.Whenflocksconjointheybecomealargerflockandwhentheybreedtheyincreasebythenumberofseagullswithwhomthey'rebreeding.NowthisisnotintendedtobegoodObject-Orientedcode,mindyou,itisheretohighlighttheperilsofourmodern,assignmentbasedapproach.Behold:
varFlock=function(n){this.seagulls=n;};
Flock.prototype.conjoin=function(other){this.seagulls+=other.seagulls;returnthis;};
Flock.prototype.breed=function(other){this.seagulls=this.seagulls*other.seagulls;returnthis;};
varflock_a=newFlock(4);varflock_b=newFlock(2);varflock_c=newFlock(0);
varresult=flock_a.conjoin(flock_c).breed(flock_b).conjoin(flock_a.breed(flock_b)).seagulls;//=>32
Whoonearthwouldcraftsuchaghastlyabomination?Itisunreasonablydifficulttokeeptrackofthemutatinginternal
Chapter1:Whateverarewedoing?
Introductions
Abriefencounter
mostly-adequate-guide
6Chapter1:Whateverarewedoing?
-
state.And,goodheavens,theanswerisevenincorrect!Itshouldhavebeen16,butflock_awounduppermanentlyalteredintheprocess.Poorflock_a.ThisisanarchyintheI.T.!Thisiswildanimalarithmetic!
Ifyoudon'tunderstandthisprogram,it'sokay,neitherdoI.Thepointisthatstateandmutablevaluesarehardtofolloweveninsuchasmallexample.
Let'stryagainwithamorefunctionalapproach:
varconjoin=function(flock_x,flock_y){returnflock_x+flock_y};varbreed=function(flock_x,flock_y){returnflock_x*flock_y};
varflock_a=4;varflock_b=2;varflock_c=0;
varresult=conjoin(breed(flock_b,conjoin(flock_a,flock_c)),breed(flock_a,flock_b));//=>16
Well,wegottherightanswerthistime.There'smuchlesscode.Thefunctionnestingisatadconfusing...(we'llremedythissituationinch5).It'sbetter,butlet'sdigdeeper.Therearebenefitstocallingaspadeaspade.Hadwedoneso,wemighthaveseenwe'rejustworkingwithsimpleaddition(conjoin)andmultiplication(breed).
There'sreallynothingspecialatallaboutthesetwofunctionsotherthantheirnames.Let'srenameourcustomfunctionstorevealtheirtrueidentity.
varadd=function(x,y){returnx+y};varmultiply=function(x,y){returnx*y};
varflock_a=4;varflock_b=2;varflock_c=0;
varresult=add(multiply(flock_b,add(flock_a,flock_c)),multiply(flock_a,flock_b));//=>16
Andwiththat,wegaintheknowledgeoftheancients:
//associativeadd(add(x,y),z)==add(x,add(y,z));
//commutativeadd(x,y)==add(y,x);
//identityadd(x,0)==x;
//distributivemultiply(x,add(y,z))==add(multiply(x,y),multiply(x,z));
Ahyes,thoseoldfaithfulmathematicalpropertiesshouldcomeinhandy.Don'tworryifyoudidn'tknowthemrightoffthetopofyourhead.Foralotofus,it'sbeenawhilesincewe'vereviewedthisinformation.Let'sseeifwecanusethesepropertiestosimplifyourlittleseagullprogram.
//Originallineadd(multiply(flock_b,add(flock_a,flock_c)),multiply(flock_a,flock_b));
mostly-adequate-guide
7Chapter1:Whateverarewedoing?
-
//Applytheidentitypropertytoremovetheextraadd//(add(flock_a,flock_c)==flock_a)add(multiply(flock_b,flock_a),multiply(flock_a,flock_b));
//Applydistributivepropertytoachieveourresultmultiply(flock_b,add(flock_a,flock_a));
Brilliant!Wedidn'thavetowritealickofcustomcodeotherthanourcallingfunction.Weincludeaddandmultiplydefinitionshereforcompleteness,butthereisreallynoneedtowritethem-wesurelyhaveanaddandmultiplyprovidedbysomepreviouslywrittenlibrary.
Youmaybethinking"howverystrawmanofyoutoputsuchamathyexampleupfront".Or"realprogramsarenotthissimpleandcannotbereasonedaboutinsuchaway".I'vechosenthisexamplebecausemostofusalreadyknowaboutadditionandmultiplicationsoit'seasytoseehowmathcanbeofusetoushere.
Don'tdespair,throughoutthisbook,we'llsprinkleinsomecategorytheory,settheory,andlambdacalculustowriterealworldexamplesthatachievethesamesimplicityandresultsasourflockofseagullsexample.Youneedn'tbeamathematicianeither,itwillfeeljustlikeusinganormalframeworkorapi.
Itmaycomeasasurprisetohearthatwecanwritefull,everydayapplicationsalongthelinesofthefunctionalanalogabove.Programsthathavesoundproperties.Programsthatareterse,yeteasytoreasonabout.Programsthatdon'treinventthewheelateveryturn.Lawlessnessisgoodifyou'reacriminal,butinthisbook,we'llwanttoacknowledgeandobeythelawsofmath.
We'llwanttousethetheorywhereeverypiecetendstofittogethersopolitely.We'llwanttorepresentourspecificproblemintermsofgeneric,composablebitsandthenexploittheirpropertiesforourownselfishbenefit.Itwilltakeabitmoredisciplinethanthe"anythinggoes"approachofimperative(We'llgoovertheprecisedefinitionofimperativelaterinthebook,butfornowit'sanythingotherthanfunctionalprogramming)programming,butthepayoffofworkingwithinaprincipled,mathematicalframeworkwillastoundyou.
We'veseenaflickerofourfunctionalnorthstar,butthereareafewconcreteconceptstograspbeforewecanreallybeginourjourney.
Chapter2:FirstClassFunctions
mostly-adequate-guide
8Chapter1:Whateverarewedoing?
-
Whenwesayfunctionsare"firstclass",wemeantheyarejustlikeeveryoneelse...sonormalclass(coach?).Wecantreatfunctionslikeanyotherdatatypeandthereisnothingparticularlyspecialaboutthem-storetheminarrays,passthemaround,assignthemtovariables,whathaveyou.
ThatisJavaScript101,butworthamentionasaquickcodesearchongithubwillshowthecollectiveevasion,orperhapswidespreadignoranceoftheconcept.Shallwegoforafeignedexample?Weshall.
varhi=function(name){return"Hi"+name;};
vargreeting=function(name){returnhi(name);};
Here,thefunctionwrapperaroundhiingreetingiscompletelyredundant.Why?BecausefunctionsarecallableinJavaScript.Whenhihasthe()attheenditwillrunandreturnavalue.Whenitdoesnot,itsimplyreturnsthefunctionstoredinthevariable.Justtobesure,havealook-see:
hi;//function(name){//return"Hi"+name//}
hi("jonas");//"Hijonas"
Sincegreetingismerelyturningaroundandcallinghiwiththeverysameargument,wecouldsimplywrite:
vargreeting=hi;
greeting("times");//"Hitimes"
Inotherwords,hiisalreadyafunctionthatexpectsoneargument,whyplaceanotherfunctionarounditthatsimplycallshiwiththesamebloodyargument?Itdoesn'tmakeanydamnsense.It'slikedonningyourheaviestparkainthedeadofJulyjusttoblasttheairanddemandanicelolly.
Itisobnoxiouslyverboseand,asithappens,badpracticetosurroundafunctionwithanotherfunctionmerelytodelayevaluation.(We'llseewhyinamoment,butithastodowithmaintenance.)
Asolidunderstandingofthisiscriticalbeforemovingon,solet'sseeafewmorefunexamplesexcavatedfromnpmmodules.
//ignorantvargetServerStuff=function(callback){returnajaxCall(function(json){returncallback(json);
Chapter2:FirstClassFunctions
Aquickreview
mostly-adequate-guide
9Chapter2:FirstClassFunctions
-
});};
//enlightenedvargetServerStuff=ajaxCall;
Theworldislitteredwithajaxcodeexactlylikethis.Hereisthereasonbothareequivalent:
//thislinereturnajaxCall(function(json){returncallback(json);});
//isthesameasthislinereturnajaxCall(callback);
//sorefactorgetServerStuffvargetServerStuff=function(callback){returnajaxCall(callback);};
//...whichisequivalenttothisvargetServerStuff=ajaxCall;//
-
Okay,let'sgetdowntothereasonstofavorfirstclassfunctions.AswesawinthegetServerStuffandBlogControllerexamples,it'seasytoaddlayersofindirectionthathavenoactualvalueandonlyincreasetheamountofcodetomaintainandsearchthrough.
Inaddition,ifafunctionweareneedlesslywrappingdoeschange,wemustalsochangeourwrapperfunction.
httpGet('/post/2',function(json){returnrenderPost(json);});
IfhttpGetweretochangetosendapossibleerr,wewouldneedtogobackandchangethe"glue".
//gobacktoeveryhttpGetcallintheapplicationandexplicitlypasserr//along.httpGet('/post/2',function(json,err){returnrenderPost(json,err);});
Hadwewrittenitasafirstclassfunction,muchlesswouldneedtochange:
//renderPostiscalledfromwithinhttpGetwithhowevermanyargumentsitwantshttpGet('/post/2',renderPost);
Besidestheremovalofunnecessaryfunctions,wemustnameandreferencearguments.Namesareabitofanissue,yousee.Wehavepotentialmisnomers-especiallyasthecodebaseagesandrequirementschange.
Havingmultiplenamesforthesameconceptisacommonsourceofconfusioninprojects.Thereisalsotheissueofgenericcode.Forinstance,thesetwofunctionsdoexactlythesamething,butonefeelsinfinitelymoregeneralandreusable:
//specifictoourcurrentblogvarvalidArticles=function(articles){returnarticles.filter(function(article){returnarticle!==null&&article!==undefined;});};
//vastlymorerelevantforfutureprojectsvarcompact=function(xs){returnxs.filter(function(x){returnx!==null&&x!==undefined;});};
Bynamingthings,we'veseeminglytiedourselvestospecificdata(inthiscasearticles).Thishappensquiteabitandisasourceofmuchreinvention.
Imustmentionthat,justlikewithObject-Orientedcode,youmustbeawareofthiscomingtobiteyouinthejugular.Ifanunderlyingfunctionusesthisandwecallitfirstclass,wearesubjecttothisleakyabstraction'swrath.
varfs=require('fs');
//scary
Whyfavorfirstclass?
mostly-adequate-guide
11Chapter2:FirstClassFunctions
-
fs.readFile('freaky_friday.txt',Db.save);
//lesssofs.readFile('freaky_friday.txt',Db.save.bind(Db));
Havingbeenboundtoitself,theDbisfreetoaccessitsprototypicalgarbagecode.Iavoidusingthislikeadirtynappy.There'sreallynoneedwhenwritingfunctionalcode.However,wheninterfacingwithotherlibraries,you'llhavetoacquiescetothemadworldaroundus.
Somewillarguethisisnecessaryforspeed.Ifyouarethemicro-optimizationsort,pleaseclosethisbook.Ifyoucannotgetyourmoneyback,perhapsyoucanexchangeitforsomethingmorefiddly.
Andwiththat,we'rereadytomoveon.
Chapter3:PureHappinesswithPureFunctions
mostly-adequate-guide
12Chapter2:FirstClassFunctions
-
Onethingweneedtogetstraightistheideaofapurefunction.
Apurefunctionisafunctionthat,giventhesameinput,willalwaysreturnthesameoutputanddoesnothaveanyobservablesideeffect.
Takesliceandsplice.Theyaretwofunctionsthatdotheexactsamething-inavastlydifferentway,mindyou,butthesamethingnonetheless.Wesaysliceispurebecauseitreturnsthesameoutputperinputeverytime,guaranteed.splice,however,willchewupitsarrayandspititbackoutforeverchangedwhichisanobservableeffect.
varxs=[1,2,3,4,5];
//purexs.slice(0,3);//=>[1,2,3]
xs.slice(0,3);//=>[1,2,3]
xs.slice(0,3);//=>[1,2,3]
//impurexs.splice(0,3);//=>[1,2,3]
xs.splice(0,3);//=>[4,5]
xs.splice(0,3);//=>[]
Infunctionalprogramming,wedislikeunwieldyfunctionslikesplicethatmutatedata.Thiswillneverdoaswe'restrivingforreliablefunctionsthatreturnthesameresulteverytime,notfunctionsthatleaveamessintheirwakelikesplice.
Let'slookatanotherexample.
//impurevarminimum=21;
varcheckAge=function(age){returnage>=minimum;};
//purevarcheckAge=function(age){varminimum=21;returnage>=minimum;};
Intheimpureportion,checkAgedependsonthemutablevariableminimumtodeterminetheresult.Inotherwords,itdependsonsystemstatewhichisdisappointingbecauseitincreasesthecognitiveloadbyintroducinganexternalenvironment.
Chapter3:PureHappinesswithPureFunctions
Ohtobepureagain
mostly-adequate-guide
13Chapter3:PureHappinesswithPureFunctions
-
Itmightnotseemlikealotinthisexample,butthisrelianceuponstateisoneofthelargestcontributorstosystemcomplexity(http://www.curtclifton.net/storage/papers/MoseleyMarks06a.pdf).ThischeckAgemayreturndifferentresultsdependingonfactorsexternaltoinput,whichnotonlydisqualifiesitfrombeingpure,butalsoputsourmindsthroughtheringereachtimewe'rereasoningaboutthesoftware.
Itspureform,ontheotherhand,iscompletelyselfsufficient.Wecanalsomakeminimumimmutable,whichpreservesthepurityasthestatewillneverchange.Todothis,wemustcreateanobjecttofreeze.
varimmutableState=Object.freeze({minimum:21});
Let'slookmoreatthese"sideeffects"toimproveourintuition.Sowhatisthisundoubtedlynefarioussideeffectmentionedinthedefinitionofpurefunction?We'llbereferringtoeffectasanythingthatoccursinourcomputationbesidesthecalculationofaresult.
There'snothingintrinsicallybadabouteffectsandwe'llbeusingthemallovertheplaceinthechapterstocome.It'sthatsidepartthatbearsthenegativeconnotation.Wateraloneisnotaninherentlarvaeincubator,it'sthestagnantpartthatyieldstheswarms,andIassureyou,sideeffectsareasimilarbreedinggroundinyourownprograms.
Asideeffectisachangeofsystemstateorobservableinteractionwiththeoutsideworldthatoccursduringthecalculationofaresult.
Sideeffectsmayinclude,butarenotlimitedto
changingthefilesysteminsertingarecordintoadatabasemakinganhttpcallmutationsprintingtothescreen/loggingobtaininguserinputqueryingtheDOMaccessingsystemstate
Andthelistgoesonandon.Anyinteractionwiththeworldoutsideofafunctionisasideeffect,whichisafactthatmaypromptyoutosuspectthepracticalityofprogrammingwithoutthem.Thephilosophyoffunctionalprogrammingpostulatesthatsideeffectsareaprimarycauseofincorrectbehavior.
Itisnotthatwe'reforbiddentousethem,ratherwewanttocontainthemandruntheminacontrolledway.We'lllearnhowtodothiswhenwegettofunctorsandmonadsinlaterchapters,butfornow,let'strytokeeptheseinsidiousfunctionsseparatefromourpureones.
Sideeffectsdisqualifyafunctionfrombeingpureanditmakessense:purefunctions,bydefinition,mustalwaysreturnthesameoutputgiventhesameinput,whichisnotpossibletoguaranteewhendealingwithmattersoutsideourlocalfunction.
Let'stakeacloserlookatwhyweinsistonthesameoutputperinput.Popyourcollars,we'regoingtolookatsome8thgrademath.
Sideeffectsmayinclude...
8thgrademath
mostly-adequate-guide
14Chapter3:PureHappinesswithPureFunctions
http://www.curtclifton.net/storage/papers/MoseleyMarks06a.pdf
-
Frommathisfun.com:
Afunctionisaspecialrelationshipbetweenvalues:Eachofitsinputvaluesgivesbackexactlyoneoutputvalue.
Inotherwords,it'sjustarelationbetweentwovalues:theinputandtheoutput.Thougheachinputhasexactlyoneoutput,thatoutputdoesn'tnecessarilyhavetobeuniqueperinput.Belowshowsadiagramofaperfectlyvalidfunctionfromxtoy;
(http://www.mathsisfun.com/sets/function.html)
Tocontrast,thefollowingdiagramshowsarelationthatisnotafunctionsincetheinputvalue5pointstoseveraloutputs:
(http://www.mathsisfun.com/sets/function.html)
Functionscanbedescribedasasetofpairswiththeposition(input,output):[(1,2),(3,6),(5,10)](Itappearsthisfunctiondoublesitsinput).
Orperhapsatable:
Input Output
1 2
2 4
3 6
Orevenasagraphwithxastheinputandyastheoutput:
mostly-adequate-guide
15Chapter3:PureHappinesswithPureFunctions
http://www.mathsisfun.com/sets/function.htmlhttp://www.mathsisfun.com/sets/function.html
-
There'snoneedforimplementationdetailsiftheinputdictatestheoutput.Sincefunctionsaresimplymappingsofinputtooutput,onecouldsimplyjotdownobjectliteralsandrunthemwith[]insteadof().
vartoLowerCase={"A":"a","B":"b","C":"c","D":"d","E":"e","D":"d"};
toLowerCase["C"];//=>"c"
varisPrime={1:false,2:true,3:true,4:false,5:true,6:false};
isPrime[3];//=>true
Ofcourse,youmightwanttocalculateinsteadofhandwritingthingsout,butthisillustratesadifferentwaytothinkaboutfunctions.(Youmaybethinking"whataboutfunctionswithmultiplearguments?".Indeed,thatpresentsabitofaninconveniencewhenthinkingintermsofmathematics.Fornow,wecanbundlethemupinanarrayorjustthinkoftheargumentsobjectastheinput.Whenwelearnaboutcurrying,we'llseehowwecandirectlymodelthemathematicaldefinitionofafunction.)
Herecomesthedramaticreveal:Purefunctionsaremathematicalfunctionsandthey'rewhatfunctionalprogrammingisallabout.Programmingwiththeselittleangelscanprovidehugebenefits.Let'slookatsomereasonswhywe'rewillingtogotogreatlengthstopreservepurity.
Forstarters,purefunctionscanalwaysbecachedbyinput.Thisistypicallydoneusingatechniquecalledmemoization:
varsquareNumber=memoize(function(x){returnx*x;});
squareNumber(4);//=>16
squareNumber(4);//returnscacheforinput4//=>16
squareNumber(5);//=>25
Thecaseforpurity
Cacheable
mostly-adequate-guide
16Chapter3:PureHappinesswithPureFunctions
-
squareNumber(5);//returnscacheforinput5//=>25
Hereisasimplifiedimplementation,thoughthereareplentyofmorerobustversionsavailable.
varmemoize=function(f){varcache={};
returnfunction(){vararg_str=JSON.stringify(arguments);cache[arg_str]=cache[arg_str]||f.apply(f,arguments);returncache[arg_str];};};
Somethingtonoteisthatyoucantransformsomeimpurefunctionsintopureonesbydelayingevaluation:
varpureHttpCall=memoize(function(url,params){returnfunction(){return$.getJSON(url,params);}});
Theinterestingthinghereisthatwedon'tactuallymakethehttpcall-weinsteadreturnafunctionthatwilldosowhencalled.Thisfunctionispurebecauseitwillalwaysreturnthesameoutputgiventhesameinput:thefunctionthatwillmaketheparticularhttpcallgiventheurlandparams.
Ourmemoizefunctionworksjustfine,thoughitdoesn'tcachetheresultsofthehttpcall,ratheritcachesthegeneratedfunction.
Thisisnotveryusefulyet,butwe'llsoonlearnsometricksthatwillmakeitso.Thetakeawayisthatwecancacheeveryfunctionnomatterhowdestructivetheyseem.
Purefunctionsarecompletelyselfcontained.Everythingthefunctionneedsishandedtoitonasilverplatter.Ponderthisforamoment...Howmightthisbebeneficial?Forstarters,afunction'sdependenciesareexplicitandthereforeeasiertoseeandunderstand-nofunnybusinessgoingonunderthehood.
//impurevarsignUp=function(attrs){varuser=saveUser(attrs);welcomeUser(user);};
//purevarsignUp=function(Db,Email,attrs){returnfunction(){varuser=saveUser(Db,attrs);welcomeUser(Email,user);};};
Theexampleheredemonstratesthatthepurefunctionmustbehonestaboutitsdependenciesand,assuch,tellusexactlywhatit'supto.Justfromitssignature,weknowthatitwilluseaDb,Email,andattrswhichshouldbetellingtosaytheleast.
We'lllearnhowtomakefunctionslikethispurewithoutmerelydeferringevaluation,butthepointshouldbeclearthatthepureformismuchmoreinformativethanitssneakyimpurecounterpartwhichisuptoGodknowswhat.
Portable/Self-Documenting
mostly-adequate-guide
17Chapter3:PureHappinesswithPureFunctions
-
Somethingelsetonoticeisthatwe'reforcedto"inject"dependencies,orpasstheminasarguments,whichmakesourappmuchmoreflexiblebecausewe'veparameterizedourdatabaseormailclientorwhathaveyou(Don'tworry,we'llseeawaytomakethislesstediousthanitsounds).ShouldwechoosetouseadifferentDbweneedonlytocallourfunctionwithit.Shouldwefindourselveswritinganewapplicationinwhichwe'dliketoreusethisreliablefunction,wesimplygivethisfunctionwhateverDbandEmailwehaveatthetime.
InaJavaScriptsetting,portabilitycouldmeanserializingandsendingfunctionsoverasocket.Itcouldmeanrunningallourappcodeinwebworkers.Portabilityisapowerfultrait.
Contraryto"typical"methodsandproceduresinimperativeprogrammingrooteddeepintheirenvironmentviastate,dependencies,andavailableeffects,purefunctionscanberunanywhereourheartsdesire.
Whenwasthelasttimeyoucopiedamethodintoanewapp?OneofmyfavoritequotescomesfromErlangcreator,JoeArmstrong:"Theproblemwithobject-orientedlanguagesisthey’vegotallthisimplicitenvironmentthattheycarryaroundwiththem.Youwantedabananabutwhatyougotwasagorillaholdingthebanana...andtheentirejungle".
Next,wecometorealizepurefunctionsmaketestingmucheasier.Wedon'thavetomocka"real"paymentgatewayorsetupandassertthestateoftheworldaftereachtest.Wesimplygivethefunctioninputandassertoutput.
Infact,wefindthefunctionalcommunitypioneeringnewtesttoolsthatcanblastourfunctionswithgeneratedinputandassertthatpropertiesholdontheoutput.It'sbeyondthescopeofthisbook,butIstronglyencourageyoutosearchforandtryQuickcheck-atestingtoolthatistailoredforapurelyfunctionalenvironment.
Manybelievethebiggestwinwhenworkingwithpurefunctionsisreferentialtransparency.Aspotofcodeisreferentiallytransparentwhenitcanbesubstitutedforitsevaluatedvaluewithoutchangingthebehavioroftheprogram.
Sincepurefunctionsalwaysreturnthesameoutputgiventhesameinput,wecanrelyonthemtoalwaysreturnthesameresultsandthuspreservereferentialtransparency.Let'sseeanexample.
varImmutable=require("immutable");
vardecrementHP=function(player){returnplayer.set("hp",player.get("hp")-1);};
varisSameTeam=function(player1,player2){returnplayer1.get("team")===player2.get("team");};
varpunch=function(player,target){if(isSameTeam(player,target)){returntarget;}else{returndecrementHP(target);}};
varjobe=Immutable.Map({name:"Jobe",hp:20,team:"red"});varmichael=Immutable.Map({name:"Michael",hp:20,team:"green"});
punch(jobe,michael);//=>Immutable.Map({name:"Michael",hp:19,team:"green"})
decrementHP,isSameTeamandpunchareallpureandthereforereferentiallytransparent.Wecanuseatechniquecalledequationalreasoningwhereinonesubstitutes"equalsforequals"toreasonaboutcode.It'sabitlikemanuallyevaluating
Testable
Reasonable
mostly-adequate-guide
18Chapter3:PureHappinesswithPureFunctions
-
thecodewithouttakingintoaccountthequirksofprogrammaticevaluation.Usingreferentialtransparency,let'splaywiththiscodeabit.
Firstwe'llinlinethefunctionisSameTeam.
varpunch=function(player,target){if(player.get("team")===target.get("team")){returntarget;}else{returndecrementHP(target);}};
Sinceourdataisimmutable,wecansimplyreplacetheteamswiththeiractualvalue
varpunch=function(player,target){if("red"==="green"){returntarget;}else{returndecrementHP(target);}};
Weseethatitisfalseinthiscasesowecanremovetheentireifbranch
varpunch=function(player,target){returndecrementHP(target);};
AndifweinlinedecrementHP,weseethat,inthiscase,punchbecomesacalltodecrementthehpby1.
varpunch=function(player,target){returntarget.set("hp",target.get("hp")-1);};
Thisabilitytoreasonaboutcodeisterrificforrefactoringandunderstandingcodeingeneral.Infact,weusedthistechniquetorefactorourflockofseagullsprogram.Weusedequationalreasoningtoharnessthepropertiesofadditionandmultiplication.Indeed,we'llbeusingthesetechniquesthroughoutthebook.
Finally,andhere'sthecoupdegrâce,wecanrunanypurefunctioninparallelsinceitdoesnotneedaccesstosharedmemoryanditcannot,bydefinition,havearaceconditionduetosomesideeffect.
Thisisverymuchpossibleinaserversidejsenvironmentwiththreadsaswellasinthebrowserwithwebworkersthoughcurrentcultureseemstoavoiditduetocomplexitywhendealingwithimpurefunctions.
We'veseenwhatpurefunctionsareandwhywe,asfunctionalprogrammers,believetheyarethecat'seveningwear.Fromthispointon,we'llstrivetowriteallourfunctionsinapureway.We'llrequiresomeextratoolstohelpusdoso,butinthemeantime,we'lltrytoseparatetheimpurefunctionsfromtherestofthepurecode.
ParallelCode
InSummary
mostly-adequate-guide
19Chapter3:PureHappinesswithPureFunctions
-
Writingprogramswithpurefunctionsisatadlaboriouswithoutsomeextratoolsinourbelt.Wehavetojuggledatabypassingargumentsallovertheplace,we'reforbiddentousestate,nottomentioneffects.Howdoesonegoaboutwritingthesemasochisticprograms?Let'sacquireanewtoolcalledcurry.
Chapter4:Currying
mostly-adequate-guide
20Chapter3:PureHappinesswithPureFunctions
-
MyDadonceexplainedhowtherearecertainthingsonecanlivewithoutuntiloneacquiresthem.Amicrowaveisonesuchthing.Smartphones,another.Theolderfolksamonguswillrememberafulfillinglifesansinternet.Forme,curryingisonthislist.
Theconceptissimple:Youcancallafunctionwithfewerargumentsthanitexpects.Itreturnsafunctionthattakestheremainingarguments.
Youcanchoosetocallitallatonceorsimplyfeedineachargumentpiecemeal.
varadd=function(x){returnfunction(y){returnx+y;};};
varincrement=add(1);varaddTen=add(10);
increment(2);//3
addTen(2);//12
Herewe'vemadeafunctionaddthattakesoneargumentandreturnafunction.Bycallingit,thereturnedfunctionremembersthefirstargumentfromthenonviatheclosure.Callingitwithbothargumentsallatonceisabitofapain,however,sowecanuseaspecialhelperfunctioncalledcurrytomakedefiningandcallingfunctionslikethiseasier.
Let'ssetupafewcurriedfunctionsforourenjoyment.
varcurry=require('lodash.curry');
varmatch=curry(function(what,str){returnstr.match(what);});
varreplace=curry(function(what,replacement,str){returnstr.replace(what,replacement);});
varfilter=curry(function(f,ary){returnary.filter(f);});
varmap=curry(function(f,ary){returnary.map(f);});
ThepatternI'vefollowedisasimple,butimportantone.I'vestrategicallypositionedthedatawe'reoperatingon(String,Array)asthelastargument.Itwillbecomeclearastowhyuponuse.
match(/\s+/g,"helloworld");//['']
Chapter4:Currying
Can'tliveiflivin'iswithoutyou
mostly-adequate-guide
21Chapter4:Currying
-
match(/\s+/g)("helloworld");//['']
varhasSpaces=match(/\s+/g);//function(x){returnx.match(/\s+/g)}
hasSpaces("helloworld");//['']
hasSpaces("spaceless");//null
filter(hasSpaces,["tori_spelling","toriamos"]);//["toriamos"]
varfindSpaces=filter(hasSpaces);//function(xs){returnxs.filter(function(x){returnx.match(/\s+/g)})}
findSpaces(["tori_spelling","toriamos"]);//["toriamos"]
varnoVowels=replace(/[aeiou]/ig);//function(replacement,x){returnx.replace(/[aeiou]/ig,replacement)}
varcensored=noVowels("*");//function(x){returnx.replace(/[aeiou]/ig,"*")}
censored("ChocolateRain");//'Ch*c*l*t*R**n'
What'sdemonstratedhereistheabilityto"pre-load"afunctionwithanargumentortwoinordertoreceiveanewfunctionthatremembersthosearguments.
Iencourageyoutonpminstalllodash,copythecodeaboveandhaveagoatitintherepl.Youcanalsodothisinabrowserwherelodashorramdaisavailable.
Curryingisusefulformanythings.WecanmakenewfunctionsjustbygivingourbasefunctionssomeargumentsasseeninhasSpaces,findSpaces,andcensored.
Wealsohavetheabilitytotransformanyfunctionthatworksonsingleelementsintoafunctionthatworksonarrayssimplybywrappingitwithmap:
vargetChildren=function(x){returnx.childNodes;};
varallTheChildren=map(getChildren);
Givingafunctionfewerargumentsthanitexpectsistypicallycalledpartialapplication.Partiallyapplyingafunctioncanremovealotofboilerplatecode.ConsiderwhattheaboveallTheChildrenfunctionwouldbewiththeuncurriedmapfromlodash(notetheargumentsareinadifferentorder):
varallTheChildren=function(elements){return_.map(elements,getChildren);};
Wetypicallydon'tdefinefunctionsthatworkonarrays,becausewecanjustcallmap(getChildren)inline.Samewithsort,filter,andotherhigherorderfunctions(Higherorderfunction:Afunctionthattakesorreturnsafunction).
Morethanapun/specialsauce
mostly-adequate-guide
22Chapter4:Currying
-
Whenwespokeaboutpurefunctions,wesaidtheytake1inputto1output.Curryingdoesexactlythis:eachsingleargumentreturnsanewfunctionexpectingtheremainingarguments.That,oldsport,is1inputto1output.
Nomatteriftheoutputisanotherfunction-itqualifiesaspure.Wedoallowmorethanoneargumentatatime,butthisisseenasmerelyremovingtheextra()'sforconvenience.
CurryingishandyandIverymuchenjoyworkingwithcurriedfunctionsonadailybasis.Itisatoolforthebeltthatmakesfunctionalprogramminglessverboseandtedious.
Wecanmakenew,usefulfunctionsontheflysimplybypassinginafewargumentsandasabonus,we'veretainedthemathematicalfunctiondefinitiondespitemultiplearguments.
Let'sacquireanotheressentialtoolcalledcompose.
Chapter5:CodingbyComposing
Aquickwordbeforewestart.We'llusealibrarycalledramdawhichcurrieseveryfunctionbydefault.Alternativelyyoumaychoosetouselodash-fpwhichdoesthesameandiswritten/maintainedbythecreatoroflodash.Bothwillworkjustfineanditisamatterofpreference.
ramdalodash-fp
Thereareunitteststorunagainstyourexercisesasyoucodethem,oryoucanjustcopy-pasteintoajavascriptREPLfortheearlyexercisesifyouwish.
Answersareprovidedwiththecodeintherepositoryforthisbook
var_=require('ramda');
//Exercise1//==============//Refactortoremoveallargumentsbypartiallyapplyingthefunction
varwords=function(str){return_.split('',str);};
//Exercise1a//==============//Usemaptomakeanewwordsfnthatworksonanarrayofstrings.
varsentences=undefined;
//Exercise2//==============//Refactortoremoveallargumentsbypartiallyapplyingthefunctions
varfilterQs=function(xs){return_.filter(function(x){returnmatch(/q/i,x);},xs);};
//Exercise3//==============//Usethehelperfunction_keepHighesttorefactormaxtonotreferenceany//arguments
Insummary
Exercises
mostly-adequate-guide
23Chapter4:Currying
http://ramdajs.comhttps://github.com/lodash/lodash-fphttps://github.com/DrBoolean/mostly-adequate-guide/tree/master/code/part1_exerciseshttps://github.com/DrBoolean/mostly-adequate-guide/tree/master/code/part1_exercises/answers
-
//LEAVEBE:var_keepHighest=function(x,y){returnx>=y?x:y;};
//REFACTORTHISONE:varmax=function(xs){return_.reduce(function(acc,x){return_keepHighest(acc,x);},-Infinity,xs);};
//Bonus1://============//wraparray'sslicetobefunctionalandcurried.////[1,2,3].slice(0,2)varslice=undefined;
//Bonus2://============//useslicetodefineafunction"take"thattakesnelementsfromthebeginningofthestring.Makeitcurried////Resultfor"Something"withn=4shouldbe"Some"vartake=undefined;
mostly-adequate-guide
24Chapter4:Currying
-
Here'scompose:
varcompose=function(f,g){returnfunction(x){returnf(g(x));};};
fandgarefunctionsandxisthevaluebeing"piped"throughthem.
Compositionfeelslikefunctionhusbandry.You,breederoffunctions,selecttwowithtraitsyou'dliketocombineandmashthemtogethertospawnabrandnewone.Usageisasfollows:
vartoUpperCase=function(x){returnx.toUpperCase();};varexclaim=function(x){returnx+'!';};varshout=compose(exclaim,toUpperCase);
shout("sendintheclowns");//=>"SENDINTHECLOWNS!"
Thecompositionoftwofunctionsreturnsanewfunction.Thismakesperfectsense:composingtwounitsofsometype(inthiscasefunction)shouldyieldanewunitofthatverytype.Youdon'tplugtwolegostogetherandgetalincolnlog.Thereisatheoryhere,someunderlyinglawthatwewilldiscoverinduetime.
Inourdefinitionofcompose,thegwillrunbeforethef,creatingarighttoleftflowofdata.Thisismuchmorereadablethannestingabunchoffunctioncalls.Withoutcompose,theabovewouldread:
varshout=function(x){returnexclaim(toUpperCase(x));};
Insteadofinsidetooutside,werunrighttoleft,whichIsupposeisastepintheleftdirection(boo).Let'slookatanexamplewheresequencematters:
varhead=function(x){returnx[0];};varreverse=reduce(function(acc,x){return[x].concat(acc);},[]);varlast=compose(head,reverse);
last(['jumpkick','roundhouse','uppercut']);//=>'uppercut'
reversewillturnthelistaroundwhileheadgrabstheinitialitem.Thisresultsinaneffective,albeitinefficient,lastfunction.Thesequenceoffunctionsinthecompositionshouldbeapparenthere.Wecoulddefinealefttorightversion,however,wemirrorthemathematicalversionmuchmorecloselyasitstands.That'sright,compositionisstraightfromthemathbooks.Infact,perhapsit'stimetolookatapropertythatholdsforanycomposition.
//associativity
Chapter5:CodingbyComposing
Functionalhusbandry
mostly-adequate-guide
25Chapter5:CodingbyComposing
-
varassociative=compose(f,compose(g,h))==compose(compose(f,g),h);//true
Compositionisassociative,meaningitdoesn'tmatterhowyougrouptwoofthem.So,shouldwechoosetouppercasethestring,wecanwrite:
compose(toUpperCase,compose(head,reverse));
//orcompose(compose(toUpperCase,head),reverse);
Sinceitdoesn'tmatterhowwegroupourcallstocompose,theresultwillbethesame.Thatallowsustowriteavariadiccomposeanduseitasfollows:
//previouslywe'dhavetowritetwocomposes,butsinceit'sassociative,wecangivecomposeasmanyfn'saswelikeandletitdecidehowtogroupthem.varlastUpper=compose(toUpperCase,head,reverse);
lastUpper(['jumpkick','roundhouse','uppercut']);//=>'UPPERCUT'
varloudLastUpper=compose(exclaim,toUpperCase,head,reverse)
loudLastUpper(['jumpkick','roundhouse','uppercut']);//=>'UPPERCUT!'
Applyingtheassociativepropertygivesusthisflexibilityandpeaceofmindthattheresultwillbeequivalent.Theslightlymorecomplicatedvariadicdefinitionisincludedwiththesupportlibrariesforthisbookandisthenormaldefinitionyou'llfindinlibrarieslikelodash,underscore,andramda.
Onepleasantbenefitofassociativityisthatanygroupoffunctionscanbeextractedandbundledtogetherintheirveryowncomposition.Let'splaywithrefactoringourpreviousexample:
varloudLastUpper=compose(exclaim,toUpperCase,head,reverse);
//orvarlast=compose(head,reverse);varloudLastUpper=compose(exclaim,toUpperCase,last);
//orvarlast=compose(head,reverse);varangry=compose(exclaim,toUpperCase);varloudLastUpper=compose(angry,last);
//morevariations...
There'snorightorwronganswers-we'rejustpluggingourlegostogetherinwhateverwayweplease.Usuallyit'sbesttogroupthingsinareusablewaylikelastandangry.IffamiliarwithFowler's"Refactoring",onemightrecognizethisprocessas"extractmethod"...exceptwithoutalltheobjectstatetoworryabout.
Pointfreestylemeansneverhavingtosayyourdata.Excuseme.Itmeansfunctionsthatnevermentionthedatauponwhichtheyoperate.Firstclassfunctions,currying,andcompositionallplaywelltogethertocreatethisstyle.
Pointfree
mostly-adequate-guide
26Chapter5:CodingbyComposing
https://lodash.com/http://underscorejs.org/http://ramdajs.com/http://martinfowler.com/books/refactoring.htmlhttp://refactoring.com/catalog/extractMethod.html
-
//notpointfreebecausewementionthedata:wordvarsnakeCase=function(word){returnword.toLowerCase().replace(/\s+/ig,'_');};
//pointfreevarsnakeCase=compose(replace(/\s+/ig,'_'),toLowerCase);
Seehowwepartiallyappliedreplace?Whatwe'redoingispipingourdatathrougheachfunctionof1argument.Curryingallowsustoprepareeachfunctiontojusttakeitsdata,operateonit,andpassitalong.Somethingelsetonoticeishowwedon'tneedthedatatoconstructourfunctioninthepointfreeversion,whereasinthepointfulone,wemusthaveourwordavailablebeforeanythingelse.
Let'slookatanotherexample.
//notpointfreebecausewementionthedata:namevarinitials=function(name){returnname.split('').map(compose(toUpperCase,head)).join('.');};
//pointfreevarinitials=compose(join('.'),map(compose(toUpperCase,head)),split(''));
initials("hunterstocktonthompson");//'H.S.T'
Pointfreecodecanagain,helpusremoveneedlessnamesandkeepusconciseandgeneric.Pointfreeisagoodlitmustestforfunctionalcodeasitlet'susknowwe'vegotsmallfunctionsthattakeinputtooutput.Onecan'tcomposeawhileloop,forinstance.Bewarned,however,pointfreeisadoubleedgeswordandcansometimesobfuscateintention.NotallfunctionalcodeispointfreeandthatisO.K.We'llshootforitwherewecanandstickwithnormalfunctionsotherwise.
Acommonmistakeistocomposesomethinglikemap,afunctionoftwoarguments,withoutfirstpartiallyapplyingit.
//wrong-weendupgivingangryanarrayandwepartiallyappliedmapwithgodknowswhat.varlatin=compose(map,angry,reverse);
latin(["frog","eyes"]);//error
//right-eachfunctionexpects1argument.varlatin=compose(map(angry),reverse);
latin(["frog","eyes"]);//["EYES!","FROG!"])
Ifyouarehavingtroubledebuggingacomposition,wecanusethishelpful,butimpuretracefunctiontoseewhat'sgoingon.
vartrace=curry(function(tag,x){console.log(tag,x);returnx;});
vardasherize=compose(join('-'),toLower,split(''),replace(/\s{2,}/ig,''));
dasherize('Theworldisavampire');
Debugging
mostly-adequate-guide
27Chapter5:CodingbyComposing
-
//TypeError:Cannotreadproperty'apply'ofundefined
Somethingiswronghere,let'strace
vardasherize=compose(join('-'),toLower,trace("aftersplit"),split(''),replace(/\s{2,}/ig,''));//aftersplit['The','world','is','a','vampire']
Ah!WeneedtomapthistoLowersinceit'sworkingonanarray.
vardasherize=compose(join('-'),map(toLower),split(''),replace(/\s{2,}/ig,''));
dasherize('Theworldisavampire');
//'the-world-is-a-vampire'
Thetracefunctionallowsustoviewthedataatacertainpointfordebuggingpurposes.Languageslikehaskellandpurescripthavesimilarfunctionsforeaseofdevelopment.
Compositionwillbeourtoolforconstructingprogramsand,asluckwouldhaveit,isbackedbyapowerfultheorythatensuresthingswillworkoutforus.Let'sexaminethistheory.
Categorytheoryisanabstractbranchofmathematicsthatcanformalizeconceptsfromseveraldifferentbranchessuchassettheory,typetheory,grouptheory,logic,andmore.Itprimarilydealswithobjects,morphisms,andtransformations,whichmirrorsprogrammingquiteclosely.Hereisachartofthesameconceptsasviewedfromeachseparatetheory.
Sorry,Ididn'tmeantofrightenyou.Idon'texpectyoutobeintimatelyfamiliarwithalltheseconcepts.Mypointistoshowyouhowmuchduplicationwehavesoyoucanseewhycategorytheoryaimstounifythesethings.
Incategorytheory,wehavesomethingcalled...acategory.Itisdefinedasacollectionwiththefollowingcomponents:
Categorytheory
mostly-adequate-guide
28Chapter5:CodingbyComposing
-
AcollectionofobjectsAcollectionofmorphismsAnotionofcompositiononthemorphismsAdistinguishedmorphismcalledidentity
Categorytheoryisabstractenoughtomodelmanythings,butlet'sapplythistotypesandfunctions,whichiswhatwecareaboutatthemoment.
AcollectionofobjectsTheobjectswillbedatatypes.Forinstance,String,Boolean,Number,Object,etc.Weoftenviewdatatypesassetsofallthepossiblevalues.OnecouldlookatBooleanasthesetof[true,false]andNumberasthesetofallpossiblenumericvalues.Treatingtypesassetsisusefulbecausewecanusesettheorytoworkwiththem.
AcollectionofmorphismsThemorphismswillbeourstandardeverydaypurefunctions.
AnotionofcompositiononthemorphismsThis,asyoumayhaveguessed,isourbrandnewtoy-compose.We'vediscussedthatourcomposefunctionisassociativewhichisnocoincidenceasitisapropertythatmustholdforanycompositionincategorytheory.
Hereisanimagedemonstratingcomposition:
Hereisaconcreteexampleincode:
varg=function(x){returnx.length;};varf=function(x){returnx===4;};varisFourLetterWord=compose(f,g);
AdistinguishedmorphismcalledidentityLet'sintroduceausefulfunctioncalledid.Thisfunctionsimplytakessomeinputandspitsitbackatyou.Takealook:
varid=function(x){returnx;};
Youmightaskyourself"Whatinthebloodyhellisthatusefulfor?".We'llmakeextensiveuseofthisfunctioninthefollowing
mostly-adequate-guide
29Chapter5:CodingbyComposing
-
chapters,butfornowthinkofitasafunctionthatcanstandinforourvalue-afunctionmasqueradingaseverydaydata.
idmustplaynicelywithcompose.Hereisapropertythatalwaysholdsforeveryunary(unary:aoneargumentfunction)functionf:
//identitycompose(id,f)==compose(f,id)==f;//true
Hey,it'sjustliketheidentitypropertyonnumbers!Ifthat'snotimmediatelyclear,takesometimewithit.Understandthefutility.We'llbeseeingidusedallovertheplacesoon,butfornowweseeit'safunctionthatactsasastandinforagivenvalue.Thisisquiteusefulwhenwritingpointfreecode.
Sothereyouhaveit,acategoryoftypesandfunctions.Ifthisisyourfirstintroduction,Iimagineyou'restillalittlefuzzyonwhatacategoryisandwhyit'suseful.Wewillbuilduponthisknowledgethroughoutthebook.Asofrightnow,inthischapter,onthisline,youcanatleastseeitasprovidinguswithsomewisdomregardingcomposition-namely,theassociativityandidentityproperties.
Whataresomeothercategories,youask?Well,wecandefineonefordirectedgraphswithnodesbeingobjects,edgesbeingmorphisms,andcompositionjustbeingpathconcatenation.WecandefinewithNumbersasobjectsand>=asmorphisms(actuallyanypartialortotalordercanbeacategory).Thereareheapsofcategories,butforthepurposesofthisbook,we'llonlyconcernourselveswiththeonedefinedabove.Wehavesufficientlyskimmedthesurfaceandmustmoveon.
Compositionconnectsourfunctionstogetherlikeaseriesofpipes.Datawillflowthroughourapplicationasitmust-purefunctionsareinputtooutputafterallsobreakingthischainwoulddisregardoutput,renderingoursoftwareuseless.
Weholdcompositionasadesignprincipleaboveallothers.Thisisbecauseitkeepsourappsimpleandreasonable.Categorytheorywillplayabigpartinapparchitecture,modellingsideeffects,andensuringcorrectness.
Wearenowatapointwhereitwouldserveuswelltoseesomeofthisinpractice.Let'smakeanexampleapplication.
Chapter6:ExampleApplication
var_=require('ramda');varaccounting=require('accounting');
//ExampleDatavarCARS=[{name:"FerrariFF",horsepower:660,dollar_value:700000,in_stock:true},{name:"SpykerC12Zagato",horsepower:650,dollar_value:648000,in_stock:false},{name:"JaguarXKR-S",horsepower:550,dollar_value:132000,in_stock:false},{name:"AudiR8",horsepower:525,dollar_value:114200,in_stock:false},{name:"AstonMartinOne-77",horsepower:750,dollar_value:1850000,in_stock:true},{name:"PaganiHuayra",horsepower:700,dollar_value:1300000,in_stock:false}];
//Exercise1://============//use_.compose()torewritethefunctionbelow.Hint:_.prop()iscurried.varisLastInStock=function(cars){varlast_car=_.last(cars);return_.prop('in_stock',last_car);
InSummary
Exercises
mostly-adequate-guide
30Chapter5:CodingbyComposing
-
};
//Exercise2://============//use_.compose(),_.prop()and_.head()toretrievethenameofthefirstcarvarnameOfFirstCar=undefined;
//Exercise3://============//Usethehelperfunction_averagetorefactoraverageDollarValueasacompositionvar_average=function(xs){return_.reduce(_.add,0,xs)/xs.length;};//
-
Wearegoingtoswitchourmindset.Fromhereonout,we'llstoptellingthecomputerhowtodoitsjobandinsteadwriteaspecificationofwhatwe'dlikeasaresult.I'msureyou'llfinditmuchlessstressfulthantryingtomicromanageeverythingallthetime.
Declarative,asopposedtoimperative,meansthatwewillwriteexpressions,asopposedtostepbystepinstructions.
ThinkofSQL.Thereisno"firstdothis,thendothat".Thereisoneexpressionthatspecifieswhat'dlikefromthedatabase.Wedon'tdecidehowtodothework,itdoes.WhenthedatabaseisupgradedandtheSQLengineoptimized,wedon'thavetochangeourquery.Thisisbecausetherearemanywaystointerpretourspecificationandachievethesameresult.
Forsomefolks,myselfincluded,it'shardtograsptheconceptofdeclarativecodingatfirstsolet'spointoutafewexamplestogetafeelforit.
//imperativevarmakes=[];for(i=0;i<cars.length;i++){makes.push(cars[i].make);}
//declarativevarmakes=cars.map(function(car){returncar.make;});
Theimperativeloopmustfirstinstantiatethearray.Theinterpretermustevaluatethisstatementbeforemovingon.Thenitdirectlyiteratesthroughthelistofcars,manuallyincreasingacounterandshowingitsbitsandpiecestousinavulgardisplayofexplicititeration.
Themapversionisoneexpression.Itdoesnotrequireanyorderofevaluation.Thereismuchfreedomhereforhowthemapfunctioniteratesandhowthereturnedarraymaybeassembled.Itspecifieswhat,nothow.Thus,itwearstheshinydeclarativesash.
Inadditiontobeingclearerandmoreconcise,themapfunctionmaybeoptimizedatwillandourpreciousapplicationcodeneedn'tchange.
Forthoseofyouwhoarethinking"Yes,butit'smuchfastertodotheimperativeloop",IsuggestyoueducateyourselfonhowtheJIToptimizesyourcode.Here'saterrificvideothatmayshedsomelight
Hereisanotherexample.
//imperativevarauthenticate=function(form){varuser=toUser(form);returnlogIn(user);};
//declarativevarauthenticate=compose(logIn,toUser);
Thoughthere'snothingnecessarilywrongwiththeimperativeversion,thereisstillanencodedstep-by-stepevaluationbakedin.Thecomposeexpressionsimplystatesafact:AuthenticationisthecompositionoftoUserandlogIn.Again,this
Chapter6:ExampleApplication
Declarativecoding
mostly-adequate-guide
32Chapter6:ExampleApplication
https://www.youtube.com/watch?v=65-RbBwZQdU
-
leaveswiggleroomforsupportcodechangesandresultsinourapplicationcodebeingahighlevelspecification.
Becausewearenotencodingorderofevaluation,declarativecodinglendsitselftoparallelcomputing.ThiscoupledwithpurefunctionsiswhyFPisagoodoptionfortheparallelfuture-wedon'treallyneedtodoanythingspecialtoachieveparallel/concurrentsystems.
Wewillnowbuildanexampleapplicationinadeclarative,composableway.We'llstillcheatandusesideeffectsfornow,butwe'llkeepthemminimalandseparatefromourpurecodebase.Wearegoingtobuildabrowserwidgetthatsucksinflickrimagesanddisplaysthem.Let'sstartbyscaffoldingtheapp.Here'sthehtml:
Andhere'stheflickr.jsskeleton:
requirejs.config({paths:{ramda:'https://cdnjs.cloudflare.com/ajax/libs/ramda/0.13.0/ramda.min',jquery:'https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min'}});
require(['ramda','jquery'],function(_,$){vartrace=_.curry(function(tag,x){console.log(tag,x);returnx;});//appgoeshere});
We'repullinginramdainsteadoflodashorsomeotherutilitylibrary.Itincludescompose,curry,andmore.I'veusedrequirejs,whichmayseemlikeoverkill,butwe'llbeusingitthroughoutthebookandconsistencyiskey.Also,I'vestartedusoffwithournicetracefunctionforeasydebugging.
Nowthatthat'soutoftheway,ontothespec.Ourappwilldo4things.
1. Constructaurlforourparticularsearchterm2. Maketheflickrapicall3. Transformtheresultingjsonintohtmlimages4. Placethemonthescreen
Thereare2impureactionsmentionedabove.Doyouseethem?Thosebitsaboutgettingdatafromtheflickrapiandplacingitonthescreen.Let'sdefinethosefirstsowecanquarantinethem.
varImpure={getJSON:_.curry(function(callback,url){$.getJSON(url,callback);
Aflickroffunctionalprogramming
mostly-adequate-guide
33Chapter6:ExampleApplication
http://ramdajs.com
-
}),
setHtml:_.curry(function(sel,html){$(sel).html(html);})};
Herewe'vesimplywrappedjQuery'smethodstobecurriedandwe'veswappedtheargumentstoamorefavorableposition.I'venamespacedthemwithImpuresoweknowthesearedangerousfunctions.Inafutureexample,wewillmakethesetwofunctionspure.
NextwemustconstructaurltopasstoourImpure.getJSONfunction.
varurl=function(term){return'https://api.flickr.com/services/feeds/photos_public.gne?tags='+term+'&format=json&jsoncallback=?';};
Therearefancyandoverlycomplexwaysofwritingurlpointfreeusingmonoids(we'lllearnabouttheselater)orcombinators.We'vechosentostickwithareadableversionandassemblethisstringinthenormalpointfulfashion.
Let'swriteanappfunctionthatmakesthecallandplacesthecontentsonthescreen.
varapp=_.compose(Impure.getJSON(trace("response")),url);
app("cats");
Thiscallsoururlfunction,thenpassesthestringtoourgetJSONfunction,whichhasbeenpartiallyappliedwithtrace.Loadingtheappwillshowtheresponsefromtheapicallintheconsole.
We'dliketoconstructimagesoutofthisjson.Itlookslikethesrcsareburiedinitemstheneachmedia'smproperty.
mostly-adequate-guide
34Chapter6:ExampleApplication
-
Anyhow,togetatthesenestedpropertieswecanuseaniceuniversalgetterfunctionfromramdacalled_.prop().Here'sahomegrownversionsoyoucanseewhat'shappening:
varprop=_.curry(function(property,object){returnobject[property];});
It'squitedullactually.Wejustuse[]syntaxtoaccessapropertyonwhateverobject.Let'susethistogetatoursrcs.
varmediaUrl=_.compose(_.prop('m'),_.prop('media'));
varsrcs=_.compose(_.map(mediaUrl),_.prop('items'));
Oncewegathertheitems,wemustmapoverthemtoextracteachmediaurl.Thisresultsinanicearrayofsrcs.Let'shookthisuptoourappandprintthemonthescreen.
varrenderImages=_.compose(Impure.setHtml("body"),srcs);varapp=_.compose(Impure.getJSON(renderImages),url);
Allwe'vedoneismakeanewcompositionthatwillcalloursrcsandsetthebodyhtmlwiththem.We'vereplacedthetracecallwithrenderImagesnowthatwehavesomethingtorenderbesidesrawjson.Thiswillcrudelydisplayoursrcsdirectlyinthebody.
Ourfinalstepistoturnthesesrcsintobonafideimages.Inabiggerapplication,we'duseatemplate/domlibrarylikeHandlebarsorReact.Forthisapplicationthough,weonlyneedanimgtagsolet'sstickwithjQuery.
varimg=function(url){return$('',{src:url});};
jQuery'shtml()methodwillacceptanarrayoftags.WeonlyhavetotransformoursrcsintoimagesandsendthemalongtosetHtml.
varimages=_.compose(_.map(img),srcs);varrenderImages=_.compose(Impure.setHtml("body"),images);varapp=_.compose(Impure.getJSON(renderImages),url);
Andwe'redone!
mostly-adequate-guide
35Chapter6:ExampleApplication
-
Hereisthefinishedscript:
requirejs.config({paths:{ramda:'https://cdnjs.cloudflare.com/ajax/libs/ramda/0.13.0/ramda.min',jquery:'https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min'}});
require(['ramda','jquery'],function(_,$){//////////////////////////////////////////////Utils
varImpure={getJSON:_.curry(function(callback,url){$.getJSON(url,callback);}),
setHtml:_.curry(function(sel,html){$(sel).html(html);})};
varimg=function(url){return$('',{src:url});};
vartrace=_.curry(function(tag,x){console.log(tag,x);returnx;});
////////////////////////////////////////////
varurl=function(t){return'http://api.flickr.com/services/feeds/photos_public.gne?tags='+t+'&format=json&jsoncallback=?';};
mostly-adequate-guide
36Chapter6:ExampleApplication
-
varmediaUrl=_.compose(_.prop('m'),_.prop('media'));
varsrcs=_.compose(_.map(mediaUrl),_.prop('items'));
varimages=_.compose(_.map(img),srcs);
varrenderImages=_.compose(Impure.setHtml("body"),images);
varapp=_.compose(Impure.getJSON(renderImages),url);
app("cats");});
Nowlookatthat.Abeautifullydeclarativespecificationofwhatthingsare,nothowtheycometobe.Wenowvieweachlineasanequationwithpropertiesthathold.Wecanusethesepropertiestoreasonaboutourapplicationandrefactor.
Thereisanoptimizationavailable-wemapovereachitemtoturnitintoamediaurl,thenwemapagainoverthosesrcstoturnthemintoimgtags.Thereisalawregardingmapandcomposition:
//map'scompositionlawvarlaw=compose(map(f),map(g))==map(compose(f,g));
Wecanusethispropertytooptimizeourcode.Let'shaveaprincipledrefactor.
//originalcodevarmediaUrl=_.compose(_.prop('m'),_.prop('media'));
varsrcs=_.compose(_.map(mediaUrl),_.prop('items'));
varimages=_.compose(_.map(img),srcs);
Let'slineupourmaps.Wecaninlinethecalltosrcsinimagesthankstoequationalreasoningandpurity.
varmediaUrl=_.compose(_.prop('m'),_.prop('media'));
varimages=_.compose(_.map(img),_.map(mediaUrl),_.prop('items'));
Nowthatwe'velinedupourmap'swecanapplythecompositionlaw.
varmediaUrl=_.compose(_.prop('m'),_.prop('media'));
varimages=_.compose(_.map(_.compose(img,mediaUrl)),_.prop('items'));
Nowthebuggerwillonlylooponcewhileturningeachitemintoanimg.Let'sjustmakeitalittlemorereadablebyextractingthefunctionout.
varmediaUrl=_.compose(_.prop('m'),_.prop('media'));
varmediaToImg=_.compose(img,mediaUrl);
varimages=_.compose(_.map(mediaToImg),_.prop('items'));
APrincipledRefactor
mostly-adequate-guide
37Chapter6:ExampleApplication
-
Wehaveseenhowtoputournewskillsintousewithasmall,butrealworldapp.We'veusedourmathematicalframeworktoreasonaboutandrefactorourcode.Butwhatabouterrorhandlingandcodebranching?Howcanwemakethewholeapplicationpureinsteadofmerelynamespacingdestructivefunctions?Howcanwemakeourappsaferandmoreexpressive?Thesearethequestionswewilltackleinpart2.
Chapter7:Hindley-MilnerandMe
InSummary
mostly-adequate-guide
38Chapter6:ExampleApplication
-
Ifyou'renewtothefunctionalworld,itwon'tbelongbeforeyoufindyourselfkneedeepintypesignatures.Typesarethemetalanguagethatenablespeoplefromalldifferentbackgroundstocommunicatesuccinctlyandeffectively.Forthemostpart,they'rewrittenwithasystemcalled"Hindley-Milner",whichwe'llbeexaminingtogetherinthischapter.
Whenworkingwithpurefunctions,typesignatureshaveanexpressivepowertowhichtheEnglishlanguagecannotholdacandle.Thesesignatureswhisperinyoureartheintimatesecretsofafunction.Inasingle,compactline,theyexposebehaviourandintention.Wecanderive"freetheorems"fromthem.Typescanbeinferredsothere'snoneedforexplicittypeannotations.Theycanbetunedtofinepointprecisionorleftgeneralandabstract.Theyarenotonlyusefulforcompiletimechecks,butalsoturnouttobethebestpossibledocumentationavailable.Typesignaturesthusplayanimportantpartinfunctionalprogramming-muchmorethanyoumightfirstexpect.
JavaScriptisadynamiclanguage,butthatdoesnotmeanweavoidtypesalltogether.We'restillworkingwithstrings,numbers,booleans,andsoon.It'sjustthatthereisn'tanylanguagelevelintegrationsoweholdthisinformationinourheads.Nottoworry,sincewe'reusingsignaturesfordocumentation,wecanusecommentstoserveourpurpose.
TherearetypecheckingtoolsavailableforJavaScriptsuchasFloworthetypeddialect,TypeScript.Theaimofthisbookistoequiponewiththetoolstowritefunctionalcodesowe'llstickwiththestandardtypesystemusedacrossFPlanguages.
Fromthedustypagesofmathbooks,acrossthevastseaofwhitepapers,amongstcasualsaturdaymorningblogposts,downintothesourcecodeitself,wefindHindley-Milnertypesignatures.Thesystemisquitesimple,butwarrantsaquickexplanationandsomepracticetofullyabsorbthelittlelanguage.
//capitalize::String->Stringvarcapitalize=function(s){returntoUpperCase(head(s))+toLowerCase(tail(s));}
capitalize("smurf");//=>"Smurf"
Here,capitalizetakesaStringandreturnsaString.Nevermindtheimplementation,it'sthetypesignaturewe'reinterestedin.
InHM,functionsarewrittenasa->bwhereaandbarevariablesforanytype.Sothesignaturesforcapitalizecanbereadas"afunctionfromStringtoString".Inotherwords,ittakesaStringasitsinputandreturnsaStringasitsoutput.
Let'slookatsomemorefunctionsignatures:
//strLength::String->NumbervarstrLength=function(s){returns.length;}
//join::String->[String]->Stringvarjoin=curry(function(what,xs){returnxs.join(what);
Hindley-MilnerandMe
What'syourtype?
Talesfromthecryptic
mostly-adequate-guide
39Chapter7:Hindley-MilnerandMe
http://flowtype.org/http://www.typescriptlang.org/
-
});
//match::Regex->String->[String]varmatch=curry(function(reg,s){returns.match(reg);});
//replace::Regex->String->String->Stringvarreplace=curry(function(reg,sub,s){returns.replace(reg,sub);});
strLengthisthesameideaasbefore:wetakeaStringandreturnyouaNumber.
Theothersmightperplexyouatfirstglance.Withoutfullyunderstandingthedetails,youcouldalwaysjustviewthelasttypeasthereturnvalue.Soformatchyoucaninterpretas:IttakesaRegexandaStringandreturnsyou[String].ButaninterestingthingisgoingonherethatI'dliketotakeamomenttoexplainifImay.
Formatchwearefreetogroupthesignaturelikeso:
//match::Regex->(String->[String])varmatch=curry(function(reg,s){returns.match(reg);});
Ahyes,groupingthelastpartinparenthesisrevealsmoreinformation.NowitisseenasafunctionthattakesaRegexandreturnsusafunctionfromStringto[String].Becauseofcurrying,thisisindeedthecase:giveitaRegexandwegetafunctionbackwaitingforitsStringargument.Ofcourse,wedon'thavetothinkofitthisway,butitisgoodtounderstandwhythelasttypeistheonereturned.
//match::Regex->(String->[String])
//onHoliday::String->[String]varonHoliday=match(/holiday/ig);
Eachargumentpopsonetypeoffthefrontofthesignature.onHolidayismatchthatalreadyhasaRegex.
//replace::Regex->(String->(String->String))varreplace=curry(function(reg,sub,s){returns.replace(reg,sub);});
Asyoucanseewiththefullparenthesisonreplace,theextranotationcangetalittlenoisyandredundantsowesimplyomitthem.Wecangivealltheargumentsatonceifwechoosesoit'seasiertojustthinkofitas:replacetakesaRegex,aString,anotherStringandreturnsyouaString.
Afewlastthingshere:
//id::a->avarid=function(x){returnx;}
//map::(a->b)->[a]->[b]varmap=curry(function(f,xs){returnxs.map(f);});
Theidfunctiontakesanyoldtypeaandreturnssomethingofthesametypea.We'reabletousevariablesintypesjust
mostly-adequate-guide
40Chapter7:Hindley-MilnerandMe
-
likeincode.Variablenameslikeaandbareconvention,buttheyarearbitraryandcanbereplacedwithwhatevernameyou'dlike.Iftheyarethesamevariable,theyhavetobethesametype.That'sanimportantrulesolet'sreiterate:a->bcanbeanytypeatoanytypeb,buta->ameansithastobethesametype.Forexample,idmaybeString->StringorNumber->Number,butnotString->Bool.
mapsimilarlyusestypevariables,butthistimeweintroducebwhichmayormaynotbethesametypeasa.Wecanreaditas:maptakesafunctionfromanytypeatothesameordifferenttypeb,thentakesanarrayofa'sandresultsinanarrayofb's.
Hopefully,you'vebeenovercomebytheexpressivebeautyinthistypesignature.Itliterallytellsuswhatthefunctiondoesalmostwordforword.It'sgivenafunctionfromatob,anarrayofa,anditdeliversusanarrayofb.Theonlysensiblethingforittodoiscallthebloodyfunctiononeacha.Anythingelsewouldbeaboldfacelie.
Beingabletoreasonabouttypesandtheirimplicationsisaskillthatwilltakeyoufarinthefunctionalworld.Notonlywillpapers,blogs,docs,etc,becomemoredigestible,butthesignatureitselfwillpracticallylectureyouonitsfunctionality.Ittakespracticetobecomeafluentreader,butifyoustickwithit,heapsofinformationwillbecomeavailabletoyousansRTFMing.
Here'safewmorejusttoseeifyoucandecipherthemonyourown.
//head::[a]->avarhead=function(xs){returnxs[0];}
//filter::(a->Bool)->[a]->[a]varfilter=curry(function(f,xs){returnxs.filter(f);});
//reduce::(b->a->b)->b->[a]->bvarreduce=curry(function(f,x,xs){returnxs.reduce(f,x);});
reduceisperhaps,themostexpressiveofall.It'satrickyone,however,sodon'tfeelinadequateshouldyoustrugglewithit.Forthecurious,I'lltrytoexplaininEnglishthoughworkingthroughthesignatureonyourownismuchmoreinstructive.
Ahem,heregoesnothing....lookingatthesignature,weseethefirstargumentisafunctionthatexpectsab,ana,andproducesab.Wheremightitgettheseasandbs?Well,thefollowingargumentsinthesignatureareabandanarrayofassowecanonlyassumethatthebandeachofthoseaswillbefedin.Wealsoseethattheresultofthefunctionisabsothethinkinghereisourfinalincantationofthepassedinfunctionwillbeouroutputvalue.Knowingwhatreducedoes,wecanstatethattheaboveinvestigationisaccurate.
Onceatypevariableisintroduced,thereemergesacuriouspropertycalledparametricity(http://en.wikipedia.org/wiki/Parametricity).Thispropertystatesthatafunctionwillactonalltypesinauniformmanner.Let'sinvestigate:
//head::[a]->a
Lookingathead,weseethatittakes[a]toa.Besidestheconcretetypearray,ithasnootherinformationavailableand,therefore,itsfunctionalityislimitedtoworkingonthearrayalone.Whatcoulditpossiblydowiththevariableaifitknowsnothingaboutit?Inotherwords,asaysitcannotbeaspecifictype,whichmeansitcanbeanytype,whichleavesuswithafunctionthatmustworkuniformlyforeveryconceivabletype.Thisiswhatparametricityisallabout.Guessingat
Narrowingthepossibility
mostly-adequate-guide
41Chapter7:Hindley-MilnerandMe
http://en.wikipedia.org/wiki/Parametricity
-
theimplementation,theonlyreasonableassumptionsarethatittakesthefirst,last,orarandomelementfromthatarray.Thenameheadshouldtipusoff.
Here'sanotherone:
//reverse::[a]->[a]
Fromthetypesignaturealone,whatcouldreversepossiblybeupto?Again,itcannotdoanythingspecifictoa.Itcannotchangeatoadifferenttypeorwe'dintroduceab.Canitsort?Well,no,itwouldn'thaveenoughinformationtosorteverypossibletype.Canitre-arrange?Yes,Isupposeitcandothat,butithastodosoinexactlythesamepredictableway.Anotherpossibilityisthatitmaydecidetoremoveorduplicateanelement.Inanycase,thepointis,thepossiblebehaviourismassivelynarrowedbyitspolymorphictype.
ThisnarrowingofpossibilityallowsustousetypesignaturesearchengineslikeHoogletofindafunctionwe'reafter.Theinformationpackedtightlyintoasignatureisquitepowerfulindeed.
Besidesdeducingimplementationpossibilities,thissortofreasoninggainsusfreetheorems.WhatfollowsareafewrandomexampletheoremslifteddirectlyfromWadler'spaperonthesubject.
//head::[a]->acompose(f,head)==compose(head,map(f));
//filter::(a->Bool)->[a]->[a]compose(map(f),filter(compose(p,f)))==compose(filter(p),map(f));
Youdon'tneedanycodetogetthesetheorems,theyfollowdirectlyfromthetypes.Thefirstonesaysthatifwegettheheadofourarray,thenrunsomefunctionfonit,thatisequivalentto,andincidentally,muchfasterthan,ifwefirstmap(f)overeveryelementthentaketheheadoftheresult.
Youmightthink,wellthat'sjustcommonsense.ButlastIchecked,computersdon'thavecommonsense.Indeed,theymusthaveaformalwaytoautomatethesekindofcodeoptimizations.Mathshasawayofformalizingtheintuitive,whichishelpfulamidsttherigidterrainofcomputerlogic.
Thefiltertheoremissimilar.Itsaysthatifwecomposefandptocheckwhichshouldbefiltered,thenactuallyapplythefviamap(rememberfilter,willnottransformtheelements-itssignatureenforcesthatawillnotbetouched),itwillalwaysbeequivalenttomappingourfthenfilteringtheresultwiththeppredicate.
Thesearejusttwoexamples,butyoucanapplythisreasoningtoanypolymorphictypesignatureanditwillalwayshold.InJavaScript,therearesometoolsavailabletodeclarerewriterules.Onemightalsodothisviathecomposefunctionitself.Thefruitislowhangingandthepossibilitiesareendless.
Onelastthingtonoteisthatwecanconstraintypestoaninterface.
//sort::Orda=>[a]->[a]
Whatweseeontheleftsideofourfatarrowhereisthestatementofafact:amustbeanOrd.Orinotherwords,amust
Freeasintheorem
Constraints
mostly-adequate-guide
42Chapter7:Hindley-MilnerandMe
https://www.haskell.org/hooglehttp://ttic.uchicago.edu/~dreyer/course/papers/wadler.pdf
-
implementtheOrdinterface.WhatisOrdandwherediditcomefrom?Inatypedlanguageitwouldbeadefinedinterfacethatsayswecanorderthevalues.Thisnotonlytellsusmoreabouttheaandwhatoursortfunctionisupto,butalsorestrictsthedomain.Wecalltheseinterfacedeclarationstypeconstraints.
//assertEqual::(Eqa,Showa)=>a->a->Assertion
Here,wehavetwoconstraints:EqandShow.Thosewillensurethatwecancheckequalityofourasandprintthedifferenceiftheyarenotequal.
We'llseemoreexamplesofconstraintsandtheideashouldtakemoreshapeinlaterchapters.
Hindley-Milnertypesignaturesareubiquitousinthefunctionalworld.Thoughtheyaresimpletoreadandwrite,ittakestimetomasterthetechniqueofunderstandingprogramsthroughsignaturesalone.Wewilladdtypesignaturestoeachlineofcodefromhereonout.
Chapter8:Tupperware
InSummary
mostly-adequate-guide
43Chapter7:Hindley-MilnerandMe
-
We'veseenhowtowriteprogramswhichpipedatathroughaseriesofpurefunctions.Theyaredeclarativespecificationsofbehaviour.Butwhataboutcontrolflow,errorhandling,asynchronousactions,stateand,dareIsay,effects?!Inthischapter,wewilldiscoverthefoundationuponwhichallofthesehelpfulabstractionsarebuilt.
Firstwewillcreateacontainer.Thiscontainermustholdanytypeofvalue;aziplockthatholdsonlytapiocapuddingisrarelyuseful.Itwillbeanobject,butwewillnotgiveitpropertiesandmethodsintheOOsense.No,wewilltreatitlikeatreasurechest-aspecialboxthatcradlesourvaluabledata.
varContainer=function(x){this.__value=x;}
Container.of=function(x){returnnewContainer(x);};
Hereisourfirstcontainer.We'vethoughtfullynameditContainer.WewilluseContainer.ofasaconstructorwhichsavesusfromhavingtowritethatgodawfulnewkeywordallovertheplace.There'smoretotheoffunctionthanmeetstheeye,butfornow,thinkofitastheproperwaytoplacevaluesintoourcontainer.
Let'sexamineourbrandnewbox...
Tupperware
TheMightyContainer
mostly-adequate-guide
44Chapter8:Tupperware
-
Container.of(3)//=>Container(3)
Container.of("hotdogs")//=>Container("hotdogs")
Container.of(Container.of({name:"yoda"}))//=>Container(Container({name:"yoda"}))
Ifyouareusingnode,youwillsee{__value:x}eventhoughwe'vegotourselvesaContainer(x).Chromewilloutputthetypeproperly,butnomatter;aslongasweunderstandwhataContainerlookslike,we'llbefine.Insomeenvironmentsyoucanoverwritetheinspectmethodifyou'dlike,butwewillnotbesothorough.Forthisbook,wewillwritetheconceptualoutputasifwe'doverwritteninspectasit'smuchmoreinstructivethan{__value:x}forpedagogicalaswellasaestheticreasons.
Let'smakeafewthingsclearbeforewemoveon:
Containerisanobjectwithoneproperty.Lotsofcontainersjustholdonething,thoughtheyaren'tlimitedtoone.We'vearbitrarilynameditsproperty__value.
The__valuecannotbeonespecifictypeorourContainerwouldhardlyliveuptothename.
OncedatagoesintotheContaineritstaysthere.Wecouldgetitoutbyusing.__value,butthatwoulddefeatthepurpose.
Thereasonswe'redoingthiswillbecomeclearasamasonjar,butfornow,bearwithme.
Onceourvalue,whateveritmaybe,isinthecontainer,we'llneedawaytorunfunctionsonit.
//(a->b)->Containera->ContainerbContainer.prototype.map=function(f){returnContainer.of(f(this.__value))}
Why,it'sjustlikeArray'sfamousmap,exceptwehaveContainerainsteadof[a].Anditworksessentiallythesameway:
Container.of(2).map(function(two){returntwo+2})//=>Container(4)
Container.of("flamethrowers").map(function(s){returns.toUpperCase()})//=>Container("FLAMETHROWERS")
Container.of("bombs").map(_.concat('away')).map(_.prop('length'))//=>Container(10)
WecanworkwithourvaluewithouteverhavingtoleavetheContainer.Thisisaremarkablething.OurvalueintheContainerishandedtothemapfunctionsowecanfusswithitandafterward,returnedtoitsContainerforsafekeeping.AsaresultofneverleavingtheContainer,wecancontinuetomapaway,runningfunctionsasweplease.Wecanevenchangethetypeaswegoalongasdemonstratedinthelatterofthethreeexamples.
MyFirstFunctor
mostly-adequate-guide
45Chapter8:Tupperware
-
Waitaminute,ifwekeepcallingmap,itappearstobesomesortofcomposition!Whatmathematicalmagicisatworkhere?Wellchaps,we'vejustdiscoveredFunctors.
AFunctorisatypethatimplementsmapandobeyssomelaws
Yes,Functorissimplyaninterfacewithacontract.WecouldhavejustaseasilynameditMappable,butnow,where'sthefuninthat?Functorscomefromcategorytheoryandwe'lllookatthemathsindetailtowardtheendofthechapter,butfornow,let'sworkonintuitionandpracticalusesforthisbizarrelynamedinterface.
Whatreasoncouldwepossiblyhaveforbottlingupavalueandusingmaptogetatit?Theanswerrevealsitselfifwechooseabetterquestion:Whatdowegainfromaskingourcontainertoapplyfunctionsforus?Well,abstractionoffunctionapplication.Whenwemapafunction,weaskthecontainertypetorunitforus.Thisisaverypowerfulconcept,indeed.
Containerisfairlyboring.Infact,itisusuallycalledIdentityandhasaboutthesameimpactasouridfunction(againthereisamathematicalconnectionwe'lllookatwhenthetimeisright).However,thereareotherfunctors,thatis,container-liketypesthathaveapropermapfunction,whichcanprovideusefulbehaviourwhilstmapping.Let'sdefineonenow.
varMaybe=function(x){this.__value=x;}
Maybe.of=function(x){returnnewMaybe(x);}
Maybe.prototype.isNothing=function(){return(this.__value===null||this.__value===undefined);}
Maybe.prototype.map=function(f){returnthis.isNothing()?Maybe.of(null):Maybe.of(f(this.__value));}
Now,MaybelooksalotlikeContainerwithoneminorchange:itwillfirstchecktoseeifithasavaluebeforecallingthesuppliedfunction.Thishastheeffectofsidesteppingthosepeskynullsaswemap(Notethatthisimplementationissimpliedforteaching).
Maybe.of("MalkovichMalkovich").map(match(/a/ig));//=>Maybe(['a','a'])
Maybe.of(null).map(match(/a/ig));//=>Maybe(null)
Maybe.of({name:"Boris"}).map(_.prop("age")).map(add(10));//=>Maybe(null)
Schrödinger'sMaybe
mostly-adequate-guide
46Chapter8:Tupperware
-
Maybe.of({name:"Dinah",age:14}).map(_.prop("age")).map(add(10));//=>Maybe(24)
Noticeourappdoesn'texplodewitherrorsaswemapfunctionsoverournullvalues.ThisisbecauseMaybewilltakecaretocheckforavalueeachandeverytimeitappliesafunction.
Thisdotsyntaxisperfectlyfineandfunctional,butforreasonsmentionedinPart1,we'dliketomaintainourpointfreestyle.Asithappens,mapisfullyequippedtodelegatetowhateverfunctoritreceives:
//map::Functorf=>(a->b)->fa->fbvarmap=curry(function(f,any_functor_at_all){returnany_functor_at_all.map(f);});
Thisisdelightfulaswecancarryonwithcompositionperusualandmapwillworkasexpected.Thisisthecasewithramda'smapaswell.We'llusedotnotationwhenit'sinstructiveandthepointfreeversionwhenit'sconvenient.Didyounoticethat?I'vesneakilyintroducedextranotationintoourtypesignature.TheFunctorf=>tellsusthatfmustbeaFunctor.Notthatdifficult,butIfeltIshouldmentionit.
Inthewild,we'lltypicallyseeMaybeusedinfunctionswhichmightfailtoreturnaresult.
//safeHead::[a]->Maybe(a)varsafeHead=function(xs){returnMaybe.of(xs[0]);};
varstreetName=compose(map(_.prop('street')),safeHead,_.prop('addresses'));
streetName({addresses:[]});//Maybe(null)
streetName({addresses:[{street:"ShadyLn.",number:4201}]});//Maybe("ShadyLn.")
safeHeadislikeournormal_.head,butwithaddedtypesafety.AcuriousthinghappenswhenMaybeisintroducedintoourcode;weareforcedtodealwiththosesneakynullvalues.ThesafeHeadfunctionishonestandupfrontaboutitspossiblefailure-there'sreallynothingtobeashamedof-andsoitreturnsaMaybetoinformusofthismatter.Wearemorethanmerelyinformed,however,becauseweareforcedtomaptogetatthevaluewewantsinceitistuckedawayinsidetheMaybeobject.Essentially,thisisanullcheckenforcedbythesafeHeadfunctionitself.Wecannowsleepbetteratnightknowinganullvaluewon'trearitsugly,decapitatedheadwhenweleastexpectit.Apislikethiswillupgradeaflimsyapplicationfrompaperandtackstowoodandnails.Theywillguaranteesafersoftware.
SometimesafunctionmightreturnaMaybe(null)explicitlytosignalfailure.Forinstance:
//withdraw::Number->Account->Maybe(Account)varwithdraw=curry(function(amount,account){returnaccount.balance>=amount?Maybe.of({balance:account.balance-amount}):Maybe.of(null);});
//finishTransaction::Account->StringvarfinishTransaction=compose(remainingBalance,updateLedger);//Maybe(String)
Usecases
mostly-adequate-guide
47Chapter8:Tupperware
-
vargetTwenty=compose(map(finishTransaction),withdraw(20));
getTwenty({balance:200.00});//Maybe("Yourbalanceis$180.00")
getTwenty({balance:10.00});//Maybe(null)
withdrawwilltipitsnoseatusandreturnMaybe(null)ifwe'reshortoncash.Thisfunctionalsocommunicatesitsficklenessandleavesusnochoice,buttomapeverythingafterwards.Thedifferenceisthatthenullwasintentionalhere.InsteadofaMaybe(String),wegettheMaybe(null)backtosignalfailureandourapplicationeffectivelyhaltsinitstracks.Thisisimportanttonote:ifthewithdrawfails,thenmapwillsevertherestofourcomputationsinceitdoesn'teverrunthemappedfunctions,namelyfinishTransaction.Thisispreciselytheintendedbehaviouraswe'dprefernottoupdateourledgerorshowanewbalanceifwehadn'tsuccessfullywithdrawnfunds.
Onethingpeopleoftenmissisthattherewillalwaysbeanendoftheline;someeffectingfunctionthatsendsJSONalong,orprintstothescreen,oraltersourfilesystem,orwhathaveyou.Wecannotdelivertheoutputwithreturn,wemustrunsomefunctionoranothertosenditoutintotheworld.WecanphraseitlikeaZenBuddhistkoan:"Ifaprogramhasnoobservableeffect,doesitevenrun?".Doesitruncorrectlyforitsownsatisfaction?Isuspectitmerelyburnssomecyclesandgoesbacktosleep...
Ourapplication'sjobistoretrieve,transform,andcarrythatdataalonguntilit'stimetosaygoodbyeandthefunctionwhichdoessomaybemapped,thusthevalueneedn'tleavethewarmwombofitscontainer.Indeed,acommonerroristotrytoremovethevaluefromourMaybeonewayoranotherasifthepossiblevalueinsidewillsuddenlymaterializeandallwillbeforgiven.Wemustunderstanditmaybeabranchofcodewhereourvalueisnotaroundtoliveuptoitsdestiny.Ourcode,muchlikeSchrödinger'scat,isintwostatesatonceandshouldmaintainthatfactuntilthefinalfunction.Thisgivesourcodealinearflowdespitethelogicalbranching.
Thereis,however,anescapehatch.Ifwewouldratherreturnacustomvalueandcontinueon,wecanusealittlehelpercalledmaybe.
//maybe::b->(a->b)->Maybea->bvarmaybe=curry(function(x,f,m){returnm.isNothing()?x:f(m.__value);});
//getTwenty::Account->StringvargetTwenty=compose(maybe("You'rebroke!",finishTransaction),withdraw(20));
getTwenty({balance:200.00});//"Yourbalanceis$180.00"
getTwenty({balance:10.00});//"You'rebroke!"
Wewillnoweitherreturnastaticvalue(ofthesametypethatfinishTransactionreturns)orcontinueonmerrilyfinishingupthetransactionsansMaybe.Withmaybe,wearewitnessingtheequivalentofanif/elsestatementwhereaswithmap,theimperativeanalogwouldbe:if(x!==null){returnf(x)}.
TheintroductionofMaybecancausesomeinitialdiscomfort.UsersofSwiftandScalawillknowwhatImeanasit'sbakedrightintothecorelibrariesundertheguiseofOption(al).Whenpushedtodealwithnullchecksallthetime(andthere
Releasingthevalue
mostly-adequate-guide
48Chapter8:Tupperware
-
aretimesweknowwithabsolutecertaintythevalueexists),mostpeoplecan'thelp,butfeelit'satadlaborious.However,withtime,itwillbecomesecondnatureandyou'lllikelyappreciatethesafety.Afterall,mostofthetimeitwillpreventcutcornersandsaveourhides.
Writingunsafesoftwareisliketakingcaretopainteacheggwithpastelsbeforehurlingitintotraffic;likebuildingaretirementhomewithmaterialswarnedagainstbythreelittlepigs.ItwilldouswelltoputsomesafetyintoourfunctionsandMaybehelpsusdojustthat.
I'dberemissifIdidn'tmentionthatthe"real"implementationwillsplitMaybeintotwotypes:oneforsomethingandtheotherfornothing.Thisallowsustoobeyparametricityinmapsovalueslikenullandundefinedcanstillbemappedoverandtheuniversalqualificationofthevalueinafunctorwillberespected.You'lloftenseetypeslikeSome(x)/NoneorJust(x)/NothinginsteadofaMaybethatdoesanullcheckonitsvalue.
Itmaycomeasashock,butthrow/catchisnotverypure.Whenanerroristhrown,insteadofreturninganoutputvalue,wesoundthealarms!Thefunctionattacks,spewingthousandsof0'sand1'slikeshields&spearsinanelectricbattleagainstourintrudinginput.WithournewfriendEither,wecandobetterthantodeclarewaroninput,wecanrespondwithapolitemessage.Let'stakealook:
varLeft=function(x){this.__value=x;}
Left.of=function(x){returnnewLeft(x);}
Left.prototype.map=function(f){returnthis;}
varRight=function(x){this.__value=x;}
Right.of=function(x){returnnewRight(x);}
Right.prototype.map=function(f){returnRight.of(f(this.__value));}
LeftandRightaretwosubclassesofanabstracttypewecallEither.I'veskippedtheceremonyofcreatingtheEithersuperclassaswewon'teveruseit,butit'sgoodtobeaware.Nowthen,there'snothingnewherebesidesthetwotypes.Let'sseehowtheyact:
PureErrorHandling
mostly-adequate-guide
49Chapter8:Tupperware
-
Right.of("rain").map(function(str){return"b"+str;});//Right("brain")
Left.of("rain").map(function(str){return"b"+str;});//Left("rain")
Right.of({host:'localhost',port:80}).map(_.prop('host'));//Right('localhost')
Left.of("rollseyes...").map(_.prop("host"));//Left('rollseyes...')
Leftistheteenagerysortandignoresourrequesttomapoverit.RightwillworkjustlikeContainer(a.k.aIdentity).ThepowercomesfromtheabilitytoembedanerrormessagewithintheLeft.
Supposewehaveafunctionthatmightnotsucceed.Howaboutwecalculateanagefromabirthdate.WecoulduseMaybe(null)tosignalfailureandbranchourprogram,however,thatdoesn'ttellusmuch.Perhaps,we'dliketoknowwhyitfailed.Let'swritethisusingEither.
varmoment=require('moment');
//getAge::Date->User->Either(String,Number)vargetAge=curry(function(now,user){varbirthdate=moment(user.birthdate,'YYYY-MM-DD');if(!birthdate.isValid())returnLeft.of("Birthdatecouldnotbeparsed");returnRight.of(now.diff(birthdate,'years'));});
getAge(moment(),{birthdate:'2005-12-12'});//Right(9)
getAge(moment(),{birthdate:'20010704'});//Left("Birthdatecouldnotbeparsed")
Now,justlikeMaybe(null),weareshortcircuitingourappwhenwereturnaLeft.Thedifference,isnowwehaveaclueastowhyourprogramhasderailed.SomethingtonoticeisthatwereturnEither(String,Number),whichholdsaStringasitsleftvalueandaNumberasitsRight.Thistypesignatureisabitinformalaswehaven'ttakenthetimetodefineanactualEithersuperclass,however,welearnalotfromthetype.Itinformsusthatwe'reeithergettinganerrormessageortheageback.
//fortune::Number->Stringvarfortune=compose(concat("Ifyousurvive,youwillbe"),add(1));
//zoltar::User->Either(String,_)varzoltar=compose(map(console.log),map(fortune),getAge(moment()));
zoltar({birthdate:'2005-12-12'});//"Ifyousurvive,youwillbe10"//Right(undefined)
zoltar({birthdate:'balloons!'});//Left("Birthdatecouldnotbeparsed")
Whenthebirthdateisvalid,theprogramoutputsitsmysticalfortunetothescreenforustobehold.Otherwise,wearehandedaLeftwiththeerrormessageplainasdaythoughstilltuckedawayinitscontainer.Thatactsjustasifwe'dthrownanerror,butinacalm,mildmannerfashionasopposedtolosingitstemperandscreaminglikeachildwhensomethinggoeswrong.
Inthisexample,wearelogicallybranchingourcontrolflowdependingonthevalidityofthebirthdate,yetitreadsasonelinearmotionfromrighttoleftratherthanclimbingthroughthecurlybracesofaconditionalstatement.Usually,we'dmovetheconsole.logoutofourzoltarfunctionandmapitatthetimeofcalling,butit'shelpfultoseehowtheRightbranch
mostly-adequate-guide
50Chapter8:Tupperware
-
differs.Weuse_intherightbranch'stypesignaturetoindicateit'savaluethatshouldbeignored(Insomebrowsersyouhavetouseconsole.log.bind(console)touseitfirstclass).
I'dliketotakethisopportunitytopointoutsomethingyoumayhavemissed:fortune,despiteitsusewithEitherinthisexample,iscompletelyignorantofanyfunctorsmillingabout.ThiswasalsothecasewithfinishTransactioninthepreviousexample.Atthetimeofcalling,afunctioncanbesurroundedbymap,whichtransformsitfromanon-functoryfunctiontoafunctoryone,ininformalterms.Wecallthisprocesslifting.Functionstendtobebetteroffworkingwithnormaldatatypesratherthancontainertypes,thenliftedintotherightcontainerasdeemednecessary.Thisleadstosimpler,morereusablefunctionsthatcanbealteredtoworkwithanyfunctorondemand.
Eitherisgreatforcasualerrorslikevalidationaswellasmoreserious,stoptheshowerrorslikemissingfilesorbrokensockets.TryreplacingsomeoftheMaybeexampleswithEithertogivebetterfeedback.
Now,Ican'thelp,butfeelI'vedoneEitheradisservicebyintroducingitasmerelyacontainerforerrormessages.Itcaptureslogicaldisjunction(a.k.a||)inatype.ItalsoencodestheideaofaCoproductfromcategorytheory,whichwon'tbetouchedoninthisbook,butiswellworthreadinguponasthere'spropertiestobeexploited.Itisthecanonicalsumtype(ordisjointunionofsets)becauseitsamountofpossibleinhabitantsisthesumofthetwocontainedtypes(Iknowthat'sabithandwavysohere'sagreatarticle.TherearemanythingsEithercanbe,butasafunctor,itisusedforitserrorhandling.
JustlikewithMaybe,wehavelittleeither,whichbehavessimilarly,buttakestwofunctionsinsteadofoneandastaticvalue.Eachfunctionshouldreturnthesametype:
//either::(a->c)->(b->c)->Eitherab->cvareither=curry(function(f,g,e){switch(e.constructor){caseLeft:returnf(e.__value);caseRight:returng(e.__value);}});
//zoltar::User->_varzoltar=compose(console.log,either(id,fortune),getAge(moment()));
zoltar({birthdate:'2005-12-12'});//"Ifyousurvive,youwillbe10"//undefined
zoltar({birthdate:'balloons!'});//"Birthdatecouldnotbeparsed"//undefined
Finally,auseforthatmysteriousidfunction.ItsimplyparrotsbackthevalueintheLefttopasstheerrormessagetoconsole.log.We'vemadeourfortunetellingappmorerobustbyenforcingerrorhandlingfromwithingetAge.Weeitherslaptheuserwithahardtruthlikeahighfivefromapalmreaderorwecarryonwithourprocess.Andwiththat,we'rereadytomoveontoanentirelydifferenttypeoffunctor.
OldMcDonaldhadEffects...
mostly-adequate-guide
51Chapter8:Tupperware
https://www.fpcomplete.com/school/to-infinity-and-beyond/pick-of-the-week/sum-types
-
Inourchapteraboutpuritywesawapeculiarexampleofapurefunction.Thisfunctioncontainedaside-effect,butwedubbeditpurebywrappingitsactioninanotherfunction.Here'sanotherexampleofthis:
//getFromStorage::String->(_->String)vargetFromStorage=function(key){returnfunction(){returnlocalStorage[key];}}
Hadwenotsurroundeditsgutsinanotherfunction,getFromStoragewouldvaryitsoutputdependingonexternalcircumstance.Withthesturdywrapperinplace,wewillalwaysgetthesameoutputperinput:afunctionthat,whencalled,willretrieveaparticularitemfromlocalStorage.Andjustlikethat(maybethrowinafewHailMary's)we'veclearedourconscienceandallisforgiven.
Except,thisisn'tparticularlyusefulnowisit.Likeacollectableactionfigureinitsoriginalpackaging,wecan'tactuallyplaywithit.Ifonlytherewereawaytoreachinsideofthecontainerandgetatitscontents...EnterIO.
varIO=function(f){this.__value=f;}
IO.of=function(x){returnnewIO(function(){returnx;});}
IO.prototype.map=function(f){returnnewIO(_.compose(f,this.__value));}
IOdiffersfromthepreviousfunctorsinthatthe__valueisalwaysafunction.Wedon'tthinkofits__valueasafunction,however-thatisanimplementationdetailandwebestignoreit.WhatishappeningisexactlywhatwesawwiththegetFromStorageexample:IOdelaystheimpureactionbycapturingitinafunctionwrapper.Assuch,wethinkofIOascontainingthereturnvalueofthewrappedactionandnotthewrapperitself.Thisisapparentintheoffunction:wehaveanIO(x),theIO(function(){returnx})isjustnecessarytoavoidevaluation.
Let'sseeitinuse:
mostly-adequate-guide
52Chapter8:Tupperware
-
//io_window_::IOWindowvario_window=newIO(function(){returnwindow;});
io_window.map(function(win){returnwin.innerWidth});//IO(1430)
io_window.map(_.prop('location')).map(_.prop('href')).map(split('/'));//IO(["http:","","localhost:8000","blog","posts"])
//$::String->IO[DOM]var$=function(selector){returnnewIO(function(){returndocument.querySelectorAll(selector);});}
$('#myDiv').map(head).map(function(div){returndiv.innerHTML;});//IO('Iamsomeinnerhtml')
Here,io_windowisanactualIOthatwecanmapoverstraightaway,whereas$isafunctionthatreturnsanIOafteritscalled.I'vewrittenouttheconceptualreturnvaluestobetterexpresstheIO,though,inreality,itwillalwaysbe{__value:[Function]}.WhenwemapoverourIO,westickthatfunctionattheendofacompositionwhich,inturn,becomesthenew__valueandsoon.Ourmappedfunctionsdonotrun,theygettackedontheendofacomputationwe'rebuildingup,functionbyfunction,likecarefullyplacingdominoesthatwedon'tdaretipover.TheresultisreminiscentofGangofFour'scommandpatternoraqueue.
Takeamomenttochannelyourfunctorintuition.Ifweseepasttheimplementationdetails,weshouldfeelrightathomemappingoveranycontainernomatteritsquirksoridiosyncrasies.Wehavethefunctorlaws,whichwewillexploretowardtheendofthechapter,tothankforthispseudo-psychicpower.Atanyrate,wecanfinallyplaywithimpurevalueswithoutsacrificingourpreciouspurity.
Now,we'vecagedthebeast,butwe'llstillhavetosetitfreeatsomepoint.MappingoverourIOhasbuiltupamightyimpurecomputationandrunningitissurelygoingtodisturbthepeace.Sowhereandwhencanwepullthetrigger?IsitevenpossibletorunourIOandstillwearwhiteatourwedding?Theanswerisyes,ifweputtheonusonthecallingcode.Ourpurecode,despitethenefariousplottingandscheming,maintainsitsinnocenceandit'sthecallerwhogetsburdenedwiththeresponsibilityofactuallyrunningtheeffects.Let'sseeanexampletomakethisconcrete.
//////Ourpurelibrary:lib/params.js///////
//url::IOStringvarurl=newIO(function(){returnwindow.location.href;});
//toPairs=String->[[String]]vartoPairs=compose(map(split('=')),split('&'));
//params::String->[[String]]varparams=compose(toPairs,last,split('?'));
//findParam::String->IOMaybe[String]varfindParam=function(key){returnmap(compose(Maybe.of,filter(compose(eq(key),head)),params),url);};
//////Impurecallingcode:main.js///////
//runitbycalling__value()!findParam("searchTerm").__value();//Maybe([['searchTerm','wafflehouse']])
OurlibrarykeepsitshandscleanbywrappingurlinanIOandpassingthebucktothecaller.Youmighthavealsonoticedthatwehavestackedourcontainers;it'sperfectlyreasonabletohaveaIO(Maybe([x])),whichisthreefunctorsdeep(Arrayismostd