Mostly Adequate Guide
User Manual:
Open the PDF directly: View PDF .
Page Count: 142 [warning: Documents this large are best viewed by clicking the View PDF Link!]
- Introduction
- Chapter 01: What Ever Are We Doing?
- Chapter 02: First Class Functions
- Chapter 03: Pure Happiness with Pure Functions
- Chapter 04: Currying
- Chapter 05: Coding by Composing
- Chapter 06: Example Application
- Chapter 07: Hindley-Milner and Me
- Chapter 08: Tupperware
- Chapter 09: Monadic Onions
- Chapter 10: Applicative Functors
- Chapter 11: Transform Again, Naturally
- Chapter 12: Traversing the Stone
- Appendix A: Essential Functions Support
- Appendix B: Algebraic Structures Support
- Appendix C: Pointfree Utilities
1.1
1.2
1.2.1
1.2.2
1.3
1.3.1
1.3.2
1.4
1.4.1
1.4.2
1.4.3
1.4.4
1.4.5
1.5
1.5.1
1.5.2
1.5.3
1.5.4
1.6
1.6.1
1.6.2
1.6.3
1.6.4
1.6.5
1.6.6
1.7
1.7.1
1.7.2
1.7.3
1.7.4
TableofContents
Introduction
Chapter01:WhatEverAreWeDoing?
Introductions
ABriefEncounter
Chapter02:FirstClassFunctions
AQuickReview
WhyFavorFirstClass?
Chapter03:PureHappinesswithPureFunctions
OhtoBePureAgain
SideEffectsMayInclude...
8thGradeMath
TheCaseforPurity
InSummary
Chapter04:Currying
Can'tLiveIfLivin'IswithoutYou
MoreThanaPun/SpecialSauce
InSummary
Exercises
Chapter05:CodingbyComposing
FunctionalHusbandry
Pointfree
Debugging
CategoryTheory
InSummary
Exercises
Chapter06:ExampleApplication
DeclarativeCoding
AFlickrofFunctionalProgramming
APrincipledRefactor
InSummary
1
1.8
1.8.1
1.8.2
1.8.3
1.8.4
1.8.5
1.8.6
1.9
1.9.1
1.9.2
1.9.3
1.9.4
1.9.5
1.9.6
1.9.7
1.9.8
1.9.9
1.9.10
1.9.11
1.10
1.10.1
1.10.2
1.10.3
1.10.4
1.10.5
1.10.6
1.10.7
1.11
1.11.1
1.11.2
1.11.3
1.11.4
1.11.5
1.11.6
Chapter07:Hindley-MilnerandMe
What'sYourType?
TalesfromtheCryptic
NarrowingthePossibility
FreeasinTheorem
Constraints
InSummary
Chapter08:Tupperware
TheMightyContainer
MyFirstFunctor
Schrödinger'sMaybe
UseCases
ReleasingtheValue
PureErrorHandling
OldMcDonaldHadEffects...
AsynchronousTasks
ASpotofTheory
InSummary
Exercises
Chapter09:MonadicOnions
PointyFunctorFactory
MixingMetaphors
MyChainHitsMyChest
PowerTrip
Theory
InSummary
Exercises
Chapter10:ApplicativeFunctors
ApplyingApplicatives
ShipsinBottles
CoordinationMotivation
Bro,DoYouEvenLift?
Operators
FreeCanOpeners
2
1.11.7
1.11.8
1.11.9
1.12
1.12.1
1.12.2
1.12.3
1.12.4
1.12.5
1.12.6
1.12.7
1.12.8
1.12.9
1.12.10
1.13
1.13.1
1.13.2
1.13.3
1.13.4
1.13.5
1.13.6
1.13.7
1.14
1.14.1
1.14.2
1.14.3
1.14.4
1.14.5
1.14.6
1.14.7
1.14.8
1.14.9
1.14.10
Laws
InSummary
Exercises
Chapter11:TransformAgain,Naturally
CurseThisNest
ASituationalComedy
AllNatural
PrincipledTypeConversions
FeatureEnvy
IsomorphicJavaScript
ABroaderDefinition
OneNestingSolution
InSummary
Exercises
Chapter12:TraversingtheStone
Typesn'Types
TypeFengShui
EffectAssortment
WaltzoftheTypes
NoLawandOrder
InSummary
Exercises
AppendixA:EssentialFunctionsSupport
always
compose
curry
either
identity
inspect
left
liftA*
maybe
nothing
3
1.14.11
1.15
1.15.1
1.15.2
1.15.3
1.15.4
1.15.5
1.15.6
1.15.7
1.15.8
1.16
1.16.1
1.16.2
1.16.3
1.16.4
1.16.5
1.16.6
1.16.7
1.16.8
1.16.9
1.16.10
1.16.11
1.16.12
1.16.13
1.16.14
1.16.15
1.16.16
1.16.17
1.16.18
1.16.19
1.16.20
1.16.21
1.16.22
1.16.23
reject
AppendixB:AlgebraicStructuresSupport
Compose
Either
Identity
IO
List
Map
Maybe
Task
AppendixC:PointfreeUtilities
add
chain
concat
eq
filter
flip
forEach
head
intercalate
join
last
map
match
prop
reduce
replace
safeHead
safeLast
safeProp
sequence
sortBy
split
take
4
Aboutthisbook
Thisisabookonthefunctionalparadigmingeneral.We'llusetheworld'smostpopular
functionalprogramminglanguage:JavaScript.Somemayfeelthisisapoorchoiceasit's
againstthegrainofthecurrentculturewhich,atthemoment,feelspredominately
imperative.However,IbelieveitisthebestwaytolearnFPforseveralreasons:
Youlikelyuseiteverydayatwork.
Thismakesitpossibletopracticeandapplyyouracquiredknowledgeeachdayonreal
worldprogramsratherthanpetprojectsonnightsandweekendsinanesotericFP
language.
Introduction
6
Wedon'thavetolearneverythingupfronttostartwritingprograms.
Inapurefunctionallanguage,youcannotlogavariableorreadaDOMnodewithout
usingmonads.Herewecancheatalittleaswelearntopurifyourcodebase.It'salso
easiertogetstartedinthislanguagesinceit'smixedparadigmandyoucanfallbackon
yourcurrentpracticeswhiletherearegapsinyourknowledge.
Thelanguageisfullycapableofwritingtopnotchfunctionalcode.
WehaveallthefeaturesweneedtomimicalanguagelikeScalaorHaskellwiththe
helpofatinylibraryortwo.Object-orientedprogrammingcurrentlydominatesthe
industry,butit'sclearlyawkwardinJavaScript.It'sakintocampingoffofahighwayor
tapdancingingaloshes.Wehaveto bindallovertheplacelest thischangeout
fromunderus,wedon'thaveclasses(yet),wehavevariousworkaroundsforthequirky
behaviorwhenthe newkeywordisforgotten,privatemembersareonlyavailablevia
closures.Toalotofus,FPfeelsmorenaturalanyways.
Thatsaid,typedfunctionallanguageswill,withoutadoubt,bethebestplacetocodeinthe
stylepresentedbythisbook.JavaScriptwillbeourmeansoflearningaparadigm,where
youapplyitisuptoyou.Luckily,theinterfacesaremathematicaland,assuch,ubiquitous.
You'llfindyourselfathomewithSwiftz,Scalaz,Haskell,PureScript,andother
mathematicallyinclinedenvironments.
ReaditOnline
Forabestreadingexperience,readitonlineviaGitbook.
Quick-accessside-bar
In-browserexercises
In-depthexamples
Downloadit
DownloadPDF
DownloadEPUB
DownloadMobi(Kindle)
Doityourself
Introduction
7
gitclonehttps://github.com/MostlyAdequate/mostly-adequate-guide.git
cdmostly-adequate-guide/
npminstall
npmrunsetup
gitbookpdf
TableofContents
SeeSUMMARY.md
Contributing
SeeCONTRIBUTING.md
Translations
SeeTRANSLATIONS.md
FAQ
SeeFAQ.md
Plansforthefuture
Part1(chapters1-7)isaguidetothebasics.I'mupdatingasIfinderrorssincethisis
theinitialdraft.Feelfreetohelp!
Part2(chapters8-10)willaddresstypeclasseslikefunctorsandmonadsalltheway
throughtotraversable.Ihopetosqueezeintransformersandapureapplication.
Part3(chapters11+)willstarttodancethefinelinebetweenpracticalprogramming
andacademicabsurdity.We'lllookatcomonads,f-algebras,freemonads,yoneda,and
othercategoricalconstructs.
ThisworkislicensedunderaCreativeCommonsAttribution-ShareAlike4.0International
License.
Introduction
8
Introduction
9
Chapter01:WhatEverAreWeDoing?
Introductions
Hithere!I'mProfessorFranklinFrisby.Pleasedtomakeyouracquaintance.We'llbe
spendingsometimetogether,asI'msupposedtoteachyouabitaboutfunctional
programming.Butenoughaboutme,whataboutyou?I'mhopingthatyou'reatleastabit
familiarwiththeJavaScriptlanguage,haveateensybitofObject-Orientedexperience,and
fancyyourselfaworkingclassprogrammer.Youdon'tneedtohaveaPhDinEntomology,
youjustneedtoknowhowtofindandkillsomebugs.
Iwon'tassumethatyouhaveanypreviousfunctionalprogrammingknowledge,becausewe
bothknowwhathappenswhenyouassume.Iwill,however,expectyoutohaveruninto
someoftheunfavorablesituationsthatarisewhenworkingwithmutablestate,unrestricted
sideeffects,andunprincipleddesign.Nowthatwe'vebeenproperlyintroduced,let'sgeton
withit.
Thepurposeofthischapteristogiveyouafeelforwhatwe'reafterwhenwewritefunctional
programs.Inordertobeabletounderstandthefollowingchapters,wemusthavesomeidea
aboutwhatmakesaprogramfunctional.Otherwisewe'llfindourselvesscribblingaimlessly,
avoidingobjectsatallcosts-aclumsyendeavorindeed.Weneedaclearbullseyetohurl
ourcodeat,somecelestialcompassforwhenthewatersgetrough.
Now,therearesomegeneralprogrammingprinciples-variousacronymiccredosthatguide
usthroughthedarktunnelsofanyapplication:DRY(don'trepeatyourself),YAGNI(yaain't
gonnaneedit),loosecouplinghighcohesion,theprincipleofleastsurprise,single
responsibility,andsoon.
Iwon'tbelaboryoubylistingeachandeveryguidelineI'veheardthroughouttheyears...The
pointofthematteristhattheyholdupinafunctionalsetting,althoughthey'remerely
tangentialtoourultimategoal.WhatI'dlikeyoutogetafeelfornow,beforewegetany
further,isourintentionwhenwepokeandprodatthekeyboard;ourfunctionalXanadu.
ABriefEncounter
Let'sstartwithatouchofinsanity.Hereisaseagullapplication.Whenflocksconjointhey
becomealargerflock,andwhentheybreed,theyincreasebythenumberofseagullswith
whomthey'rebreeding.Now,thisisnotintendedtobegoodObject-Orientedcode,mind
you,itisheretohighlighttheperilsofourmodern,assignmentbasedapproach.Behold:
Chapter01:WhatEverAreWeDoing?
10
classFlock{
constructor(n){
this.seagulls=n;
}
conjoin(other){
this.seagulls+=other.seagulls;
returnthis;
}
breed(other){
this.seagulls=this.seagulls*other.seagulls;
returnthis;
}
}
constflockA=newFlock(4);
constflockB=newFlock(2);
constflockC=newFlock(0);
constresult=flockA
.conjoin(flockC)
.breed(flockB)
.conjoin(flockA.breed(flockB))
.seagulls;
//32
Whoonearthwouldcraftsuchaghastlyabomination?Itisunreasonablydifficulttokeep
trackofthemutatinginternalstate.And,goodheavens,theanswerisevenincorrect!It
shouldhavebeen 16,but flockAwounduppermanentlyalteredintheprocess.Poor
flockA.ThisisanarchyintheI.T.!Thisiswildanimalarithmetic!
Ifyoudon'tunderstandthisprogram,it'sokay,neitherdoI.Thepointtorememberhereis
thatstateandmutablevaluesarehardtofollow,eveninsuchasmallexample.
Let'stryagain,thistimeusingamorefunctionalapproach:
constconjoin=(flockX,flockY)=>flockX+flockY;
constbreed=(flockX,flockY)=>flockX*flockY;
constflockA=4;
constflockB=2;
constflockC=0;
constresult=
conjoin(breed(flockB,conjoin(flockA,flockC)),breed(flockA,flockB));
//16
Chapter01:WhatEverAreWeDoing?
11
Well,thistimewegottherightanswer.Withmuchlesscode.Thefunctionnestingisatad
confusing...(we'llremedythissituationinch5).It'sbetter,butlet'sdigalittlebitdeeper.
Therearebenefitstocallingaspadeaspade.Hadwescrutinizedourcustomfunctions
moreclosely,wewouldhavediscoveredthatwe'rejustworkingwithsimpleaddition
(conjoin)andmultiplication( breed).
There'sreallynothingspecialatallaboutthesetwofunctionsotherthantheirnames.Let's
renameourcustomfunctionsto multiplyand addinordertorevealtheirtrueidentities.
constadd=(x,y)=>x+y;
constmultiply=(x,y)=>x*y;
constflockA=4;
constflockB=2;
constflockC=0;
constresult=
add(multiply(flockB,add(flockA,flockC)),multiply(flockA,flockB));
//16
Andwiththat,wegaintheknowledgeoftheancients:
//associative
add(add(x,y),z)===add(x,add(y,z));
//commutative
add(x,y)===add(y,x);
//identity
add(x,0)===x;
//distributive
multiply(x,add(y,z))===add(multiply(x,y),multiply(x,z));
Ahyes,thoseoldfaithfulmathematicalpropertiesshouldcomeinhandy.Don'tworryifyou
didn'tknowthemrightoffthetopofyourhead.Foralotofus,it'sbeenawhilesincewe
learnedabouttheselawsofarithmetic.Let'sseeifwecanusethesepropertiestosimplify
ourlittleseagullprogram.
Chapter01:WhatEverAreWeDoing?
12
//Originalline
add(multiply(flockB,add(flockA,flockC)),multiply(flockA,flockB));
//Applytheidentitypropertytoremovetheextraadd
//(add(flockA,flockC)==flockA)
add(multiply(flockB,flockA),multiply(flockA,flockB));
//Applydistributivepropertytoachieveourresult
multiply(flockB,add(flockA,flockA));
Brilliant!Wedidn'thavetowritealickofcustomcodeotherthanourcallingfunction.We
include addand multiplydefinitionshereforcompleteness,butthereisreallynoneedto
writethem-wesurelyhavean addand multiplyprovidedbysomeexistinglibrary.
Youmaybethinking"howverystrawmanofyoutoputsuchamathyexampleupfront".Or
"realprogramsarenotthissimpleandcannotbereasonedaboutinsuchaway."I'vechosen
thisexamplebecausemostofusalreadyknowaboutadditionandmultiplication,soit'seasy
toseehowmathisveryusefulforushere.
Don'tdespair-throughoutthisbook,we'llsprinkleinsomecategorytheory,settheory,and
lambdacalculusandwriterealworldexamplesthatachievethesameelegantsimplicityand
resultsasourflockofseagullsexample.Youneedn'tbeamathematicianeither.Itwillfeel
naturalandeasy,justlikeyouwereusinga"normal"frameworkorAPI.
Itmaycomeasasurprisetohearthatwecanwritefull,everydayapplicationsalongthe
linesofthefunctionalanalogabove.Programsthathavesoundproperties.Programsthat
areterse,yeteasytoreasonabout.Programsthatdon'treinventthewheelateveryturn.
Lawlessnessisgoodifyou'reacriminal,butinthisbook,we'llwanttoacknowledgeand
obeythelawsofmath.
We'llwanttouseatheorywhereeverypiecetendstofittogethersopolitely.We'llwantto
representourspecificproblemintermsofgeneric,composablebitsandthenexploittheir
propertiesforourownselfishbenefit.Itwilltakeabitmoredisciplinethanthe"anything
goes"approachofimperativeprogramming(we'llgoovertheprecisedefinitionof
"imperative"laterinthebook,butfornowconsideritanythingotherthanfunctional
programming).Thepayoffofworkingwithinaprincipled,mathematicalframeworkwilltruly
astoundyou.
We'veseenaflickerofourfunctionalnorthernstar,butthereareafewconcreteconceptsto
graspbeforewecanreallybeginourjourney.
Chapter02:FirstClassFunctions
Chapter01:WhatEverAreWeDoing?
13
Chapter01:WhatEverAreWeDoing?
14
Chapter02:FirstClassFunctions
AQuickReview
Whenwesayfunctionsare"firstclass",wemeantheyarejustlikeeveryoneelse...soin
otherwordsanormalclass.Wecantreatfunctionslikeanyotherdatatypeandthereis
nothingparticularlyspecialaboutthem-theymaybestoredinarrays,passedaroundas
functionparameters,assignedtovariables,andwhathaveyou.
ThatisJavaScript101,butworthmentioningsinceaquickcodesearchongithubwillreveal
thecollectiveevasion,orperhapswidespreadignoranceofthisconcept.Shallwegofora
feignedexample?Weshall.
consthi=name=>`Hi${name}`;
constgreeting=name=>hi(name);
Here,thefunctionwrapperaround hiin greetingiscompletelyredundant.Why?
BecausefunctionsarecallableinJavaScript.When hihasthe ()attheenditwillrun
andreturnavalue.Whenitdoesnot,itsimplyreturnsthefunctionstoredinthevariable.
Justtobesure,havealookyourself:
hi;//name=>`Hi${name}`
hi("jonas");//"Hijonas"
Since greetingismerelyinturncalling hiwiththeverysameargument,wecouldsimply
write:
constgreeting=hi;
greeting("times");//"Hitimes"
Inotherwords, hiisalreadyafunctionthatexpectsoneargument,whyplaceanother
functionarounditthatsimplycalls hiwiththesamebloodyargument?Itdoesn'tmakeany
damnsense.It'slikedonningyourheaviestparkainthedeadofJulyjusttoblasttheairand
demandanicelolly.
Itisobnoxiouslyverboseand,asithappens,badpracticetosurroundafunctionwith
anotherfunctionmerelytodelayevaluation(we'llseewhyinamoment,butithastodowith
maintenance)
Chapter02:FirstClassFunctions
15
Asolidunderstandingofthisiscriticalbeforemovingon,solet'sexamineafewmorefun
examplesexcavatedfromthelibraryofnpmpackages.
//ignorant
constgetServerStuff=callback=>ajaxCall(json=>callback(json));
//enlightened
constgetServerStuff=ajaxCall;
Theworldislitteredwithajaxcodeexactlylikethis.Hereisthereasonbothareequivalent:
//thisline
ajaxCall(json=>callback(json));
//isthesameasthisline
ajaxCall(callback);
//sorefactorgetServerStuff
constgetServerStuff=callback=>ajaxCall(callback);
//...whichisequivalenttothis
constgetServerStuff=ajaxCall;//<--lookmum,no()'s
Andthat,folks,ishowitisdone.OncemoresothatweunderstandwhyI'mbeingso
persistent.
constBlogController={
index(posts){returnViews.index(posts);},
show(post){returnViews.show(post);},
create(attrs){returnDb.create(attrs);},
update(post,attrs){returnDb.update(post,attrs);},
destroy(post){returnDb.destroy(post);},
};
Thisridiculouscontrolleris99%fluff.Wecouldeitherrewriteitas:
constBlogController={
index:Views.index,
show:Views.show,
create:Db.create,
update:Db.update,
destroy:Db.destroy,
};
...orscrapitaltogethersinceitdoesnothingmorethanjustbundleourViewsandDb
together.
Chapter02:FirstClassFunctions
16
WhyFavorFirstClass?
Okay,let'sgetdowntothereasonstofavorfirstclassfunctions.Aswesawinthe
getServerStuffand BlogControllerexamples,it'seasytoaddlayersofindirectionthat
providenoaddedvalueandonlyincreasetheamountofredundantcodetomaintainand
searchthrough.
Inaddition,ifsuchaneedlesslywrappedfunctionmustbechanged,wemustalsoneedto
changeourwrapperfunctionaswell.
httpGet('/post/2',json=>renderPost(json));
If httpGetweretochangetosendapossible err,wewouldneedtogobackandchange
the"glue".
//gobacktoeveryhttpGetcallintheapplicationandexplicitlypasserralong.
httpGet('/post/2',(json,err)=>renderPost(json,err));
Hadwewrittenitasafirstclassfunction,muchlesswouldneedtochange:
//renderPostiscalledfromwithinhttpGetwithhowevermanyargumentsitwants
httpGet('/post/2',renderPost);
Besidestheremovalofunnecessaryfunctions,wemustnameandreferencearguments.
Namesareabitofanissue,yousee.Wehavepotentialmisnomers-especiallyasthe
codebaseagesandrequirementschange.
Havingmultiplenamesforthesameconceptisacommonsourceofconfusioninprojects.
Thereisalsotheissueofgenericcode.Forinstance,thesetwofunctionsdoexactlythe
samething,butonefeelsinfinitelymoregeneralandreusable:
//specifictoourcurrentblog
constvalidArticles=articles=>
articles.filter(article=>article!==null&&article!==undefined),
//vastlymorerelevantforfutureprojects
constcompact=xs=>xs.filter(x=>x!==null&&x!==undefined);
Byusingspecificnaming,we'veseeminglytiedourselvestospecificdata(inthiscase
articles).Thishappensquiteabitandisasourceofmuchreinvention.
Chapter02:FirstClassFunctions
17
Imustmentionthat,justlikewithObject-Orientedcode,youmustbeawareof thiscoming
tobiteyouinthejugular.Ifanunderlyingfunctionuses thisandwecallitfirstclass,we
aresubjecttothisleakyabstraction'swrath.
constfs=require('fs');
//scary
fs.readFile('freaky_friday.txt',Db.save);
//lessso
fs.readFile('freaky_friday.txt',Db.save.bind(Db));
Havingbeenboundtoitself,the Dbisfreetoaccessitsprototypicalgarbagecode.Iavoid
using thislikeadirtynappy.There'sreallynoneedwhenwritingfunctionalcode.
However,wheninterfacingwithotherlibraries,youmighthavetoacquiescetothemad
worldaroundus.
Somewillarguethat thisisnecessaryforoptimizingspeed.Ifyouarethemicro-
optimizationsort,pleaseclosethisbook.Ifyoucannotgetyourmoneyback,perhapsyou
canexchangeitforsomethingmorefiddly.
Andwiththat,we'rereadytomoveon.
Chapter03:PureHappinesswithPureFunctions
Chapter02:FirstClassFunctions
18
Chapter03:PureHappinesswithPure
Functions
OhtoBePureAgain
Onethingweneedtogetstraightistheideaofapurefunction.
Apurefunctionisafunctionthat,giventhesameinput,willalwaysreturnthesame
outputanddoesnothaveanyobservablesideeffect.
Take sliceand splice.Theyaretwofunctionsthatdotheexactsamething-inavastly
differentway,mindyou,butthesamethingnonetheless.Wesay sliceispurebecauseit
returnsthesameoutputperinputeverytime,guaranteed. splice,however,willchewupits
arrayandspititbackoutforeverchangedwhichisanobservableeffect.
constxs=[1,2,3,4,5];
//pure
xs.slice(0,3);//[1,2,3]
xs.slice(0,3);//[1,2,3]
xs.slice(0,3);//[1,2,3]
//impure
xs.splice(0,3);//[1,2,3]
xs.splice(0,3);//[4,5]
xs.splice(0,3);//[]
Infunctionalprogramming,wedislikeunwieldyfunctionslike splicethatmutatedata.This
willneverdoaswe'restrivingforreliablefunctionsthatreturnthesameresulteverytime,
notfunctionsthatleaveamessintheirwakelike splice.
Let'slookatanotherexample.
Chapter03:PureHappinesswithPureFunctions
19
//impure
constminimum=21;
constcheckAge=age=>age>=minimum;
//pure
constcheckAge=(age)=>{
constminimum=21;
returnage>=minimum;
};
Intheimpureportion, checkAgedependsonthemutablevariable minimumtodeterminethe
result.Inotherwords,itdependsonsystemstatewhichisdisappointingbecauseit
increasesthecognitiveloadbyintroducinganexternalenvironment.
Itmightnotseemlikealotinthisexample,butthisrelianceuponstateisoneofthelargest
contributorstosystemcomplexity
(http://www.curtclifton.net/storage/papers/MoseleyMarks06a.pdf).This checkAgemayreturn
differentresultsdependingonfactorsexternaltoinput,whichnotonlydisqualifiesitfrom
beingpure,butalsoputsourmindsthroughtheringereachtimewe'rereasoningaboutthe
software.
Itspureform,ontheotherhand,iscompletelyselfsufficient.Wecanalsomake minimum
immutable,whichpreservesthepurityasthestatewillneverchange.Todothis,wemust
createanobjecttofreeze.
constimmutableState=Object.freeze({minimum:21});
SideEffectsMayInclude...
Let'slookmoreatthese"sideeffects"toimproveourintuition.Sowhatisthisundoubtedly
nefarioussideeffectmentionedinthedefinitionofpurefunction?We'llbereferringtoeffect
asanythingthatoccursinourcomputationotherthanthecalculationofaresult.
There'snothingintrinsicallybadabouteffectsandwe'llbeusingthemallovertheplacein
thechapterstocome.It'sthatsidepartthatbearsthenegativeconnotation.Wateraloneis
notaninherentlarvaeincubator,it'sthestagnantpartthatyieldstheswarms,andIassure
you,sideeffectsareasimilarbreedinggroundinyourownprograms.
Asideeffectisachangeofsystemstateorobservableinteractionwiththeoutside
worldthatoccursduringthecalculationofaresult.
Sideeffectsmayinclude,butarenotlimitedto
Chapter03:PureHappinesswithPureFunctions
20
changingthefilesystem
insertingarecordintoadatabase
makinganhttpcall
mutations
printingtothescreen/logging
obtaininguserinput
queryingtheDOM
accessingsystemstate
Andthelistgoesonandon.Anyinteractionwiththeworldoutsideofafunctionisaside
effect,whichisafactthatmaypromptyoutosuspectthepracticalityofprogrammingwithout
them.Thephilosophyoffunctionalprogrammingpostulatesthatsideeffectsareaprimary
causeofincorrectbehavior.
Itisnotthatwe'reforbiddentousethem,ratherwewanttocontainthemandrunthemina
controlledway.We'lllearnhowtodothiswhenwegettofunctorsandmonadsinlater
chapters,butfornow,let'strytokeeptheseinsidiousfunctionsseparatefromourpureones.
Sideeffectsdisqualifyafunctionfrombeingpure.Anditmakessense:purefunctions,by
definition,mustalwaysreturnthesameoutputgiventhesameinput,whichisnotpossibleto
guaranteewhendealingwithmattersoutsideourlocalfunction.
Let'stakeacloserlookatwhyweinsistonthesameoutputperinput.Popyourcollars,
we'regoingtolookatsome8thgrademath.
8thGradeMath
Frommathisfun.com:
Afunctionisaspecialrelationshipbetweenvalues:Eachofitsinputvaluesgivesback
exactlyoneoutputvalue.
Inotherwords,it'sjustarelationbetweentwovalues:theinputandtheoutput.Thougheach
inputhasexactlyoneoutput,thatoutputdoesn'tnecessarilyhavetobeuniqueperinput.
Belowshowsadiagramofaperfectlyvalidfunctionfrom xto y;
(http://www.mathsisfun.com/sets/function.html)
Chapter03:PureHappinesswithPureFunctions
21
Tocontrast,thefollowingdiagramshowsarelationthatisnotafunctionsincetheinput
value 5pointstoseveraloutputs:
(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
Orevenasagraphwith xastheinputand yastheoutput:
There'snoneedforimplementationdetailsiftheinputdictatestheoutput.Sincefunctions
aresimplymappingsofinputtooutput,onecouldsimplyjotdownobjectliteralsandrun
themwith []insteadof ().
Chapter03:PureHappinesswithPureFunctions
22
consttoLowerCase={
A:'a',
B:'b',
C:'c',
D:'d',
E:'e',
F:'f',
};
toLowerCase['C'];//'c'
constisPrime={
1:false,
2:true,
3:true,
4:false,
5:true,
6:false,
};
isPrime[3];//true
Ofcourse,youmightwanttocalculateinsteadofhandwritingthingsout,butthisillustratesa
differentwaytothinkaboutfunctions.(Youmaybethinking"whataboutfunctionswith
multiplearguments?".Indeed,thatpresentsabitofaninconveniencewhenthinkinginterms
ofmathematics.Fornow,wecanbundlethemupinanarrayorjustthinkofthe arguments
objectastheinput.Whenwelearnaboutcurrying,we'llseehowwecandirectlymodelthe
mathematicaldefinitionofafunction.)
Herecomesthedramaticreveal:Purefunctionsaremathematicalfunctionsandthey'rewhat
functionalprogrammingisallabout.Programmingwiththeselittleangelscanprovidehuge
benefits.Let'slookatsomereasonswhywe'rewillingtogotogreatlengthstopreserve
purity.
TheCaseforPurity
Cacheable
Forstarters,purefunctionscanalwaysbecachedbyinput.Thisistypicallydoneusinga
techniquecalledmemoization:
Chapter03:PureHappinesswithPureFunctions
23
constsquareNumber=memoize(x=>x*x);
squareNumber(4);//16
squareNumber(4);//16,returnscacheforinput4
squareNumber(5);//25
squareNumber(5);//25,returnscacheforinput5
Hereisasimplifiedimplementation,thoughthereareplentyofmorerobustversions
available.
constmemoize=(f)=>{
constcache={};
return(...args)=>{
constargStr=JSON.stringify(args);
cache[argStr]=cache[argStr]||f(...args);
returncache[argStr];
};
};
Somethingtonoteisthatyoucantransformsomeimpurefunctionsintopureonesby
delayingevaluation:
constpureHttpCall=memoize((url,params)=>()=>$.getJSON(url,params));
Theinterestingthinghereisthatwedon'tactuallymakethehttpcall-weinsteadreturna
functionthatwilldosowhencalled.Thisfunctionispurebecauseitwillalwaysreturnthe
sameoutputgiventhesameinput:thefunctionthatwillmaketheparticularhttpcallgiven
the urland params.
Our memoizefunctionworksjustfine,thoughitdoesn'tcachetheresultsofthehttpcall,
ratheritcachesthegeneratedfunction.
Thisisnotveryusefulyet,butwe'llsoonlearnsometricksthatwillmakeitso.Thetakeaway
isthatwecancacheeveryfunctionnomatterhowdestructivetheyseem.
Portable/Self-documenting
Purefunctionsarecompletelyselfcontained.Everythingthefunctionneedsishandedtoit
onasilverplatter.Ponderthisforamoment...Howmightthisbebeneficial?Forstarters,a
function'sdependenciesareexplicitandthereforeeasiertoseeandunderstand-nofunny
Chapter03:PureHappinesswithPureFunctions
24
businessgoingonunderthehood.
//impure
constsignUp=(attrs)=>{
constuser=saveUser(attrs);
welcomeUser(user);
};
//pure
constsignUp=(Db,Email,attrs)=>()=>{
constuser=saveUser(Db,attrs);
welcomeUser(Email,user);
};
Theexampleheredemonstratesthatthepurefunctionmustbehonestaboutits
dependenciesand,assuch,tellusexactlywhatit'supto.Justfromitssignature,weknow
thatitwillusea Db, Email,and attrswhichshouldbetellingtosaytheleast.
We'lllearnhowtomakefunctionslikethispurewithoutmerelydeferringevaluation,butthe
pointshouldbeclearthatthepureformismuchmoreinformativethanitssneakyimpure
counterpartwhichisuptowhoknowswhat.
Somethingelsetonoticeisthatwe'reforcedto"inject"dependencies,orpasstheminas
arguments,whichmakesourappmuchmoreflexiblebecausewe'veparameterizedour
databaseormailclientorwhathaveyou(don'tworry,we'llseeawaytomakethisless
tediousthanitsounds).ShouldwechoosetouseadifferentDbweneedonlytocallour
functionwithit.Shouldwefindourselveswritinganewapplicationinwhichwe'dliketo
reusethisreliablefunction,wesimplygivethisfunctionwhatever Dband Emailwehave
atthetime.
InaJavaScriptsetting,portabilitycouldmeanserializingandsendingfunctionsovera
socket.Itcouldmeanrunningallourappcodeinwebworkers.Portabilityisapowerfultrait.
Contraryto"typical"methodsandproceduresinimperativeprogrammingrooteddeepin
theirenvironmentviastate,dependencies,andavailableeffects,purefunctionscanberun
anywhereourheartsdesire.
Whenwasthelasttimeyoucopiedamethodintoanewapp?Oneofmyfavoritequotes
comesfromErlangcreator,JoeArmstrong:"Theproblemwithobject-orientedlanguagesis
they’vegotallthisimplicitenvironmentthattheycarryaroundwiththem.Youwanteda
bananabutwhatyougotwasagorillaholdingthebanana...andtheentirejungle".
Testable
Chapter03:PureHappinesswithPureFunctions
25
Next,wecometorealizepurefunctionsmaketestingmucheasier.Wedon'thavetomocka
"real"paymentgatewayorsetupandassertthestateoftheworldaftereachtest.Wesimply
givethefunctioninputandassertoutput.
Infact,wefindthefunctionalcommunitypioneeringnewtesttoolsthatcanblastour
functionswithgeneratedinputandassertthatpropertiesholdontheoutput.It'sbeyondthe
scopeofthisbook,butIstronglyencourageyoutosearchforandtryQuickcheck-atesting
toolthatistailoredforapurelyfunctionalenvironment.
Reasonable
Manybelievethebiggestwinwhenworkingwithpurefunctionsisreferentialtransparency.A
spotofcodeisreferentiallytransparentwhenitcanbesubstitutedforitsevaluatedvalue
withoutchangingthebehavioroftheprogram.
Sincepurefunctionsalwaysreturnthesameoutputgiventhesameinput,wecanrelyon
themtoalwaysreturnthesameresultsandthuspreservereferentialtransparency.Let'ssee
anexample.
const{Map}=require('immutable');
//Aliases:p=player,a=attacker,t=target
constjobe=Map({name:'Jobe',hp:20,team:'red'});
constmichael=Map({name:'Michael',hp:20,team:'green'});
constdecrementHP=p=>p.set('hp',p.get('hp')-1);
constisSameTeam=(p1,p2)=>p1.get('team')===p2.get('team');
constpunch=(a,t)=>(isSameTeam(a,t)?t:decrementHP(t));
punch(jobe,michael);//Map({name:'Michael',hp:19,team:'green'})
decrementHP, isSameTeamand punchareallpureandthereforereferentiallytransparent.
Wecanuseatechniquecalledequationalreasoningwhereinonesubstitutes"equalsfor
equals"toreasonaboutcode.It'sabitlikemanuallyevaluatingthecodewithouttakinginto
accountthequirksofprogrammaticevaluation.Usingreferentialtransparency,let'splaywith
thiscodeabit.
Firstwe'llinlinethefunction isSameTeam.
constpunch=(a,t)=>(a.get('team')===t.get('team')?t:decrementHP(t));
Sinceourdataisimmutable,wecansimplyreplacetheteamswiththeiractualvalue
constpunch=(a,t)=>('red'==='green'?t:decrementHP(t));
Chapter03:PureHappinesswithPureFunctions
26
Weseethatitisfalseinthiscasesowecanremovetheentireifbranch
constpunch=(a,t)=>decrementHP(t);
Andifweinline decrementHP,weseethat,inthiscase,punchbecomesacalltodecrement
the hpby1.
constpunch=(a,t)=>t.set('hp',t.get('hp')-1);
Thisabilitytoreasonaboutcodeisterrificforrefactoringandunderstandingcodeingeneral.
Infact,weusedthistechniquetorefactorourflockofseagullsprogram.Weusedequational
reasoningtoharnessthepropertiesofadditionandmultiplication.Indeed,we'llbeusing
thesetechniquesthroughoutthebook.
ParallelCode
Finally,andhere'sthecoupdegrâce,wecanrunanypurefunctioninparallelsinceitdoes
notneedaccesstosharedmemoryanditcannot,bydefinition,havearaceconditiondueto
somesideeffect.
Thisisverymuchpossibleinaserversidejsenvironmentwiththreadsaswellasinthe
browserwithwebworkersthoughcurrentcultureseemstoavoiditduetocomplexitywhen
dealingwithimpurefunctions.
InSummary
We'veseenwhatpurefunctionsareandwhywe,asfunctionalprogrammers,believethey
arethecat'seveningwear.Fromthispointon,we'llstrivetowriteallourfunctionsinapure
way.We'llrequiresomeextratoolstohelpusdoso,butinthemeantime,we'lltryto
separatetheimpurefunctionsfromtherestofthepurecode.
Writingprogramswithpurefunctionsisatadlaboriouswithoutsomeextratoolsinourbelt.
Wehavetojuggledatabypassingargumentsallovertheplace,we'reforbiddentouse
state,nottomentioneffects.Howdoesonegoaboutwritingthesemasochisticprograms?
Let'sacquireanewtoolcalledcurry.
Chapter04:Currying
Chapter03:PureHappinesswithPureFunctions
27
Chapter04:Currying
Can'tLiveIfLivin'IswithoutYou
MyDadonceexplainedhowtherearecertainthingsonecanlivewithoutuntiloneacquires
them.Amicrowaveisonesuchthing.Smartphones,another.Theolderfolksamonguswill
rememberafulfillinglifesansinternet.Forme,curryingisonthislist.
Theconceptissimple:Youcancallafunctionwithfewerargumentsthanitexpects.It
returnsafunctionthattakestheremainingarguments.
Youcanchoosetocallitallatonceorsimplyfeedineachargumentpiecemeal.
constadd=x=>y=>x+y;
constincrement=add(1);
constaddTen=add(10);
increment(2);//3
addTen(2);//12
Herewe'vemadeafunction addthattakesoneargumentandreturnsafunction.Bycalling
it,thereturnedfunctionremembersthefirstargumentfromthenonviatheclosure.Callingit
withbothargumentsallatonceisabitofapain,however,sowecanuseaspecialhelper
functioncalled currytomakedefiningandcallingfunctionslikethiseasier.
Let'ssetupafewcurriedfunctionsforourenjoyment.Fromnowon,we'llsummonour
curryfunctiondefinedintheAppendixA-EssentialFunctionSupport.
constmatch=curry((what,s)=>s.match(what));
constreplace=curry((what,replacement,s)=>s.replace(what,replacement));
constfilter=curry((f,xs)=>xs.filter(f));
constmap=curry((f,xs)=>xs.map(f));
ThepatternI'vefollowedisasimple,butimportantone.I'vestrategicallypositionedthedata
we'reoperatingon(String,Array)asthelastargument.Itwillbecomeclearastowhyupon
use.
(Thesyntax /r/gisaregularexpressionthatmeansmatcheveryletter'r'.Readmore
aboutregularexpressionsifyoulike.)
Chapter04:Currying
28
match(/r/g,'helloworld');//['r']
consthasLetterR=match(/r/g);//x=>x.match(/r/g)
hasLetterR('helloworld');//['r']
hasLetterR('justjandsandtetc');//null
filter(hasLetterR,['rockandroll','smoothjazz']);//['rockandroll']
constremoveStringsWithoutRs=filter(hasLetterR);//xs=>xs.filter(x=>x.match(/r/
g))
removeStringsWithoutRs(['rockandroll','smoothjazz','drumcircle']);//['rockand
roll','drumcircle']
constnoVowels=replace(/[aeiou]/ig);//(r,x)=>x.replace(/[aeiou]/ig,r)
constcensored=noVowels('*');//x=>x.replace(/[aeiou]/ig,'*')
censored('ChocolateRain');//'Ch*c*l*t*R**n'
What'sdemonstratedhereistheabilityto"pre-load"afunctionwithanargumentortwoin
ordertoreceiveanewfunctionthatremembersthosearguments.
IencourageyoutoclonetheMostlyAdequaterepository( gitclone
https://github.com/MostlyAdequate/mostly-adequate-guide.git),copythecodeaboveand
haveagoatitintheREPL.Thecurryfunction(andactuallyanythingdefinedinthe
appendixes)hasbeenmadeavailablefromthe exercises/support.jsmodule.
MoreThanaPun/SpecialSauce
Curryingisusefulformanythings.Wecanmakenewfunctionsjustbygivingourbase
functionssomeargumentsasseenin hasLetterR, removeStringsWithoutRs,and censored.
Wealsohavetheabilitytotransformanyfunctionthatworksonsingleelementsintoa
functionthatworksonarrayssimplybywrappingitwith map:
constgetChildren=x=>x.childNodes;
constallTheChildren=map(getChildren);
Givingafunctionfewerargumentsthanitexpectsistypicallycalledpartialapplication.
Partiallyapplyingafunctioncanremovealotofboilerplatecode.Considerwhattheabove
allTheChildrenfunctionwouldbewiththeuncurried mapfromlodash(notethearguments
areinadifferentorder):
constallTheChildren=elements=>map(elements,getChildren);
Chapter04:Currying
29
Wetypicallydon'tdefinefunctionsthatworkonarrays,becausewecanjustcall
map(getChildren)inline.Samewith sort, filter,andotherhigherorderfunctions(a
higherorderfunctionisafunctionthattakesorreturnsafunction).
Whenwespokeaboutpurefunctions,wesaidtheytake1inputto1output.Curryingdoes
exactlythis:eachsingleargumentreturnsanewfunctionexpectingtheremaining
arguments.That,oldsport,is1inputto1output.
Nomatteriftheoutputisanotherfunction-itqualifiesaspure.Wedoallowmorethanone
argumentatatime,butthisisseenasmerelyremovingtheextra ()'sforconvenience.
InSummary
CurryingishandyandIverymuchenjoyworkingwithcurriedfunctionsonadailybasis.Itis
atoolforthebeltthatmakesfunctionalprogramminglessverboseandtedious.
Wecanmakenew,usefulfunctionsontheflysimplybypassinginafewargumentsandas
abonus,we'veretainedthemathematicalfunctiondefinitiondespitemultiplearguments.
Let'sacquireanotheressentialtoolcalled compose.
Chapter05:CodingbyComposing
Exercises
NoteaboutExercises
Throughoutthebook,youmightencounteran'Exercises'sectionlikethisone.Exercises
canbedonedirectlyin-browserprovidedyou'rereadingfromgitbook(recommended).
Notethat,forallexercisesofthebook,youalwayshaveahandfulofhelperfunctions
availableintheglobalscope.Hence,anythingthatisdefinedinAppendixA,AppendixBand
AppendixCisavailableforyou!And,asifitwasn'tenough,someexerciseswillalsodefine
functionsspecifictotheproblemtheypresent;asamatteroffact,considerthemavailable
aswell.
Hint:youcansubmityoursolutionbydoing Ctrl+Enterintheembeddededitor!
RunningExercisesonYourMachine(optional)
Shouldyouprefertodoexercisesdirectlyinfilesusingyourowneditor:
clonetherepository( gitclonegit@github.com/MostlyAdequate/mostly-adequate-
Chapter04:Currying
30
guide.git)
gointheexercisessection( cdmostly-adequate-guide/exercises)
installthenecessaryplumbingusingnpm( npminstall)
completeanswersbymodifyingthefilesnamedexercises_*inthecorresponding
chapter'sfolder
runthecorrectionwithnpm(e.g. npmrunch04)
Unittestswillrunagainstyouranswersandprovidehintsincaseofmistake.Bytheby,the
answerstotheexercisesareavailableinfilesnamedanswers_*.
Let'sPractice!
Exercise
Refactortoremoveallargumentsbypartiallyapplyingthefunction.
//words::String->[String]
constwords=str=>split('',str);
Exercise
Refactortoremoveallargumentsbypartiallyapplyingthefunctions.
//filterQs::[String]->[String]
constfilterQs=xs=>filter(x=>x.match(/q/i),xs);
Consideringthefollowingfunction:
constkeepHighest=(x,y)=>(x>=y?x:y);
Exercise
Refactor`max`tonotreferenceanyargumentsusingthehelperfunction`keepHighest`.
//max::[Number]->Number
constmax=xs=>reduce((acc,x)=>(x>=acc?x:acc),-Infinity,xs);
Chapter04:Currying
31
Chapter04:Currying
32
Chapter05:CodingbyComposing
FunctionalHusbandry
Here's compose:
constcompose=(f,g)=>x=>f(g(x));
fand garefunctionsand xisthevaluebeing"piped"throughthem.
Compositionfeelslikefunctionhusbandry.You,breederoffunctions,selecttwowithtraits
you'dliketocombineandmashthemtogethertospawnabrandnewone.Usageisas
follows:
consttoUpperCase=x=>x.toUpperCase();
constexclaim=x=>`${x}!`;
constshout=compose(exclaim,toUpperCase);
shout('sendintheclowns');//"SENDINTHECLOWNS!"
Thecompositionoftwofunctionsreturnsanewfunction.Thismakesperfectsense:
composingtwounitsofsometype(inthiscasefunction)shouldyieldanewunitofthatvery
type.Youdon'tplugtwolegostogetherandgetalincolnlog.Thereisatheoryhere,some
underlyinglawthatwewilldiscoverinduetime.
Inourdefinitionof compose,the gwillrunbeforethe f,creatingarighttoleftflowof
data.Thisismuchmorereadablethannestingabunchoffunctioncalls.Withoutcompose,
theabovewouldread:
constshout=x=>exclaim(toUpperCase(x));
Insteadofinsidetooutside,werunrighttoleft,whichIsupposeisastepintheleftdirection
(boo!).Let'slookatanexamplewheresequencematters:
consthead=x=>x[0];
constreverse=reduce((acc,x)=>[x].concat(acc),[]);
constlast=compose(head,reverse);
last(['jumpkick','roundhouse','uppercut']);//'uppercut'
Chapter05:CodingbyComposing
33
reversewillturnthelistaroundwhile headgrabstheinitialitem.Thisresultsinan
effective,albeitinefficient, lastfunction.Thesequenceoffunctionsinthecomposition
shouldbeapparenthere.Wecoulddefinealefttorightversion,however,wemirrorthe
mathematicalversionmuchmorecloselyasitstands.That'sright,compositionisstraight
fromthemathbooks.Infact,perhapsit'stimetolookatapropertythatholdsforany
composition.
//associativity
compose(f,compose(g,h))===compose(compose(f,g),h);
Compositionisassociative,meaningitdoesn'tmatterhowyougrouptwoofthem.So,
shouldwechoosetouppercasethestring,wecanwrite:
compose(toUpperCase,compose(head,reverse));
//or
compose(compose(toUpperCase,head),reverse);
Sinceitdoesn'tmatterhowwegroupourcallsto compose,theresultwillbethesame.That
allowsustowriteavariadiccomposeanduseitasfollows:
//previouslywe'dhavetowritetwocomposes,butsinceit'sassociative,
//wecangivecomposeasmanyfn'saswelikeandletitdecidehowtogroupthem.
constarg=['jumpkick','roundhouse','uppercut'];
constlastUpper=compose(toUpperCase,head,reverse);
constloudLastUpper=compose(exclaim,toUpperCase,head,reverse);
lastUpper(arg);//'UPPERCUT'
loudLastUpper(arg);//'UPPERCUT!'
Applyingtheassociativepropertygivesusthisflexibilityandpeaceofmindthattheresult
willbeequivalent.Theslightlymorecomplicatedvariadicdefinitionisincludedwiththe
supportlibrariesforthisbookandisthenormaldefinitionyou'llfindinlibrarieslikelodash,
underscore,andramda.
Onepleasantbenefitofassociativityisthatanygroupoffunctionscanbeextractedand
bundledtogetherintheirveryowncomposition.Let'splaywithrefactoringourprevious
example:
Chapter05:CodingbyComposing
34
constloudLastUpper=compose(exclaim,toUpperCase,head,reverse);
//--or---------------------------------------------------------------
constlast=compose(head,reverse);
constloudLastUpper=compose(exclaim,toUpperCase,last);
//--or---------------------------------------------------------------
constlast=compose(head,reverse);
constangry=compose(exclaim,toUpperCase);
constloudLastUpper=compose(angry,last);
//morevariations...
There'snorightorwronganswers-we'rejustpluggingourlegostogetherinwhateverway
weplease.Usuallyit'sbesttogroupthingsinareusablewaylike lastand angry.If
familiarwithFowler's"Refactoring",onemightrecognizethisprocessas"extract
method"...exceptwithoutalltheobjectstatetoworryabout.
Pointfree
Pointfreestylemeansneverhavingtosayyourdata.Excuseme.Itmeansfunctionsthat
nevermentionthedatauponwhichtheyoperate.Firstclassfunctions,currying,and
compositionallplaywelltogethertocreatethisstyle.
Hint:Pointfreeversionsof replace& toLowerCasearedefinedintheAppendixC-
PointfreeUtilities.Donothesitatetohaveapeek!
//notpointfreebecausewementionthedata:word
constsnakeCase=word=>word.toLowerCase().replace(/\s+/ig,'_');
//pointfree
constsnakeCase=compose(replace(/\s+/ig,'_'),toLowerCase);
Seehowwepartiallyapplied replace?Whatwe'redoingispipingourdatathrougheach
functionof1argument.Curryingallowsustoprepareeachfunctiontojusttakeitsdata,
operateonit,andpassitalong.Somethingelsetonoticeishowwedon'tneedthedatato
constructourfunctioninthepointfreeversion,whereasinthepointfulone,wemusthaveour
wordavailablebeforeanythingelse.
Let'slookatanotherexample.
Chapter05:CodingbyComposing
35
//notpointfreebecausewementionthedata:name
constinitials=name=>name.split('').map(compose(toUpperCase,head)).join('.');
//pointfree
constinitials=compose(join('.'),map(compose(toUpperCase,head)),split(''));
initials('hunterstocktonthompson');//'H.S.T'
Pointfreecodecanagain,helpusremoveneedlessnamesandkeepusconciseand
generic.Pointfreeisagoodlitmustestforfunctionalcodeasitletsusknowwe'vegotsmall
functionsthattakeinputtooutput.Onecan'tcomposeawhileloop,forinstance.Bewarned,
however,pointfreeisadouble-edgedswordandcansometimesobfuscateintention.Notall
functionalcodeispointfreeandthatisO.K.We'llshootforitwherewecanandstickwith
normalfunctionsotherwise.
Debugging
Acommonmistakeistocomposesomethinglike map,afunctionoftwoarguments,without
firstpartiallyapplyingit.
//wrong-weendupgivingangryanarrayandwepartiallyappliedmapwithwhoknows
what.
constlatin=compose(map,angry,reverse);
latin(['frog','eyes']);//error
//right-eachfunctionexpects1argument.
constlatin=compose(map(angry),reverse);
latin(['frog','eyes']);//['EYES!','FROG!'])
Ifyouarehavingtroubledebuggingacomposition,wecanusethishelpful,butimpuretrace
functiontoseewhat'sgoingon.
Chapter05:CodingbyComposing
36
consttrace=curry((tag,x)=>{
console.log(tag,x);
returnx;
});
constdasherize=compose(
join('-'),
toLower,
split(''),
replace(/\s{2,}/ig,''),
);
dasherize('Theworldisavampire');
//TypeError:Cannotreadproperty'apply'ofundefined
Somethingiswronghere,let's trace
constdasherize=compose(
join('-'),
toLower,
trace('aftersplit'),
split(''),
replace(/\s{2,}/ig,''),
);
dasherize('Theworldisavampire');
//aftersplit['The','world','is','a','vampire']
Ah!Weneedto mapthis toLowersinceit'sworkingonanarray.
constdasherize=compose(
join('-'),
map(toLower),
split(''),
replace(/\s{2,}/ig,''),
);
dasherize('Theworldisavampire');//'the-world-is-a-vampire'
The tracefunctionallowsustoviewthedataatacertainpointfordebuggingpurposes.
LanguageslikeHaskellandPureScripthavesimilarfunctionsforeaseofdevelopment.
Compositionwillbeourtoolforconstructingprogramsand,asluckwouldhaveit,isbacked
byapowerfultheorythatensuresthingswillworkoutforus.Let'sexaminethistheory.
CategoryTheory
Chapter05:CodingbyComposing
37
Categorytheoryisanabstractbranchofmathematicsthatcanformalizeconceptsfrom
severaldifferentbranchessuchassettheory,typetheory,grouptheory,logic,andmore.It
primarilydealswithobjects,morphisms,andtransformations,whichmirrorsprogramming
quiteclosely.Hereisachartofthesameconceptsasviewedfromeachseparatetheory.
Sorry,Ididn'tmeantofrightenyou.Idon'texpectyoutobeintimatelyfamiliarwithallthese
concepts.Mypointistoshowyouhowmuchduplicationwehavesoyoucanseewhy
categorytheoryaimstounifythesethings.
Incategorytheory,wehavesomethingcalled...acategory.Itisdefinedasacollectionwith
thefollowingcomponents:
Acollectionofobjects
Acollectionofmorphisms
Anotionofcompositiononthemorphisms
Adistinguishedmorphismcalledidentity
Categorytheoryisabstractenoughtomodelmanythings,butlet'sapplythistotypesand
functions,whichiswhatwecareaboutatthemoment.
AcollectionofobjectsTheobjectswillbedatatypes.Forinstance, String, Boolean,
Number, Object,etc.Weoftenviewdatatypesassetsofallthepossiblevalues.One
couldlookat Booleanasthesetof [true,false]and Numberasthesetofallpossible
numericvalues.Treatingtypesassetsisusefulbecausewecanusesettheorytoworkwith
them.
AcollectionofmorphismsThemorphismswillbeourstandardeverydaypurefunctions.
Chapter05:CodingbyComposing
38
AnotionofcompositiononthemorphismsThis,asyoumayhaveguessed,isourbrand
newtoy- compose.We'vediscussedthatour composefunctionisassociativewhichisno
coincidenceasitisapropertythatmustholdforanycompositionincategorytheory.
Hereisanimagedemonstratingcomposition:
Hereisaconcreteexampleincode:
constg=x=>x.length;
constf=x=>x===4;
constisFourLetterWord=compose(f,g);
AdistinguishedmorphismcalledidentityLet'sintroduceausefulfunctioncalled id.
Thisfunctionsimplytakessomeinputandspitsitbackatyou.Takealook:
constid=x=>x;
Youmightaskyourself"Whatinthebloodyhellisthatusefulfor?".We'llmakeextensiveuse
ofthisfunctioninthefollowingchapters,butfornowthinkofitasafunctionthatcanstandin
forourvalue-afunctionmasqueradingaseverydaydata.
idmustplaynicelywithcompose.Hereisapropertythatalwaysholdsforeveryunary
(unary:aone-argumentfunction)functionf:
//identity
compose(id,f)===compose(f,id)===f;
//true
Chapter05:CodingbyComposing
39
Hey,it'sjustliketheidentitypropertyonnumbers!Ifthat'snotimmediatelyclear,takesome
timewithit.Understandthefutility.We'llbeseeing idusedallovertheplacesoon,butfor
nowweseeit'safunctionthatactsasastandinforagivenvalue.Thisisquiteusefulwhen
writingpointfreecode.
Sothereyouhaveit,acategoryoftypesandfunctions.Ifthisisyourfirstintroduction,I
imagineyou'restillalittlefuzzyonwhatacategoryisandwhyit'suseful.Wewillbuildupon
thisknowledgethroughoutthebook.Asofrightnow,inthischapter,onthisline,youcanat
leastseeitasprovidinguswithsomewisdomregardingcomposition-namely,the
associativityandidentityproperties.
Whataresomeothercategories,youask?Well,wecandefineonefordirectedgraphswith
nodesbeingobjects,edgesbeingmorphisms,andcompositionjustbeingpath
concatenation.WecandefinewithNumbersasobjectsand >=asmorphisms(actuallyany
partialortotalordercanbeacategory).Thereareheapsofcategories,butforthepurposes
ofthisbook,we'llonlyconcernourselveswiththeonedefinedabove.Wehavesufficiently
skimmedthesurfaceandmustmoveon.
InSummary
Compositionconnectsourfunctionstogetherlikeaseriesofpipes.Datawillflowthroughour
applicationasitmust-purefunctionsareinputtooutputafterall,sobreakingthischain
woulddisregardoutput,renderingoursoftwareuseless.
Weholdcompositionasadesignprincipleaboveallothers.Thisisbecauseitkeepsourapp
simpleandreasonable.Categorytheorywillplayabigpartinapparchitecture,modelling
sideeffects,andensuringcorrectness.
Wearenowatapointwhereitwouldserveuswelltoseesomeofthisinpractice.Let's
makeanexampleapplication.
Chapter06:ExampleApplication
Exercises
Ineachfollowingexercise,we'llconsiderCarobjectswiththefollowingshape:
Chapter05:CodingbyComposing
40
{
name:'AstonMartinOne-77',
horsepower:750,
dollar_value:1850000,
in_stock:true,
}
Exercise
Use`compose()`torewritethefunctionbelow.
//isLastInStock::[Car]->Boolean
constisLastInStock=(cars)=>{
constlastCar=last(cars);
returnprop('in_stock',lastCar);
};
Consideringthefollowingfunction:
constaverage=xs=>reduce(add,0,xs)/xs.length;
Exercise
Usethehelperfunction`average`torefactor`averageDollarValue`asacomposition.
//averageDollarValue::[Car]->Int
constaverageDollarValue=(cars)=>{
constdollarValues=map(c=>c.dollar_value,cars);
returnaverage(dollarValues);
};
Exercise
Refactor`fastestCar`using`compose()`andotherfunctionsinpointfree-style.Hint,the
`flip`functionmaycomeinhandy.
Chapter05:CodingbyComposing
41
//fastestCar::[Car]->String
constfastestCar=(cars)=>{
constsorted=sortBy(car=>car.horsepower,cars);
constfastest=last(sorted);
returnconcat(fastest.name,'isthefastest');
};
Chapter05:CodingbyComposing
42
Chapter06:ExampleApplication
DeclarativeCoding
Wearegoingtoswitchourmindset.Fromhereonout,we'llstoptellingthecomputerhowto
doitsjobandinsteadwriteaspecificationofwhatwe'dlikeasaresult.I'msureyou'llfindit
muchlessstressfulthantryingtomicromanageeverythingallthetime.
Declarative,asopposedtoimperative,meansthatwewillwriteexpressions,asopposedto
stepbystepinstructions.
ThinkofSQL.Thereisno"firstdothis,thendothat".Thereisoneexpressionthatspecifies
whatwe'dlikefromthedatabase.Wedon'tdecidehowtodothework,itdoes.Whenthe
databaseisupgradedandtheSQLengineoptimized,wedon'thavetochangeourquery.
Thisisbecausetherearemanywaystointerpretourspecificationandachievethesame
result.
Forsomefolks,myselfincluded,it'shardtograsptheconceptofdeclarativecodingatfirst
solet'spointoutafewexamplestogetafeelforit.
//imperative
constmakes=[];
for(leti=0;i<cars.length;i+=1){
makes.push(cars[i].make);
}
//declarative
constmakes=cars.map(car=>car.make);
Theimperativeloopmustfirstinstantiatethearray.Theinterpretermustevaluatethis
statementbeforemovingon.Thenitdirectlyiteratesthroughthelistofcars,manually
increasingacounterandshowingitsbitsandpiecestousinavulgardisplayofexplicit
iteration.
The mapversionisoneexpression.Itdoesnotrequireanyorderofevaluation.Thereis
muchfreedomhereforhowthemapfunctioniteratesandhowthereturnedarraymaybe
assembled.Itspecifieswhat,nothow.Thus,itwearstheshinydeclarativesash.
Inadditiontobeingclearerandmoreconcise,themapfunctionmaybeoptimizedatwilland
ourpreciousapplicationcodeneedn'tchange.
Chapter06:ExampleApplication
43
Forthoseofyouwhoarethinking"Yes,butit'smuchfastertodotheimperativeloop",I
suggestyoueducateyourselfonhowtheJIToptimizesyourcode.Here'saterrificvideothat
mayshedsomelight
Hereisanotherexample.
//imperative
constauthenticate=(form)=>{
constuser=toUser(form);
returnlogIn(user);
};
//declarative
constauthenticate=compose(logIn,toUser);
Thoughthere'snothingnecessarilywrongwiththeimperativeversion,thereisstillan
encodedstep-by-stepevaluationbakedin.The composeexpressionsimplystatesafact:
Authenticationisthecompositionof toUserand logIn.Again,thisleaveswiggleroomfor
supportcodechangesandresultsinourapplicationcodebeingahighlevelspecification.
Intheexampleabove,theorderofevaluationisspecified( toUsermustbecalledbefore
logIn),buttherearemanyscenarioswheretheorderisnotimportant,andthisiseasily
specifiedwithdeclarativecoding(moreonthislater).
Becausewedon'thavetoencodetheorderofevaluation,declarativecodinglendsitselfto
parallelcomputing.ThiscoupledwithpurefunctionsiswhyFPisagoodoptionforthe
parallelfuture-wedon'treallyneedtodoanythingspecialtoachieveparallel/concurrent
systems.
AFlickrofFunctionalProgramming
Wewillnowbuildanexampleapplicationinadeclarative,composableway.We'llstillcheat
andusesideeffectsfornow,butwe'llkeepthemminimalandseparatefromourpure
codebase.Wearegoingtobuildabrowserwidgetthatsucksinflickrimagesanddisplays
them.Let'sstartbyscaffoldingtheapp.Here'sthehtml:
Chapter06:ExampleApplication
44
<!doctypehtml>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>FlickrApp</title>
</head>
<body>
<mainid="js-main"class="main"></main>
<scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.2.0/require.min.j
s"></script>
<scriptsrc="main.js"></script>
</body>
</html>
Andhere'sthemain.jsskeleton:
constCDN=s=>`https://cdnjs.cloudflare.com/ajax/libs/${s}`;
constramda=CDN('ramda/0.21.0/ramda.min');
constjquery=CDN('jquery/3.0.0-rc1/jquery.min');
requirejs.config({paths:{ramda,jquery}});
require(['jquery','ramda'],($,{compose,curry,map,prop})=>{
//appgoeshere
});
We'repullinginramdainsteadoflodashorsomeotherutilitylibrary.Itincludes compose,
curry,andmore.I'veusedrequirejs,whichmayseemlikeoverkill,butwe'llbeusingit
throughoutthebookandconsistencyiskey.
Nowthatthat'soutoftheway,ontothespec.Ourappwilldo4things.
1. Constructaurlforourparticularsearchterm
2. Maketheflickrapicall
3. Transformtheresultingjsonintohtmlimages
4. Placethemonthescreen
Thereare2impureactionsmentionedabove.Doyouseethem?Thosebitsaboutgetting
datafromtheflickrapiandplacingitonthescreen.Let'sdefinethosefirstsowecan
quarantinethem.Also,I'lladdournice tracefunctionforeasydebugging.
constImpure={
getJSON:curry((callback,url)=>$.getJSON(url,callback)),
setHtml:curry((sel,html)=>$(sel).html(html)),
trace:curry((tag,x)=>{console.log(tag,x);returnx;}),
};
Chapter06:ExampleApplication
45
Herewe'vesimplywrappedjQuery'smethodstobecurriedandwe'veswappedthe
argumentstoamorefavorableposition.I'venamespacedthemwith Impuresoweknow
thesearedangerousfunctions.Inafutureexample,wewillmakethesetwofunctionspure.
Nextwemustconstructaurltopasstoour Impure.getJSONfunction.
consthost='api.flickr.com';
constpath='/services/feeds/photos_public.gne';
constquery=t=>`?tags=${t}&format=json&jsoncallback=?`;
consturl=t=>`https://${host}${path}${query(t)}`;
Therearefancyandoverlycomplexwaysofwriting urlpointfreeusingmonoids(we'lllearn
abouttheselater)orcombinators.We'vechosentostickwithareadableversionand
assemblethisstringinthenormalpointfulfashion.
Let'swriteanappfunctionthatmakesthecallandplacesthecontentsonthescreen.
constapp=compose(Impure.getJSON(Impure.trace('response')),url);
app('cats');
Thiscallsour urlfunction,thenpassesthestringtoour getJSONfunction,whichhas
beenpartiallyappliedwith trace.Loadingtheappwillshowtheresponsefromtheapicall
intheconsole.
We'dliketoconstructimagesoutofthisjson.Itlookslikethe mediaUrlsareburiedin
itemstheneach media's mproperty.
Chapter06:ExampleApplication
46
Anyhow,togetatthesenestedpropertieswecanuseaniceuniversalgetterfunctionfrom
ramdacalled prop.Here'sahomegrownversionsoyoucanseewhat'shappening:
constprop=curry((property,object)=>object[property]);
It'squitedullactually.Wejustuse []syntaxtoaccessapropertyonwhateverobject.Let's
usethistogetatour mediaUrls.
constmediaUrl=compose(prop('m'),prop('media'));
constmediaUrls=compose(map(mediaUrl),prop('items'));
Oncewegatherthe items,wemust mapoverthemtoextracteachmediaurl.Thisresults
inanicearrayof mediaUrls.Let'shookthisuptoourappandprintthemonthescreen.
constrender=compose(Impure.setHtml('#js-main'),mediaUrls);
constapp=compose(Impure.getJSON(render),url);
Allwe'vedoneismakeanewcompositionthatwillcallour mediaUrlsandsetthe <main>
htmlwiththem.We'vereplacedthe tracecallwith rendernowthatwehavesomethingto
renderbesidesrawjson.Thiswillcrudelydisplayour mediaUrlswithinthebody.
Ourfinalstepistoturnthese mediaUrlsintobonafide images.Inabiggerapplication,we'd
useatemplate/domlibrarylikeHandlebarsorReact.Forthisapplicationthough,weonly
needanimgtagsolet'sstickwithjQuery.
constimg=src=>$('<img/>',{src});
jQuery's htmlmethodwillacceptanarrayoftags.Weonlyhavetotransformour
mediaUrlsintoimagesandsendthemalongto setHtml.
constimages=compose(map(img),mediaUrls);
constrender=compose(Impure.setHtml('#js-main'),images);
constapp=compose(Impure.getJSON(render),url);
Andwe'redone!
Chapter06:ExampleApplication
47
Hereisthefinishedscript:
Chapter06:ExampleApplication
48
constCDN=s=>`https://cdnjs.cloudflare.com/ajax/libs/${s}`;
constramda=CDN('ramda/0.21.0/ramda.min');
constjquery=CDN('jquery/3.0.0-rc1/jquery.min');
requirejs.config({paths:{ramda,jquery}});
require(['jquery','ramda'],($,{compose,curry,map,prop})=>{
//--Utils----------------------------------------------------------
constImpure={
trace:curry((tag,x)=>{console.log(tag,x);returnx;}),//eslint-disable-li
neno-console
getJSON:curry((callback,url)=>$.getJSON(url,callback)),
setHtml:curry((sel,html)=>$(sel).html(html)),
};
//--Pure-----------------------------------------------------------
consthost='api.flickr.com';
constpath='/services/feeds/photos_public.gne';
constquery=t=>`?tags=${t}&format=json&jsoncallback=?`;
consturl=t=>`https://${host}${path}${query(t)}`;
constimg=src=>$('<img/>',{src});
constmediaUrl=compose(prop('m'),prop('media'));
constmediaUrls=compose(map(mediaUrl),prop('items'));
constimages=compose(map(img),mediaUrls);
//--Impure---------------------------------------------------------
constrender=compose(Impure.setHtml('#js-main'),images);
constapp=compose(Impure.getJSON(render),url);
app('cats');
});
Nowlookatthat.Abeautifullydeclarativespecificationofwhatthingsare,nothowthey
cometobe.Wenowvieweachlineasanequationwithpropertiesthathold.Wecanuse
thesepropertiestoreasonaboutourapplicationandrefactor.
APrincipledRefactor
Thereisanoptimizationavailable-wemapovereachitemtoturnitintoamediaurl,then
wemapagainoverthosemediaUrlstoturnthemintoimgtags.Thereisalawregarding
mapandcomposition:
//map'scompositionlaw
compose(map(f),map(g))===map(compose(f,g));
Wecanusethispropertytooptimizeourcode.Let'shaveaprincipledrefactor.
Chapter06:ExampleApplication
49
//originalcode
constmediaUrl=compose(prop('m'),prop('media'));
constmediaUrls=compose(map(mediaUrl),prop('items'));
constimages=compose(map(img),mediaUrls);
Let'slineupourmaps.Wecaninlinethecallto mediaUrlsin imagesthankstoequational
reasoningandpurity.
constmediaUrl=compose(prop('m'),prop('media'));
constimages=compose(map(img),map(mediaUrl),prop('items'));
Nowthatwe'velinedupour mapswecanapplythecompositionlaw.
/*
compose(map(f),map(g))===map(compose(f,g));
compose(map(img),map(mediaUrl))===map(compose(img,mediaUrl));
*/
constmediaUrl=compose(prop('m'),prop('media'));
constimages=compose(map(compose(img,mediaUrl)),prop('items'));
Nowthebuggerwillonlylooponcewhileturningeachitemintoanimg.Let'sjustmakeita
littlemorereadablebyextractingthefunctionout.
constmediaUrl=compose(prop('m'),prop('media'));
constmediaToImg=compose(img,mediaUrl);
constimages=compose(map(mediaToImg),prop('items'));
InSummary
Wehaveseenhowtoputournewskillsintousewithasmall,butrealworldapp.We've
usedourmathematicalframeworktoreasonaboutandrefactorourcode.Butwhatabout
errorhandlingandcodebranching?Howcanwemakethewholeapplicationpureinsteadof
merelynamespacingdestructivefunctions?Howcanwemakeourappsaferandmore
expressive?Thesearethequestionswewilltackleinpart2.
Chapter07:Hindley-MilnerandMe
Chapter06:ExampleApplication
50
Chapter07:Hindley-MilnerandMe
What'sYourType?
Ifyou'renewtothefunctionalworld,itwon'tbelongbeforeyoufindyourselfkneedeepin
typesignatures.Typesarethemetalanguagethatenablespeoplefromalldifferent
backgroundstocommunicatesuccinctlyandeffectively.Forthemostpart,they'rewritten
withasystemcalled"Hindley-Milner",whichwe'llbeexaminingtogetherinthischapter.
Whenworkingwithpurefunctions,typesignatureshaveanexpressivepowertowhichthe
Englishlanguagecannotholdacandle.Thesesignatureswhisperinyoureartheintimate
secretsofafunction.Inasingle,compactline,theyexposebehaviourandintention.Wecan
derive"freetheorems"fromthem.Typescanbeinferredsothere'snoneedforexplicittype
annotations.Theycanbetunedtofinepointprecisionorleftgeneralandabstract.Theyare
notonlyusefulforcompiletimechecks,butalsoturnouttobethebestpossible
documentationavailable.Typesignaturesthusplayanimportantpartinfunctional
programming-muchmorethanyoumightfirstexpect.
JavaScriptisadynamiclanguage,butthatdoesnotmeanweavoidtypesalltogether.We're
stillworkingwithstrings,numbers,booleans,andsoon.It'sjustthatthereisn'tanylanguage
levelintegrationsoweholdthisinformationinourheads.Nottoworry,sincewe'reusing
signaturesfordocumentation,wecanusecommentstoserveourpurpose.
TherearetypecheckingtoolsavailableforJavaScriptsuchasFloworthetypeddialect,
TypeScript.Theaimofthisbookistoequiponewiththetoolstowritefunctionalcodeso
we'llstickwiththestandardtypesystemusedacrossFPlanguages.
TalesfromtheCryptic
Fromthedustypagesofmathbooks,acrossthevastseaofwhitepapers,amongstcasual
Saturdaymorningblogposts,downintothesourcecodeitself,wefindHindley-Milnertype
signatures.Thesystemisquitesimple,butwarrantsaquickexplanationandsomepractice
tofullyabsorbthelittlelanguage.
//capitalize::String->String
constcapitalize=s=>toUpperCase(head(s))+toLowerCase(tail(s));
capitalize('smurf');//'Smurf'
Chapter07:Hindley-MilnerandMe
51
Here, capitalizetakesa Stringandreturnsa String.Nevermindtheimplementation,
it'sthetypesignaturewe'reinterestedin.
InHM,functionsarewrittenas a->bwhere aand barevariablesforanytype.Sothe
signaturesfor capitalizecanbereadas"afunctionfrom Stringto String".Inother
words,ittakesa Stringasitsinputandreturnsa Stringasitsoutput.
Let'slookatsomemorefunctionsignatures:
//strLength::String->Number
conststrLength=s=>s.length;
//join::String->[String]->String
constjoin=curry((what,xs)=>xs.join(what));
//match::Regex->String->[String]
constmatch=curry((reg,s)=>s.match(reg));
//replace::Regex->String->String->String
constreplace=curry((reg,sub,s)=>s.replace(reg,sub));
strLengthisthesameideaasbefore:wetakea Stringandreturnyoua Number.
Theothersmightperplexyouatfirstglance.Withoutfullyunderstandingthedetails,you
couldalwaysjustviewthelasttypeasthereturnvalue.Sofor matchyoucaninterpretas:It
takesa Regexanda Stringandreturnsyou [String].Butaninterestingthingisgoing
onherethatI'dliketotakeamomenttoexplainifImay.
For matchwearefreetogroupthesignaturelikeso:
//match::Regex->(String->[String])
constmatch=curry((reg,s)=>s.match(reg));
Ahyes,groupingthelastpartinparenthesisrevealsmoreinformation.Nowitisseenasa
functionthattakesa Regexandreturnsusafunctionfrom Stringto [String].Because
ofcurrying,thisisindeedthecase:giveita Regexandwegetafunctionbackwaitingforits
Stringargument.Ofcourse,wedon'thavetothinkofitthisway,butitisgoodto
understandwhythelasttypeistheonereturned.
//match::Regex->(String->[String])
//onHoliday::String->[String]
constonHoliday=match(/holiday/ig);
Eachargumentpopsonetypeoffthefrontofthesignature. onHolidayis matchthat
alreadyhasa Regex.
Chapter07:Hindley-MilnerandMe
52
//replace::Regex->(String->(String->String))
constreplace=curry((reg,sub,s)=>s.replace(reg,sub));
Asyoucanseewiththefullparenthesison replace,theextranotationcangetalittlenoisy
andredundantsowesimplyomitthem.Wecangivealltheargumentsatonceifwechoose
soit'seasiertojustthinkofitas: replacetakesa Regex,a String,another Stringand
returnsyoua String.
Afewlastthingshere:
//id::a->a
constid=x=>x;
//map::(a->b)->[a]->[b]
constmap=curry((f,xs)=>xs.map(f));
The idfunctiontakesanyoldtype aandreturnssomethingofthesametype a.We're
abletousevariablesintypesjustlikeincode.Variablenameslike aand bare
convention,buttheyarearbitraryandcanbereplacedwithwhatevernameyou'dlike.Ifthey
arethesamevariable,theyhavetobethesametype.That'sanimportantrulesolet's
reiterate: a->bcanbeanytype atoanytype b,but a->ameansithastobethe
sametype.Forexample, idmaybe String->Stringor Number->Number,butnot
String->Bool.
mapsimilarlyusestypevariables,butthistimeweintroduce bwhichmayormaynotbe
thesametypeas a.Wecanreaditas: maptakesafunctionfromanytype atothe
sameordifferenttype b,thentakesanarrayof a'sandresultsinanarrayof b's.
Hopefully,you'vebeenovercomebytheexpressivebeautyinthistypesignature.Itliterally
tellsuswhatthefunctiondoesalmostwordforword.It'sgivenafunctionfrom ato b,an
arrayof a,anditdeliversusanarrayof b.Theonlysensiblethingforittodoiscallthe
bloodyfunctiononeach a.Anythingelsewouldbeaboldfacelie.
Beingabletoreasonabouttypesandtheirimplicationsisaskillthatwilltakeyoufarinthe
functionalworld.Notonlywillpapers,blogs,docs,etc,becomemoredigestible,butthe
signatureitselfwillpracticallylectureyouonitsfunctionality.Ittakespracticetobecomea
fluentreader,butifyoustickwithit,heapsofinformationwillbecomeavailabletoyousans
RTFMing.
Here'safewmorejusttoseeifyoucandecipherthemonyourown.
Chapter07:Hindley-MilnerandMe
53
//head::[a]->a
consthead=xs=>xs[0];
//filter::(a->Bool)->[a]->[a]
constfilter=curry((f,xs)=>xs.filter(f));
//reduce::(b->a->b)->b->[a]->b
constreduce=curry((f,x,xs)=>xs.reduce(f,x));
reduceisperhaps,themostexpressiveofall.It'satrickyone,however,sodon'tfeel
inadequateshouldyoustrugglewithit.Forthecurious,I'lltrytoexplaininEnglishthough
workingthroughthesignatureonyourownismuchmoreinstructive.
Ahem,heregoesnothing....lookingatthesignature,weseethefirstargumentisafunction
thatexpectsa b,an a,andproducesa b.Wheremightitgetthese asand bs?Well,
thefollowingargumentsinthesignaturearea bandanarrayof assowecanonly
assumethatthe bandeachofthose aswillbefedin.Wealsoseethattheresultofthe
functionisa bsothethinkinghereisourfinalincantationofthepassedinfunctionwillbe
ouroutputvalue.Knowingwhatreducedoes,wecanstatethattheaboveinvestigationis
accurate.
NarrowingthePossibility
Onceatypevariableisintroduced,thereemergesacuriouspropertycalledparametricity.
Thispropertystatesthatafunctionwillactonalltypesinauniformmanner.Let's
investigate:
//head::[a]->a
Lookingat head,weseethatittakes [a]to a.Besidestheconcretetype array,ithas
nootherinformationavailableand,therefore,itsfunctionalityislimitedtoworkingonthe
arrayalone.Whatcoulditpossiblydowiththevariable aifitknowsnothingaboutit?In
otherwords, asaysitcannotbeaspecifictype,whichmeansitcanbeanytype,which
leavesuswithafunctionthatmustworkuniformlyforeveryconceivabletype.Thisiswhat
parametricityisallabout.Guessingattheimplementation,theonlyreasonableassumptions
arethatittakesthefirst,last,orarandomelementfromthatarray.Thename headshould
tipusoff.
Here'sanotherone:
//reverse::[a]->[a]
Chapter07:Hindley-MilnerandMe
54
Fromthetypesignaturealone,whatcould reversepossiblybeupto?Again,itcannotdo
anythingspecificto a.Itcannotchange atoadifferenttypeorwe'dintroducea b.Can
itsort?Well,no,itwouldn'thaveenoughinformationtosorteverypossibletype.Canitre-
arrange?Yes,Isupposeitcandothat,butithastodosoinexactlythesamepredictable
way.Anotherpossibilityisthatitmaydecidetoremoveorduplicateanelement.Inanycase,
thepointis,thepossiblebehaviourismassivelynarrowedbyitspolymorphictype.
ThisnarrowingofpossibilityallowsustousetypesignaturesearchengineslikeHoogleto
findafunctionwe'reafter.Theinformationpackedtightlyintoasignatureisquitepowerful
indeed.
FreeasinTheorem
Besidesdeducingimplementationpossibilities,thissortofreasoninggainsusfreetheorems.
WhatfollowsareafewrandomexampletheoremslifteddirectlyfromWadler'spaperonthe
subject.
//head::[a]->a
compose(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.Thefirst
onesaysthatifwegetthe headofourarray,thenrunsomefunction fonit,thatis
equivalentto,andincidentally,muchfasterthan,ifwefirst map(f)overeveryelementthen
takethe headoftheresult.
Youmightthink,wellthat'sjustcommonsense.ButlastIchecked,computersdon'thave
commonsense.Indeed,theymusthaveaformalwaytoautomatethesekindofcode
optimizations.Mathshasawayofformalizingtheintuitive,whichishelpfulamidsttherigid
terrainofcomputerlogic.
The filtertheoremissimilar.Itsaysthatifwecompose fand ptocheckwhichshould
befiltered,thenactuallyapplythe fvia map(rememberfilter,willnottransformthe
elements-itssignatureenforcesthat awillnotbetouched),itwillalwaysbeequivalentto
mappingour fthenfilteringtheresultwiththe ppredicate.
Thesearejusttwoexamples,butyoucanapplythisreasoningtoanypolymorphictype
signatureanditwillalwayshold.InJavaScript,therearesometoolsavailabletodeclare
rewriterules.Onemightalsodothisviathe composefunctionitself.Thefruitislowhanging
andthepossibilitiesareendless.
Chapter07:Hindley-MilnerandMe
55
Constraints
Onelastthingtonoteisthatwecanconstraintypestoaninterface.
//sort::Orda=>[a]->[a]
Whatweseeontheleftsideofourfatarrowhereisthestatementofafact: amustbean
Ord.Orinotherwords, amustimplementthe Ordinterface.Whatis Ordandwhere
diditcomefrom?Inatypedlanguageitwouldbeadefinedinterfacethatsayswecanorder
thevalues.Thisnotonlytellsusmoreaboutthe aandwhatour sortfunctionisupto,
butalsorestrictsthedomain.Wecalltheseinterfacedeclarationstypeconstraints.
//assertEqual::(Eqa,Showa)=>a->a->Assertion
Here,wehavetwoconstraints: Eqand Show.Thosewillensurethatwecancheck
equalityofour asandprintthedifferenceiftheyarenotequal.
We'llseemoreexamplesofconstraintsandtheideashouldtakemoreshapeinlater
chapters.
InSummary
Hindley-Milnertypesignaturesareubiquitousinthefunctionalworld.Thoughtheyaresimple
toreadandwrite,ittakestimetomasterthetechniqueofunderstandingprogramsthrough
signaturesalone.Wewilladdtypesignaturestoeachlineofcodefromhereonout.
Chapter08:Tupperware
Chapter07:Hindley-MilnerandMe
56
Chapter08:Tupperware
TheMightyContainer
We'veseenhowtowriteprogramswhichpipedatathroughaseriesofpurefunctions.They
aredeclarativespecificationsofbehaviour.Butwhataboutcontrolflow,errorhandling,
asynchronousactions,stateand,dareIsay,effects?!Inthischapter,wewilldiscoverthe
foundationuponwhichallofthesehelpfulabstractionsarebuilt.
Firstwewillcreateacontainer.Thiscontainermustholdanytypeofvalue;aziplockthat
holdsonlytapiocapuddingisrarelyuseful.Itwillbeanobject,butwewillnotgiveit
propertiesandmethodsintheOOsense.No,wewilltreatitlikeatreasurechest-aspecial
boxthatcradlesourvaluabledata.
Chapter08:Tupperware
57
classContainer{
constructor(x){
this.$value=x;
}
staticof(x){
returnnewContainer(x);
}
}
Hereisourfirstcontainer.We'vethoughtfullynamedit Container.Wewilluse
Container.ofasaconstructorwhichsavesusfromhavingtowritethatawful newkeyword
allovertheplace.There'smoretothe offunctionthanmeetstheeye,butfornow,thinkof
itastheproperwaytoplacevaluesintoourcontainer.
Let'sexamineourbrandnewbox...
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'vegotourselvesa
Container(x).Chromewilloutputthetypeproperly,butnomatter;aslongaswe
understandwhata Containerlookslike,we'llbefine.Insomeenvironmentsyoucan
overwritethe inspectmethodifyou'dlike,butwewillnotbesothorough.Forthisbook,we
willwritetheconceptualoutputasifwe'doverwritten inspectasit'smuchmoreinstructive
than {$value:x}forpedagogicalaswellasaestheticreasons.
Let'smakeafewthingsclearbeforewemoveon:
Containerisanobjectwithoneproperty.Lotsofcontainersjustholdonething,though
theyaren'tlimitedtoone.We'vearbitrarilynameditsproperty $value.
The $valuecannotbeonespecifictypeorour Containerwouldhardlyliveuptothe
name.
Oncedatagoesintothe Containeritstaysthere.Wecouldgetitoutbyusing
.$value,butthatwoulddefeatthepurpose.
Thereasonswe'redoingthiswillbecomeclearasamasonjar,butfornow,bearwithme.
Chapter08:Tupperware
58
MyFirstFunctor
Onceourvalue,whateveritmaybe,isinthecontainer,we'llneedawaytorunfunctionson
it.
//(a->b)->Containera->Containerb
Container.prototype.map=function(f){
returnContainer.of(f(this.$value));
};
Why,it'sjustlikeArray'sfamous map,exceptwehave Containerainsteadof [a].Andit
worksessentiallythesameway:
Container.of(2).map(two=>two+2);
//Container(4)
Container.of('flamethrowers').map(s=>s.toUpperCase());
//Container('FLAMETHROWERS')
Container.of('bombs').map(concat('away')).map(prop('length'));
//Container(10)
Wecanworkwithourvaluewithouteverhavingtoleavethe Container.Thisisa
remarkablething.Ourvalueinthe Containerishandedtothe mapfunctionsowecanfuss
withitandafterward,returnedtoits Containerforsafekeeping.Asaresultofneverleaving
the Container,wecancontinueto mapaway,runningfunctionsasweplease.Wecan
evenchangethetypeaswegoalongasdemonstratedinthelatterofthethreeexamples.
Waitaminute,ifwekeepcalling map,itappearstobesomesortofcomposition!What
mathematicalmagicisatworkhere?Wellchaps,we'vejustdiscoveredFunctors.
AFunctorisatypethatimplements mapandobeyssomelaws
Yes,Functorissimplyaninterfacewithacontract.Wecouldhavejustaseasilynamedit
Mappable,butnow,where'sthefuninthat?Functorscomefromcategorytheoryandwe'll
lookatthemathsindetailtowardtheendofthechapter,butfornow,let'sworkonintuition
andpracticalusesforthisbizarrelynamedinterface.
Whatreasoncouldwepossiblyhaveforbottlingupavalueandusing maptogetatit?The
answerrevealsitselfifwechooseabetterquestion:Whatdowegainfromaskingour
containertoapplyfunctionsforus?Well,abstractionoffunctionapplication.Whenwe map
afunction,weaskthecontainertypetorunitforus.Thisisaverypowerfulconcept,indeed.
Chapter08:Tupperware
59
Schrödinger'sMaybe
Containerisfairlyboring.Infact,itisusuallycalled Identityandhasaboutthesame
impactasour idfunction(againthereisamathematicalconnectionwe'lllookatwhenthe
timeisright).However,thereareotherfunctors,thatis,container-liketypesthathavea
proper mapfunction,whichcanprovideusefulbehaviourwhilstmapping.Let'sdefineone
now.
AcompleteimplementationisgivenintheAppendixB
classMaybe{
staticof(x){
returnnewMaybe(x);
}
getisNothing(){
returnthis.$value===null||this.$value===undefined;
}
constructor(x){
this.$value=x;
}
map(fn){
returnthis.isNothing?this:Maybe.of(fn(this.$value));
}
inspect(){
returnthis.isNothing?'Nothing':`Just(${inspect(this.$value)})`;
}
}
Now, Maybelooksalotlike Containerwithoneminorchange:itwillfirstchecktoseeifit
hasavaluebeforecallingthesuppliedfunction.Thishastheeffectofsidesteppingthose
peskynullsaswe map(Notethatthisimplementationissimpliedforteaching).
Chapter08:Tupperware
60
Maybe.of('MalkovichMalkovich').map(match(/a/ig));
//Just(['a','a'])
Maybe.of(null).map(match(/a/ig));
//Nothing
Maybe.of({name:'Boris'}).map(prop('age')).map(add(10));
//Nothing
Maybe.of({name:'Dinah',age:14}).map(prop('age')).map(add(10));
//Just(24)
Noticeourappdoesn'texplodewitherrorsaswemapfunctionsoverournullvalues.Thisis
because Maybewilltakecaretocheckforavalueeachandeverytimeitappliesafunction.
Thisdotsyntaxisperfectlyfineandfunctional,butforreasonsmentionedinPart1,we'dlike
tomaintainourpointfreestyle.Asithappens, mapisfullyequippedtodelegatetowhatever
functoritreceives:
//map::Functorf=>(a->b)->fa->fb
constmap=curry((f,anyFunctor)=>anyFunctor.map(f));
Thisisdelightfulaswecancarryonwithcompositionperusualand mapwillworkas
expected.Thisisthecasewithramda's mapaswell.We'llusedotnotationwhenit's
instructiveandthepointfreeversionwhenit'sconvenient.Didyounoticethat?I'vesneakily
introducedextranotationintoourtypesignature.The Functorf=>tellsusthat fmustbe
aFunctor.Notthatdifficult,butIfeltIshouldmentionit.
UseCases
Inthewild,we'lltypicallysee Maybeusedinfunctionswhichmightfailtoreturnaresult.
//safeHead::[a]->Maybe(a)
constsafeHead=xs=>Maybe.of(xs[0]);
//streetName::Object->MaybeString
conststreetName=compose(map(prop('street')),safeHead,prop('addresses'));
streetName({addresses:[]});
//Nothing
streetName({addresses:[{street:'ShadyLn.',number:4201}]});
//Just('ShadyLn.')
Chapter08:Tupperware
61
safeHeadislikeournormal head,butwithaddedtypesafety.Acuriousthinghappens
when Maybeisintroducedintoourcode;weareforcedtodealwiththosesneaky null
values.The safeHeadfunctionishonestandupfrontaboutitspossiblefailure-there's
reallynothingtobeashamedof-andsoitreturnsa Maybetoinformusofthismatter.We
aremorethanmerelyinformed,however,becauseweareforcedto maptogetatthevalue
wewantsinceitistuckedawayinsidethe Maybeobject.Essentially,thisisa nullcheck
enforcedbythe safeHeadfunctionitself.Wecannowsleepbetteratnightknowinga null
valuewon'trearitsugly,decapitatedheadwhenweleastexpectit.APIslikethiswill
upgradeaflimsyapplicationfrompaperandtackstowoodandnails.Theywillguarantee
safersoftware.
Sometimesafunctionmightreturna Nothingexplicitlytosignalfailure.Forinstance:
//withdraw::Number->Account->Maybe(Account)
constwithdraw=curry((amount,{balance})=>
Maybe.of(balance>=amount?{balance:balance-amount}:null));
//Thisfunctionishypothetical,notimplementedhere...noranywhereelse.
//updateLedger::Account->Account
constupdateLedger=account=>account;
//remainingBalance::Account->String
constremainingBalance=({balance})=>`Yourbalanceis$${balance}`;
//finishTransaction::Account->String
constfinishTransaction=compose(remainingBalance,updateLedger);
//getTwenty::Account->Maybe(String)
constgetTwenty=compose(map(finishTransaction),withdraw(20));
getTwenty({balance:200.00});
//Just('Yourbalanceis$180')
getTwenty({balance:10.00});
//Nothing
withdrawwilltipitsnoseatusandreturn Nothingifwe'reshortoncash.Thisfunctionalso
communicatesitsficklenessandleavesusnochoice,butto mapeverythingafterwards.
Thedifferenceisthatthe nullwasintentionalhere.Insteadofa Just('..'),wegetthe
Nothingbacktosignalfailureandourapplicationeffectivelyhaltsinitstracks.Thisis
importanttonote:ifthe withdrawfails,then mapwillsevertherestofourcomputation
sinceitdoesn'teverrunthemappedfunctions,namely finishTransaction.Thisisprecisely
theintendedbehaviouraswe'dprefernottoupdateourledgerorshowanewbalanceifwe
hadn'tsuccessfullywithdrawnfunds.
Chapter08:Tupperware
62
ReleasingtheValue
Onethingpeopleoftenmissisthattherewillalwaysbeanendoftheline;someeffecting
functionthatsendsJSONalong,orprintstothescreen,oraltersourfilesystem,orwhat
haveyou.Wecannotdelivertheoutputwith return,wemustrunsomefunctionoranother
tosenditoutintotheworld.WecanphraseitlikeaZenBuddhistkoan:"Ifaprogramhasno
observableeffect,doesitevenrun?".Doesitruncorrectlyforitsownsatisfaction?Isuspect
itmerelyburnssomecyclesandgoesbacktosleep...
Ourapplication'sjobistoretrieve,transform,andcarrythatdataalonguntilit'stimetosay
goodbyeandthefunctionwhichdoessomaybemapped,thusthevalueneedn'tleavethe
warmwombofitscontainer.Indeed,acommonerroristotrytoremovethevaluefromour
Maybeonewayoranotherasifthepossiblevalueinsidewillsuddenlymaterializeandall
willbeforgiven.Wemustunderstanditmaybeabranchofcodewhereourvalueisnot
aroundtoliveuptoitsdestiny.Ourcode,muchlikeSchrödinger'scat,isintwostatesat
onceandshouldmaintainthatfactuntilthefinalfunction.Thisgivesourcodealinearflow
despitethelogicalbranching.
Thereis,however,anescapehatch.Ifwewouldratherreturnacustomvalueandcontinue
on,wecanusealittlehelpercalled maybe.
//maybe::b->(a->b)->Maybea->b
constmaybe=curry((v,f,m)=>{
if(m.isNothing){
returnv;
}
returnf(m.$value);
});
//getTwenty::Account->String
constgetTwenty=compose(maybe('You\'rebroke!',finishTransaction),withdraw(20));
getTwenty({balance:200.00});
//'Yourbalanceis$180.00'
getTwenty({balance:10.00});
//'You\'rebroke!'
Wewillnoweitherreturnastaticvalue(ofthesametypethat finishTransactionreturns)or
continueonmerrilyfinishingupthetransactionsans Maybe.With maybe,wearewitnessing
theequivalentofan if/elsestatementwhereaswith map,theimperativeanalogwould
be: if(x!==null){returnf(x)}.
Chapter08:Tupperware
63
Theintroductionof Maybecancausesomeinitialdiscomfort.UsersofSwiftandScalawill
knowwhatImeanasit'sbakedrightintothecorelibrariesundertheguiseof Option(al).
Whenpushedtodealwith nullchecksallthetime(andtherearetimesweknowwith
absolutecertaintythevalueexists),mostpeoplecan'thelpbutfeelit'satadlaborious.
However,withtime,itwillbecomesecondnatureandyou'lllikelyappreciatethesafety.After
all,mostofthetimeitwillpreventcutcornersandsaveourhides.
Writingunsafesoftwareisliketakingcaretopainteacheggwithpastelsbeforehurlingitinto
traffic;likebuildingaretirementhomewithmaterialswarnedagainstbythreelittlepigs.Itwill
douswelltoputsomesafetyintoourfunctionsand Maybehelpsusdojustthat.
I'dberemissifIdidn'tmentionthatthe"real"implementationwillsplit Maybeintotwotypes:
oneforsomethingandtheotherfornothing.Thisallowsustoobeyparametricityin mapso
valueslike nulland undefinedcanstillbemappedoverandtheuniversalqualificationof
thevalueinafunctorwillberespected.You'lloftenseetypeslike Some(x)/Noneor
Just(x)/Nothinginsteadofa Maybethatdoesa nullcheckonitsvalue.
PureErrorHandling
Itmaycomeasashock,but throw/catchisnotverypure.Whenanerroristhrown,instead
ofreturninganoutputvalue,wesoundthealarms!Thefunctionattacks,spewingthousands
of0sand1slikeshieldsandspearsinanelectricbattleagainstourintrudinginput.Withour
newfriend Either,wecandobetterthantodeclarewaroninput,wecanrespondwitha
politemessage.Let'stakealook:
AcompleteimplementationisgivenintheAppendixB
Chapter08:Tupperware
64
classEither{
staticof(x){
returnnewRight(x);
}
constructor(x){
this.$value=x;
}
}
classLeftextendsEither{
map(f){
returnthis;
}
inspect(){
return`Left(${inspect(this.$value)})`;
}
}
classRightextendsEither{
map(f){
returnEither.of(f(this.$value));
}
inspect(){
return`Right(${inspect(this.$value)})`;
}
}
constleft=x=>newLeft(x);
Leftand Rightaretwosubclassesofanabstracttypewecall Either.I'veskippedthe
ceremonyofcreatingthe Eithersuperclassaswewon'teveruseit,butit'sgoodtobe
aware.Nowthen,there'snothingnewherebesidesthetwotypes.Let'sseehowtheyact:
Either.of('rain').map(str=>`b${str}`);
//Right('brain')
left('rain').map(str=>`It'sgonna${str},betterbringyourumbrella!`);
//Left('rain')
Either.of({host:'localhost',port:80}).map(prop('host'));
//Right('localhost')
left('rollseyes...').map(prop('host'));
//Left('rollseyes...')
Chapter08:Tupperware
65
Leftistheteenagerysortandignoresourrequestto mapoverit. Rightwillworkjustlike
Container(a.k.aIdentity).Thepowercomesfromtheabilitytoembedanerrormessage
withinthe Left.
Supposewehaveafunctionthatmightnotsucceed.Howaboutwecalculateanagefroma
birthdate.Wecoulduse Nothingtosignalfailureandbranchourprogram,however,that
doesn'ttellusmuch.Perhaps,we'dliketoknowwhyitfailed.Let'swritethisusing Either.
constmoment=require('moment');
//getAge::Date->User->Either(String,Number)
constgetAge=curry((now,user)=>{
constbirthDate=moment(user.birthDate,'YYYY-MM-DD');
returnbirthDate.isValid()
?Either.of(now.diff(birthDate,'years'))
:left('Birthdatecouldnotbeparsed');
});
getAge(moment(),{birthDate:'2005-12-12'});
//Right(9)
getAge(moment(),{birthDate:'July4,2001'});
//Left('Birthdatecouldnotbeparsed')
Now,justlike Nothing,weareshort-circuitingourappwhenwereturna Left.The
difference,isnowwehaveaclueastowhyourprogramhasderailed.Somethingtonotice
isthatwereturn Either(String,Number),whichholdsa Stringasitsleftvalueanda
Numberasits Right.Thistypesignatureisabitinformalaswehaven'ttakenthetimeto
defineanactual Eithersuperclass,however,welearnalotfromthetype.Itinformsusthat
we'reeithergettinganerrormessageortheageback.
//fortune::Number->String
constfortune=compose(concat('Ifyousurvive,youwillbe'),toString,add(1));
//zoltar::User->Either(String,_)
constzoltar=compose(map(console.log),map(fortune),getAge(moment()));
zoltar({birthDate:'2005-12-12'});
//'Ifyousurvive,youwillbe10'
//Right(undefined)
zoltar({birthDate:'balloons!'});
//Left('Birthdatecouldnotbeparsed')
Chapter08:Tupperware
66
Whenthe birthDateisvalid,theprogramoutputsitsmysticalfortunetothescreenforusto
behold.Otherwise,wearehandeda Leftwiththeerrormessageplainasdaythoughstill
tuckedawayinitscontainer.Thatactsjustasifwe'dthrownanerror,butinacalm,mild
mannerfashionasopposedtolosingitstemperandscreaminglikeachildwhensomething
goeswrong.
Inthisexample,wearelogicallybranchingourcontrolflowdependingonthevalidityofthe
birthdate,yetitreadsasonelinearmotionfromrighttoleftratherthanclimbingthroughthe
curlybracesofaconditionalstatement.Usually,we'dmovethe console.logoutofour
zoltarfunctionand mapitatthetimeofcalling,butit'shelpfultoseehowthe Right
branchdiffers.Weuse _intherightbranch'stypesignaturetoindicateit'savaluethat
shouldbeignored(Insomebrowsersyouhavetouse console.log.bind(console)touseit
firstclass).
I'dliketotakethisopportunitytopointoutsomethingyoumayhavemissed: fortune,
despiteitsusewith Eitherinthisexample,iscompletelyignorantofanyfunctorsmilling
about.Thiswasalsothecasewith finishTransactioninthepreviousexample.Atthetime
ofcalling,afunctioncanbesurroundedby map,whichtransformsitfromanon-functory
functiontoafunctoryone,ininformalterms.Wecallthisprocesslifting.Functionstendtobe
betteroffworkingwithnormaldatatypesratherthancontainertypes,thenliftedintotheright
containerasdeemednecessary.Thisleadstosimpler,morereusablefunctionsthatcanbe
alteredtoworkwithanyfunctorondemand.
Eitherisgreatforcasualerrorslikevalidationaswellasmoreserious,stoptheshow
errorslikemissingfilesorbrokensockets.Tryreplacingsomeofthe Maybeexampleswith
Eithertogivebetterfeedback.
Now,Ican'thelpbutfeelI'vedone Eitheradisservicebyintroducingitasmerelya
containerforerrormessages.Itcaptureslogicaldisjunction(a.k.a ||)inatype.Italso
encodestheideaofaCoproductfromcategorytheory,whichwon'tbetouchedoninthis
book,butiswellworthreadinguponasthere'spropertiestobeexploited.Itisthecanonical
sumtype(ordisjointunionofsets)becauseitsamountofpossibleinhabitantsisthesumof
thetwocontainedtypes(Iknowthat'sabithandwavysohere'sagreatarticle).Thereare
manythings Eithercanbe,butasafunctor,itisusedforitserrorhandling.
Justlikewith Maybe,wehavelittle either,whichbehavessimilarly,buttakestwo
functionsinsteadofoneandastaticvalue.Eachfunctionshouldreturnthesametype:
Chapter08:Tupperware
67
//either::(a->c)->(b->c)->Eitherab->c
consteither=curry((f,g,e)=>{
letresult;
switch(e.constructor){
caseLeft:
result=f(e.$value);
break;
caseRight:
result=g(e.$value);
break;
//NoDefault
}
returnresult;
});
//zoltar::User->_
constzoltar=compose(console.log,either(id,fortune),getAge(moment()));
zoltar({birthDate:'2005-12-12'});
//'Ifyousurvive,youwillbe10'
//undefined
zoltar({birthDate:'balloons!'});
//'Birthdatecouldnotbeparsed'
//undefined
Finally,auseforthatmysterious idfunction.Itsimplyparrotsbackthevalueinthe Left
topasstheerrormessageto console.log.We'vemadeourfortune-tellingappmorerobust
byenforcingerrorhandlingfromwithin getAge.Weeitherslaptheuserwithahardtruthlike
ahighfivefromapalmreaderorwecarryonwithourprocess.Andwiththat,we'rereadyto
moveontoanentirelydifferenttypeoffunctor.
OldMcDonaldHadEffects...
Chapter08:Tupperware
68
Inourchapteraboutpuritywesawapeculiarexampleofapurefunction.Thisfunction
containedaside-effect,butwedubbeditpurebywrappingitsactioninanotherfunction.
Here'sanotherexampleofthis:
//getFromStorage::String->(_->String)
constgetFromStorage=key=>()=>localStorage[key];
Hadwenotsurroundeditsgutsinanotherfunction, getFromStoragewouldvaryitsoutput
dependingonexternalcircumstance.Withthesturdywrapperinplace,wewillalwaysget
thesameoutputperinput:afunctionthat,whencalled,willretrieveaparticularitemfrom
localStorage.Andjustlikethat(maybethrowinafewHailMary's)we'veclearedour
conscienceandallisforgiven.
Except,thisisn'tparticularlyusefulnowisit.Likeacollectibleactionfigureinitsoriginal
packaging,wecan'tactuallyplaywithit.Ifonlytherewereawaytoreachinsideofthe
containerandgetatitscontents...Enter IO.
Chapter08:Tupperware
69
classIO{
staticof(x){
returnnewIO(()=>x);
}
constructor(fn){
this.$value=fn;
}
map(fn){
returnnewIO(compose(fn,this.$value));
}
inspect(){
return`IO(${inspect(this.$value)})`;
}
}
IOdiffersfromthepreviousfunctorsinthatthe $valueisalwaysafunction.Wedon'tthink
ofits $valueasafunction,however-thatisanimplementationdetailandwebestignoreit.
Whatishappeningisexactlywhatwesawwiththe getFromStorageexample: IOdelays
theimpureactionbycapturingitinafunctionwrapper.Assuch,wethinkof IOas
containingthereturnvalueofthewrappedactionandnotthewrapperitself.Thisisapparent
inthe offunction:wehavean IO(x),the IO(()=>x)isjustnecessarytoavoid
evaluation.Notethat,tosimplifyreading,we'llshowthehypotheticalvaluecontainedinthe
IOasresult;howeverinpractice,youcan'ttellwhatthisvalueisuntilyou'veactually
unleashedtheeffects!
Let'sseeitinuse:
//ioWindow::IOWindow
constioWindow=newIO(()=>window);
ioWindow.map(win=>win.innerWidth);
//IO(1430)
ioWindow
.map(prop('location'))
.map(prop('href'))
.map(split('/'));
//IO(['http:','','localhost:8000','blog','posts'])
//$::String->IO[DOM]
const$=selector=>newIO(()=>document.querySelectorAll(selector));
$('#myDiv').map(head).map(div=>div.innerHTML);
//IO('Iamsomeinnerhtml')
Chapter08:Tupperware
70
Here, ioWindowisanactual IOthatwecan mapoverstraightaway,whereas $isa
functionthatreturnsan IOafterit'scalled.I'vewrittenouttheconceptualreturnvaluesto
betterexpressthe IO,though,inreality,itwillalwaysbe {$value:[Function]}.When
we mapoverour IO,westickthatfunctionattheendofacompositionwhich,inturn,
becomesthenew $valueandsoon.Ourmappedfunctionsdonotrun,theygettackedon
theendofacomputationwe'rebuildingup,functionbyfunction,likecarefullyplacing
dominoesthatwedon'tdaretipover.TheresultisreminiscentofGangofFour'scommand
patternoraqueue.
Takeamomenttochannelyourfunctorintuition.Ifweseepasttheimplementationdetails,
weshouldfeelrightathomemappingoveranycontainernomatteritsquirksor
idiosyncrasies.Wehavethefunctorlaws,whichwewillexploretowardtheendofthe
chapter,tothankforthispseudo-psychicpower.Atanyrate,wecanfinallyplaywithimpure
valueswithoutsacrificingourpreciouspurity.
Now,we'vecagedthebeast,butwe'llstillhavetosetitfreeatsomepoint.Mappingoverour
IOhasbuiltupamightyimpurecomputationandrunningitissurelygoingtodisturbthe
peace.Sowhereandwhencanwepullthetrigger?Isitevenpossibletorunour IOand
stillwearwhiteatourwedding?Theanswerisyes,ifweputtheonusonthecallingcode.
Ourpurecode,despitethenefariousplottingandscheming,maintainsitsinnocenceandit's
thecallerwhogetsburdenedwiththeresponsibilityofactuallyrunningtheeffects.Let'ssee
anexampletomakethisconcrete.
//url::IOString
consturl=newIO(()=>window.location.href);
//toPairs::String->[[String]]
consttoPairs=compose(map(split('=')),split('&'));
//params::String->[[String]]
constparams=compose(toPairs,last,split('?'));
//findParam::String->IOMaybe[String]
constfindParam=key=>map(compose(Maybe.of,filter(compose(eq(key),head)),params)
,url);
//--Impurecallingcode----------------------------------------------
//runitbycalling$value()!
findParam('searchTerm').$value();
//Just([['searchTerm','wafflehouse']])
Ourlibrarykeepsitshandscleanbywrapping urlinan IOandpassingthebucktothe
caller.Youmighthavealsonoticedthatwehavestackedourcontainers;it'sperfectly
reasonabletohavea IO(Maybe([x])),whichisthreefunctorsdeep( Arrayismost
Chapter08:Tupperware
71
definitelyamappablecontainertype)andexceptionallyexpressive.
There'ssomethingthat'sbeenbotheringmeandweshouldrectifyitimmediately: IO's
$valueisn'treallyitscontainedvalue,norisitaprivateproperty.Itisthepininthegrenade
anditismeanttobepulledbyacallerinthemostpublicofways.Let'srenamethisproperty
to unsafePerformIOtoremindourusersofitsvolatility.
classIO{
constructor(io){
this.unsafePerformIO=io;
}
map(fn){
returnnewIO(compose(fn,this.unsafePerformIO));
}
}
There,muchbetter.Nowourcallingcodebecomes
findParam('searchTerm').unsafePerformIO(),whichisclearasdaytousers(andreaders)of
theapplication.
IOwillbealoyalcompanion,helpingustamethoseferalimpureactions.Next,we'llseea
typesimilarinspirit,buthasadrasticallydifferentusecase.
AsynchronousTasks
Callbacksarethenarrowingspiralstaircasetohell.Theyarecontrolflowasdesignedby
M.C.Escher.Witheachnestedcallbacksqueezedinbetweenthejunglegymofcurlybraces
andparenthesis,theyfeellikelimboinanoubliette(howlowcanwego?!).I'mgetting
claustrophobicchillsjustthinkingaboutthem.Nottoworry,wehaveamuchbetterwayof
dealingwithasynchronouscodeanditstartswithan"F".
Theinternalsareabittoocomplicatedtospilloutalloverthepageheresowewilluse
Data.Task(previously Data.Future)fromQuildreenMotta'sfantasticFolktale.Behold
someexampleusage:
Chapter08:Tupperware
72
//--NodereadFileexample------------------------------------------
constfs=require('fs');
//readFile::String->TaskErrorString
constreadFile=filename=>newTask((reject,result)=>{
fs.readFile(filename,(err,data)=>(err?reject(err):result(data)));
});
readFile('metamorphosis').map(split('\n')).map(head);
//Task('Onemorning,asGregorSamsawaswakingupfromanxiousdreams,hediscovered
that
//inbedhehadbeenchangedintoamonstrousverminousbug.')
//--jQuerygetJSONexample-----------------------------------------
//getJSON::String->{}->TaskErrorJSON
constgetJSON=curry((url,params)=>newTask((reject,result)=>{
$.getJSON(url,params,result).fail(reject);
}));
getJSON('/video',{id:10}).map(prop('title'));
//Task('FamilyMattersep15')
//--DefaultMinimalContext----------------------------------------
//Wecanputnormal,nonfuturisticvaluesinsideaswell
Task.of(3).map(three=>three+1);
//Task(4)
ThefunctionsI'mcalling rejectand resultareourerrorandsuccesscallbacks,
respectively.Asyoucansee,wesimply mapoverthe Tasktoworkonthefuturevalueas
ifitwasrightthereinourgrasp.Bynow mapshouldbeoldhat.
Ifyou'refamiliarwithpromises,youmightrecognizethefunction mapas thenwith Task
playingtheroleofourpromise.Don'tfretifyouaren'tfamiliarwithpromises,wewon'tbe
usingthemanyhowbecausetheyarenotpure,buttheanalogyholdsnonetheless.
Like IO, Taskwillpatientlywaitforustogiveitthegreenlightbeforerunning.Infact,
becauseitwaitsforourcommand, IOiseffectivelysubsumedby Taskforallthings
asynchronous; readFileand getJSONdon'trequireanextra IOcontainertobepure.
What'smore, Taskworksinasimilarfashionwhenwe mapoverit:we'replacing
instructionsforthefuturelikeachorechartinatimecapsule-anactofsophisticated
technologicalprocrastination.
Chapter08:Tupperware
73
Torunour Task,wemustcallthemethod fork.Thisworkslike unsafePerformIO,butas
thenamesuggests,itwillforkourprocessandevaluationcontinuesonwithoutblockingour
thread.Thiscanbeimplementedinnumerouswayswiththreadsandsuch,buthereitacts
asanormalasynccallwouldandthebigwheeloftheeventloopkeepsonturning.Let's
lookat fork:
//--Pureapplication-------------------------------------------------
//blogPage::Posts->HTML
constblogPage=Handlebars.compile(blogTemplate);
//renderPage::Posts->HTML
constrenderPage=compose(blogPage,sortBy('date'));
//blog::Params->TaskErrorHTML
constblog=compose(map(renderPage),getJSON('/posts'));
//--Impurecallingcode----------------------------------------------
blog({}).fork(
error=>$('#error').html(error.message),
page=>$('#main').html(page),
);
$('#spinner').show();
Uponcalling fork,the Taskhurriesofftofindsomepostsandrenderthepage.
Meanwhile,weshowaspinnersince forkdoesnotwaitforaresponse.Finally,wewill
eitherdisplayanerrororrenderthepageontothescreendependingifthe getJSONcall
succeededornot.
Takeamomenttoconsiderhowlinearthecontrolflowishere.Wejustreadbottomtotop,
righttolefteventhoughtheprogramwillactuallyjumparoundabitduringexecution.This
makesreadingandreasoningaboutourapplicationsimplerthanhavingtobouncebetween
callbacksanderrorhandlingblocks.
Goodness,wouldyoulookatthat, Taskhasalsoswallowedup Either!Itmustdosoin
ordertohandlefuturisticfailuressinceournormalcontrolflowdoesnotapplyintheasync
world.Thisisallwellandgoodasitprovidessufficientandpureerrorhandlingoutofthe
box.
Evenwith Task,our IOand Eitherfunctorsarenotoutofajob.Bearwithmeonaquick
examplethatleanstowardthemorecomplexandhypotheticalside,butisusefulfor
illustrativepurposes.
Chapter08:Tupperware
74
//Postgres.connect::Url->IODbConnection
//runQuery::DbConnection->ResultSet
//readFile::String->TaskErrorString
//--Pureapplication-------------------------------------------------
//dbUrl::Config->EitherErrorUrl
constdbUrl=({uname,pass,db})=>{
if(uname&&pass&&host&&db){
returnEither.of(`db:pg://${uname}:${pass}@${host}5432/${db}`);
}
returnleft(Error('Invalidconfig!'));
};
//connectDb::Config->EitherError(IODbConnection)
constconnectDb=compose(map(Postgres.connect),dbUrl);
//getConfig::Filename->TaskError(EitherError(IODbConnection))
constgetConfig=compose(map(compose(connectDb,JSON.parse)),readFile);
//--Impurecallingcode----------------------------------------------
getConfig('db.json').fork(
logErr('couldn\'treadfile'),
either(console.log,map(runQuery)),
);
Inthisexample,westillmakeuseof Eitherand IOfromwithinthesuccessbranchof
readFile. Tasktakescareoftheimpuritiesofreadingafileasynchronously,butwestill
dealwithvalidatingtheconfigwith Eitherandwranglingthedbconnectionwith IO.So
yousee,we'restillinbusinessforallthingssynchronous.
Icouldgoon,butthat'sallthereistoit.Simpleas map.
Inpractice,you'lllikelyhavemultipleasynchronoustasksinoneworkflowandwehaven'tyet
acquiredthefullcontainerapistotacklethisscenario.Nottoworry,we'lllookatmonadsand
suchsoon,butfirst,wemustexaminethemathsthatmakethisallpossible.
ASpotofTheory
Asmentionedbefore,functorscomefromcategorytheoryandsatisfyafewlaws.Let'sfirst
exploretheseusefulproperties.
Chapter08:Tupperware
75
//identity
map(id)===id;
//composition
compose(map(f),map(g))===map(compose(f,g));
Theidentitylawissimple,butimportant.Theselawsarerunnablebitsofcodesowecantry
themonourownfunctorstovalidatetheirlegitimacy.
constidLaw1=map(id);
constidLaw2=id;
idLaw1(Container.of(2));//Container(2)
idLaw2(Container.of(2));//Container(2)
Yousee,theyareequal.Nextlet'slookatcomposition.
constcompLaw1=compose(map(concat('world')),map(concat('cruel')));
constcompLaw2=map(compose(concat('world'),concat('cruel')));
compLaw1(Container.of('Goodbye'));//Container('worldcruelGoodbye')
compLaw2(Container.of('Goodbye'));//Container('worldcruelGoodbye')
Incategorytheory,functorstaketheobjectsandmorphismsofacategoryandmapthemto
adifferentcategory.Bydefinition,thisnewcategorymusthaveanidentityandtheabilityto
composemorphisms,butweneedn'tcheckbecausetheaforementionedlawsensurethese
arepreserved.
Perhapsourdefinitionofacategoryisstillabitfuzzy.Youcanthinkofacategoryasa
networkofobjectswithmorphismsthatconnectthem.Soafunctorwouldmaptheone
categorytotheotherwithoutbreakingthenetwork.Ifanobject aisinoursourcecategory
C,whenwemapittocategory Dwithfunctor F,werefertothatobjectas Fa(Ifyou
putittogetherwhatdoesthatspell?!).Perhaps,it'sbettertolookatadiagram:
Chapter08:Tupperware
76
Forinstance, Maybemapsourcategoryoftypesandfunctionstoacategorywhereeach
objectmaynotexistandeachmorphismhasa nullcheck.Weaccomplishthisincodeby
surroundingeachfunctionwith mapandeachtypewithourfunctor.Weknowthateachof
ournormaltypesandfunctionswillcontinuetocomposeinthisnewworld.Technically,each
functorinourcodemapstoasubcategoryoftypesandfunctionswhichmakesallfunctorsa
particularbrandcalledendofunctors,butforourpurposes,we'llthinkofitasadifferent
category.
Wecanalsovisualizethemappingofamorphismanditscorrespondingobjectswiththis
diagram:
Inadditiontovisualizingthemappedmorphismfromonecategorytoanotherunderthe
functor F,weseethatthediagramcommutes,whichistosay,ifyoufollowthearrowseach
routeproducesthesameresult.Thedifferentroutesmeandifferentbehavior,butwealways
endatthesametype.Thisformalismgivesusprincipledwaystoreasonaboutourcode-
wecanboldlyapplyformulaswithouthavingtoparseandexamineeachindividualscenario.
Let'stakeaconcreteexample.
Chapter08:Tupperware
77
//topRoute::String->MaybeString
consttopRoute=compose(Maybe.of,reverse);
//bottomRoute::String->MaybeString
constbottomRoute=compose(map(reverse),Maybe.of);
topRoute('hi');//Just('ih')
bottomRoute('hi');//Just('ih')
Orvisually:
Wecaninstantlyseeandrefactorcodebasedonpropertiesheldbyallfunctors.
Functorscanstack:
constnested=Task.of([Either.of('pillows'),left('nosleepforyou')]);
map(map(map(toUpperCase)),nested);
//Task([Right('PILLOWS'),Left('nosleepforyou')])
Whatwehaveherewith nestedisafuturearrayofelementsthatmightbeerrors.We map
topeelbackeachlayerandrunourfunctionontheelements.Weseenocallbacks,if/else's,
orforloops;justanexplicitcontext.Wedo,however,haveto map(map(map(f))).Wecan
insteadcomposefunctors.Youheardmecorrectly:
Chapter08:Tupperware
78
classCompose{
constructor(fgx){
this.getCompose=fgx;
}
staticof(fgx){
returnnewCompose(fgx);
}
map(fn){
returnnewCompose(map(map(fn),this.getCompose));
}
}
consttmd=Task.of(Maybe.of(',rockon,Chicago'));
constctmd=Compose.of(tmd);
map(concat('RockoverLondon'),ctmd);
//Compose(Task(Just('RockoverLondon,rockon,Chicago')))
ctmd.getCompose;
//Task(Just('RockoverLondon,rockon,Chicago'))
There,one map.Functorcompositionisassociativeandearlier,wedefined Container,
whichisactuallycalledthe Identityfunctor.Ifwehaveidentityandassociative
compositionwehaveacategory.Thisparticularcategoryhascategoriesasobjectsand
functorsasmorphisms,whichisenoughtomakeone'sbrainperspire.Wewon'tdelvetoo
farintothis,butit'snicetoappreciatethearchitecturalimplicationsorevenjustthesimple
abstractbeautyinthepattern.
InSummary
We'veseenafewdifferentfunctors,butthereareinfinitelymany.Somenotableomissions
areiterabledatastructuresliketrees,lists,maps,pairs,younameit.Eventstreamsand
observablesarebothfunctors.Otherscanbeforencapsulationorevenjusttypemodelling.
Functorsareallaroundusandwe'llusethemextensivelythroughoutthebook.
Whataboutcallingafunctionwithmultiplefunctorarguments?Howaboutworkingwithan
ordersequenceofimpureorasyncactions?Wehaven'tyetacquiredthefulltoolsetfor
workinginthisboxedupworld.Next,we'llcutrighttothechaseandlookatmonads.
Chapter09:MonadicOnions
Chapter08:Tupperware
79
Exercises
Exercise
Use`add`and`map`tomakeafunctionthatincrementsavalueinsideafunctor.
//incrF::Functorf=>fInt->fInt
constincrF=undefined;
GiventhefollowingUserobject:
constuser={id:2,name:'Albert',active:true};
Exercise
Use`safeProp`and`head`tofindthefirstinitialoftheuser.
//initial::User->MaybeString
constinitial=undefined;
Giventhefollowinghelperfunctions:
//showWelcome::User->String
constshowWelcome=compose(concat('Welcome'),prop('name'));
//checkActive::User->EitherStringUser
constcheckActive=functioncheckActive(user){
returnuser.active
?Either.of(user)
:left('Youraccountisnotactive');
};
Exercise
Writeafunctionthatuses`checkActive`and`showWelcome`tograntaccessorreturn
theerror.
//eitherWelcome::User->EitherStringString
consteitherWelcome=undefined;
Chapter08:Tupperware
80
Wenowconsiderthefollowingfunctions:
//validateUser::(User->EitherString())->User->EitherStringUser
constvalidateUser=curry((validate,user)=>validate(user).map(_=>user));
//save::User->IOUser
constsave=user=>newIO(()=>({...user,saved:true}));
Exercise
Writeafunction`validateName`whichcheckswhetherauserhasanamelongerthan3
charactersorreturnanerrormessage.Thenuse`either`,`showWelcome`and`save`to
writea`register`functiontosignupandwelcomeauserwhenthevalidationisok.
Remembereither'stwoargumentsmustreturnthesametype.
//validateName::User->EitherString()
constvalidateName=undefined;
//register::User->IOString
constregister=compose(undefined,validateUser(validateName));
Chapter08:Tupperware
81
Chapter09:MonadicOnions
PointyFunctorFactory
Beforewegoanyfurther,Ihaveaconfessiontomake:Ihaven'tbeenfullyhonestaboutthat
ofmethodwe'veplacedoneachofourtypes.Turnsout,itisnottheretoavoidthe new
keyword,butrathertoplacevaluesinwhat'scalledadefaultminimalcontext.Yes, ofdoes
notactuallytaketheplaceofaconstructor-itispartofanimportantinterfacewecall
Pointed.
Apointedfunctorisafunctorwithan ofmethod
What'simportanthereistheabilitytodropanyvalueinourtypeandstartmappingaway.
IO.of('tetris').map(concat('master'));
//IO('tetrismaster')
Maybe.of(1336).map(add(1));
//Maybe(1337)
Task.of([{id:2},{id:3}]).map(map(prop('id')));
//Task([2,3])
Either.of('Thepast,presentandfuturewalkintoabar...').map(concat('itwastense.'
));
//Right('Thepast,presentandfuturewalkintoabar...itwastense.')
Ifyourecall, IOand Task'sconstructorsexpectafunctionastheirargument,but Maybe
and Eitherdonot.Themotivationforthisinterfaceisacommon,consistentwaytoplacea
valueintoourfunctorwithoutthecomplexitiesandspecificdemandsofconstructors.The
term"defaultminimalcontext"lacksprecision,yetcapturestheideawell:we'dliketoliftany
valueinourtypeand mapawayperusualwiththeexpectedbehaviourofwhichever
functor.
OneimportantcorrectionImustmakeatthispoint,punintended,isthat Left.ofdoesn't
makeanysense.Eachfunctormusthaveonewaytoplaceavalueinsideitandwith
Either,that's newRight(x).Wedefine ofusing Rightbecauseifourtypecanmap,it
shouldmap.Lookingattheexamplesabove,weshouldhaveanintuitionabouthow of
willusuallyworkand Leftbreaksthatmold.
Chapter09:MonadicOnions
82
Youmayhaveheardoffunctionssuchas pure, point, unit,and return.Theseare
variousmonikersforour ofmethod,internationalfunctionofmystery. ofwillbecome
importantwhenwestartusingmonadsbecause,aswewillsee,it'sourresponsibilityto
placevaluesbackintothetypemanually.
Toavoidthe newkeyword,thereareseveralstandardJavaScripttricksorlibrariessolet's
usethemanduse oflikearesponsibleadultfromhereonout.Irecommendusingfunctor
instancesfrom folktale, ramdaor fantasy-landastheyprovidethecorrect ofmethod
aswellasniceconstructorsthatdon'trelyon new.
MixingMetaphors
Yousee,inadditiontospaceburritos(ifyou'veheardtherumors),monadsarelikeonions.
Allowmetodemonstratewithacommonsituation:
Chapter09:MonadicOnions
83
constfs=require('fs');
//readFile::String->IOString
constreadFile=filename=>newIO(()=>fs.readFileSync(filename,'utf-8'));
//print::String->IOString
constprint=x=>newIO(()=>{
console.log(x);
returnx;
});
//cat::String->IO(IOString)
constcat=compose(map(print),readFile);
cat('.git/config');
//IO(IO('[core]\nrepositoryformatversion=0\n'))
Whatwe'vegothereisan IOtrappedinsideanother IObecause printintroduceda
second IOduringour map.Tocontinueworkingwithourstring,wemust map(map(f))and
toobservetheeffect,wemust unsafePerformIO().unsafePerformIO().
//cat::String->IO(IOString)
constcat=compose(map(print),readFile);
//catFirstChar::String->IO(IOString)
constcatFirstChar=compose(map(map(head)),cat);
catFirstChar('.git/config');
//IO(IO('['))
Whileitisnicetoseethatwehavetwoeffectspackagedupandreadytogoinour
application,itfeelsabitlikeworkingintwohazmatsuitsandweendupwithan
uncomfortablyawkwardAPI.Let'slookatanothersituation:
Chapter09:MonadicOnions
84
//safeProp::Key->{Key:a}->Maybea
constsafeProp=curry((x,obj)=>Maybe.of(obj[x]));
//safeHead::[a]->Maybea
constsafeHead=safeProp(0);
//firstAddressStreet::User->Maybe(Maybe(MaybeStreet))
constfirstAddressStreet=compose(
map(map(safeProp('street'))),
map(safeHead),
safeProp('addresses'),
);
firstAddressStreet({
addresses:[{street:{name:'Mulburry',number:8402},postcode:'WC2N'}],
});
//Maybe(Maybe(Maybe({name:'Mulburry',number:8402})))
Again,weseethisnestedfunctorsituationwhereit'sneattoseetherearethreepossible
failuresinourfunction,butit'salittlepresumptuoustoexpectacallerto mapthreetimesto
getatthevalue-we'donlyjustmet.Thispatternwillarisetimeandtimeagainanditisthe
primarysituationwherewe'llneedtoshinethemightymonadsymbolintothenightsky.
Isaidmonadsarelikeonionsbecausetearswellupaswepeelbackeachlayerofthe
nestedfunctorwith maptogetattheinnervalue.Wecandryoureyes,takeadeepbreath,
anduseamethodcalled join.
constmmo=Maybe.of(Maybe.of('nunchucks'));
//Maybe(Maybe('nunchucks'))
mmo.join();
//Maybe('nunchucks')
constioio=IO.of(IO.of('pizza'));
//IO(IO('pizza'))
ioio.join();
//IO('pizza')
constttt=Task.of(Task.of(Task.of('sewers')));
//Task(Task(Task('sewers')));
ttt.join();
//Task(Task('sewers'))
Ifwehavetwolayersofthesametype,wecansmashthemtogetherwith join.Thisability
tojointogether,thisfunctormatrimony,iswhatmakesamonadamonad.Let'sinchtoward
thefulldefinitionwithsomethingalittlemoreaccurate:
Chapter09:MonadicOnions
85
Monadsarepointedfunctorsthatcanflatten
Anyfunctorwhichdefinesa joinmethod,hasan ofmethod,andobeysafewlawsisa
monad.Defining joinisnottoodifficultsolet'sdosofor Maybe:
Maybe.prototype.join=functionjoin(){
returnthis.isNothing()?Maybe.of(null):this.$value;
};
There,simpleasconsumingone'stwininthewomb.Ifwehavea Maybe(Maybe(x))then
.$valuewilljustremovetheunnecessaryextralayerandwecansafely mapfromthere.
Otherwise,we'lljusthavetheone Maybeasnothingwouldhavebeenmappedinthefirst
place.
Nowthatwehavea joinmethod,let'ssprinklesomemagicmonaddustoverthe
firstAddressStreetexampleandseeitinaction:
//join::Monadm=>m(ma)->ma
constjoin=mma=>mma.join();
//firstAddressStreet::User->MaybeStreet
constfirstAddressStreet=compose(
join,
map(safeProp('street')),
join,
map(safeHead),safeProp('addresses'),
);
firstAddressStreet({
addresses:[{street:{name:'Mulburry',number:8402},postcode:'WC2N'}],
});
//Maybe({name:'Mulburry',number:8402})
Weadded joinwhereverweencounteredthenested Maybe'stokeepthemfromgetting
outofhand.Let'sdothesamewith IOtogiveusafeelforthat.
IO.prototype.join=()=>this.unsafePerformIO();
Again,wesimplyremoveonelayer.Mindyou,wehavenotthrownoutpurity,butmerely
removedonelayerofexcessshrinkwrap.
Chapter09:MonadicOnions
86
//log::a->IOa
constlog=x=>IO.of(()=>{
console.log(x);
returnx;
});
//setStyle::Selector->CSSProps->IODOM
constsetStyle=
curry((sel,props)=>newIO(()=>jQuery(sel).css(props)));
//getItem::String->IOString
constgetItem=key=>newIO(()=>localStorage.getItem(key));
//applyPreferences::String->IODOM
constapplyPreferences=compose(
join,
map(setStyle('#main')),
join,
map(log),
map(JSON.parse),
getItem,
);
applyPreferences('preferences').unsafePerformIO();
//Object{backgroundColor:"green"}
//<divstyle="background-color:'green'"/>
getItemreturnsan IOStringsowe maptoparseit.Both logand setStylereturn
IO'sthemselvessowemust jointokeepournestingundercontrol.
MyChainHitsMyChest
Chapter09:MonadicOnions
87
Youmighthavenoticedapattern.Weoftenendupcalling joinrightaftera map.Let's
abstractthisintoafunctioncalled chain.
//chain::Monadm=>(a->mb)->ma->mb
constchain=curry((f,m)=>m.map(f).join());
//or
//chain::Monadm=>(a->mb)->ma->mb
constchain=f=>compose(join,map(f));
We'lljustbundleupthismap/joincombointoasinglefunction.Ifyou'vereadaboutmonads
previously,youmighthaveseen chaincalled >>=(pronouncedbind)or flatMapwhich
areallaliasesforthesameconcept.Ipersonallythink flatMapisthemostaccuratename,
butwe'llstickwith chainasit'sthewidelyacceptednameinJS.Let'srefactorthetwo
examplesabovewith chain:
Chapter09:MonadicOnions
88
//map/join
constfirstAddressStreet=compose(
join,
map(safeProp('street')),
join,
map(safeHead),
safeProp('addresses'),
);
//chain
constfirstAddressStreet=compose(
chain(safeProp('street')),
chain(safeHead),
safeProp('addresses'),
);
//map/join
constapplyPreferences=compose(
join,
map(setStyle('#main')),
join,
map(log),
map(JSON.parse),
getItem,
);
//chain
constapplyPreferences=compose(
chain(setStyle('#main')),
chain(log),
map(JSON.parse),
getItem,
);
Iswappedoutany map/joinwithournew chainfunctiontotidythingsupabit.
Cleanlinessisniceandall,butthere'smoreto chainthanmeetstheeye-it'smoreofa
tornadothanavacuum.Because chaineffortlesslynestseffects,wecancaptureboth
sequenceandvariableassignmentinapurelyfunctionalway.
Chapter09:MonadicOnions
89
//getJSON::Url->Params->TaskJSON
getJSON('/authenticate',{username:'stale',password:'crackers'})
.chain(user=>getJSON('/friends',{user_id:user.id}));
//Task([{name:'Seimith',id:14},{name:'Ric',id:39}]);
//querySelector::Selector->IODOM
querySelector('input.username')
.chain(({value:uname})=>querySelector('input.email')
.chain(({value:email})=>IO.of(`Welcome${uname}prepareforspamat${email}`))
);
//IO('WelcomeOliviaprepareforspamatolivia@tremorcontrol.net');
Maybe.of(3)
.chain(three=>Maybe.of(2).map(add(three)));
//Maybe(5);
Maybe.of(null)
.chain(safeProp('address'))
.chain(safeProp('street'));
//Maybe(null);
Wecouldhavewrittentheseexampleswith compose,butwe'dneedafewhelperfunctions
andthisstylelendsitselftoexplicitvariableassignmentviaclosureanyhow.Insteadwe're
usingtheinfixversionof chainwhich,incidentally,canbederivedfrom mapand joinfor
anytypeautomatically: t.prototype.chain=function(f){returnthis.map(f).join();}.
Wecanalsodefine chainmanuallyifwe'dlikeafalsesenseofperformance,thoughwe
musttakecaretomaintainthecorrectfunctionality-thatis,itmustequal mapfollowedby
join.Aninterestingfactisthatwecanderive mapforfreeifwe'vecreated chainsimply
bybottlingthevaluebackupwhenwe'refinishedwith of.With chain,wecanalsodefine
joinas chain(id).ItmayfeellikeplayingTexasHoldem'witharhinestonemagicianin
thatI'mjustpullingthingsoutofmybehind,but,aswithmostmathematics,allofthese
principledconstructsareinterrelated.Lotsofthesederivationsarementionedinthe
fantasylandrepo,whichistheofficialspecificationforalgebraicdatatypesinJavaScript.
Anyways,let'sgettotheexamplesabove.Inthefirstexample,weseetwo Task'schained
inasequenceofasynchronousactions-firstitretrievesthe user,thenitfindsthefriends
withthatuser'sid.Weuse chaintoavoida Task(Task([Friend]))situation.
Next,weuse querySelectortofindafewdifferentinputsandcreateawelcomingmessage.
Noticehowwehaveaccesstoboth unameand emailattheinnermostfunction-thisis
functionalvariableassignmentatitsfinest.Since IOisgraciouslylendingusitsvalue,we
areinchargeofputtingitbackhowwefoundit-wewouldn'twanttobreakitstrust(andour
program). IO.ofistheperfecttoolforthejobandit'swhyPointedisanimportant
prerequisitetotheMonadinterface.However,wecouldchooseto mapasthatwouldalso
returnthecorrecttype:
Chapter09:MonadicOnions
90
querySelector('input.username').chain(({value:uname})=>
querySelector('input.email').map(({value:email})=>
`Welcome${uname}prepareforspamat${email}`));
//IO('WelcomeOliviaprepareforspamatolivia@tremorcontrol.net');
Finally,wehavetwoexamplesusing Maybe.Since chainismappingunderthehood,if
anyvalueis null,westopthecomputationdeadinitstracks.
Don'tworryiftheseexamplesarehardtograspatfirst.Playwiththem.Pokethemwitha
stick.Smashthemtobitsandreassemble.Rememberto mapwhenreturninga"normal"
valueand chainwhenwe'rereturninganotherfunctor.Inthenextchapter,we'llapproach
Applicativesandseenicetrickstomakethiskindofexpressionsnicerandhighly
readable.
Asareminder,thisdoesnotworkwithtwodifferentnestedtypes.Functorcompositionand
later,monadtransformers,canhelpusinthatsituation.
PowerTrip
Containerstyleprogrammingcanbeconfusingattimes.Wesometimesfindourselves
strugglingtounderstandhowmanycontainersdeepavalueisorifweneed mapor chain
(soonwe'llseemorecontainermethods).Wecangreatlyimprovedebuggingwithtrickslike
implementing inspectandwe'lllearnhowtocreatea"stack"thatcanhandlewhatever
effectswethrowatit,buttherearetimeswhenwequestionifit'sworththehassle.
I'dliketoswingthefierymonadicswordforamomenttoexhibitthepowerofprogramming
thisway.
Let'sreadafile,thenuploaditdirectlyafterward:
//readFile::Filename->EitherString(TaskErrorString)
//httpPost::String->TaskErrorJSON
//upload::String->EitherString(TaskErrorJSON)
constupload=compose(map(chain(httpPost('/uploads'))),readFile);
Here,wearebranchingourcodeseveraltimes.LookingatthetypesignaturesIcanseethat
weprotectagainst3errors- readFileuses Eithertovalidatetheinput(perhapsensuring
thefilenameispresent), readFilemayerrorwhenaccessingthefileasexpressedinthe
firsttypeparameterof Task,andtheuploadmayfailforwhateverreasonwhichis
expressedbythe Errorin httpPost.Wecasuallypullofftwonested,sequential
asynchronousactionswith chain.
Chapter09:MonadicOnions
91
Allofthisisachievedinonelinearlefttorightflow.Thisisallpureanddeclarative.Itholds
equationalreasoningandreliableproperties.Wearen'tforcedtoaddneedlessand
confusingvariablenames.Our uploadfunctioniswrittenagainstgenericinterfacesandnot
specificone-offapis.It'sonebloodylineforgoodnesssake.
Forcontrast,let'slookatthestandardimperativewaytopullthisoff:
//upload::String->(String->a)->Void
constupload=(filename,callback)=>{
if(!filename){
thrownewError('Youneedafilename!');
}else{
readFile(filename,(errF,contents)=>{
if(errF)throwerr;
httpPost(contents,(errH,json)=>{
if(errH)throwerrH;
callback(json);
});
});
}
};
Wellisn'tthatthedevil'sarithmetic.We'repinballedthroughavolatilemazeofmadness.
Imagineifitwereatypicalappthatalsomutatedvariablesalongtheway!We'dbeinthetar
pitindeed.
Theory
Thefirstlawwe'lllookatisassociativity,butperhapsnotinthewayyou'reusedtoit.
//associativity
compose(join,map(join))===compose(join,join);
Theselawsgetatthenestednatureofmonadssoassociativityfocusesonjoiningtheinner
oroutertypesfirsttoachievethesameresult.Apicturemightbemoreinstructive:
Chapter09:MonadicOnions
92
Startingwiththetopleftmovingdownward,wecan jointheoutertwo M'sof M(M(Ma))
firstthencruiseovertoourdesired Mawithanother join.Alternatively,wecanpopthe
hoodandflattentheinnertwo M'swith map(join).Weendupwiththesame Ma
regardlessofifwejointheinnerorouter M'sfirstandthat'swhatassociativityisallabout.
It'sworthnotingthat map(join)!=join.Theintermediatestepscanvaryinvalue,butthe
endresultofthelast joinwillbethesame.
Thesecondlawissimilar:
//identityforall(Ma)
compose(join,of)===compose(join,map(of))===id;
Itstatesthat,foranymonad M, ofand joinamountsto id.Wecanalso map(of)and
attackitfromtheinsideout.Wecallthis"triangleidentity"becauseitmakessuchashape
whenvisualized:
Ifwestartatthetopleftheadingright,wecanseethat ofdoesindeeddropour Main
another Mcontainer.Thenifwemovedownwardand joinit,wegetthesameasifwe
justcalled idinthefirstplace.Movingrighttoleft,weseethatifwesneakunderthe
coverswith mapandcall ofoftheplain a,we'llstillendupwith M(Ma)and joining
willbringusbacktosquareone.
Chapter09:MonadicOnions
93
IshouldmentionthatI'vejustwritten of,however,itmustbethespecific M.offor
whatevermonadwe'reusing.
Now,I'veseentheselaws,identityandassociativity,somewherebefore...Holdon,I'm
thinking...Yesofcourse!Theyarethelawsforacategory.Butthatwouldmeanweneeda
compositionfunctiontocompletethedefinition.Behold:
constmcompose=(f,g)=>compose(chain(f),g);
//leftidentity
mcompose(M,f)===f;
//rightidentity
mcompose(f,M)===f;
//associativity
mcompose(mcompose(f,g),h)===mcompose(f,mcompose(g,h));
Theyarethecategorylawsafterall.Monadsformacategorycalledthe"Kleislicategory"
whereallobjectsaremonadsandmorphismsarechainedfunctions.Idon'tmeantotaunt
youwithbitsandbobsofcategorytheorywithoutmuchexplanationofhowthejigsawfits
together.Theintentionistoscratchthesurfaceenoughtoshowtherelevanceandspark
someinterestwhilefocusingonthepracticalpropertieswecanuseeachday.
InSummary
Monadsletusdrilldownwardintonestedcomputations.Wecanassignvariables,run
sequentialeffects,performasynchronoustasks,allwithoutlayingonebrickinapyramidof
doom.Theycometotherescuewhenavaluefindsitselfjailedinmultiplelayersofthesame
type.Withthehelpofthetrustysidekick"pointed",monadsareabletolendusanunboxed
valueandknowwe'llbeabletoplaceitbackinwhenwe'redone.
Yes,monadsareverypowerful,yetwestillfindourselvesneedingsomeextracontainer
functions.Forinstance,whatifwewantedtorunalistofapicallsatonce,thengatherthe
results?Wecanaccomplishthistaskwithmonads,butwe'dhavetowaitforeachoneto
finishbeforecallingthenext.Whataboutcombiningseveralvalidations?We'dliketo
continuevalidatingtogatherthelistoferrors,butmonadswouldstoptheshowafterthefirst
Leftenteredthepicture.
Inthenextchapter,we'llseehowapplicativefunctorsfitintothecontainerworldandwhywe
preferthemtomonadsinmanycases.
Chapter10:ApplicativeFunctors
Chapter09:MonadicOnions
94
Exercises
ConsideringaUserobjectasfollow:
constuser={
id:1,
name:'Albert',
address:{
street:{
number:22,
name:'WalnutSt',
},
},
};
Exercise
Use`safeProp`and`map/join`or`chain`tosafelygetthestreetnamewhengivenauser
//getStreetName::User->MaybeString
constgetStreetName=undefined;
Wenowconsiderthefollowingfunctions
//getFile::()->IOString
constgetFile=()=>IO.of('/home/mostly-adequate/ch9.md');
//pureLog::String->IO()
constpureLog=str=>newIO(()=>console.log(str));
Exercise
UsegetFiletogetthefilepath,removethedirectoryandkeeponlythebasename,then
purelylogit.Hint:youmaywanttouse`split`and`last`toobtainthebasenamefroma
filepath.
//logFilename::IO()
constlogFilename=undefined;
Chapter09:MonadicOnions
95
Forthisexercise,weconsiderhelperswiththefollowingsignatures:
//validateEmail::Email->EitherStringEmail
//addToMailingList::Email->IO([Email])
//emailBlast::[Email]->IO()
Exercise
Use`validateEmail`,`addToMailingList`and`emailBlast`tocreateafunctionwhichadds
anewemailtothemailinglistifvalid,andthennotifythewholelist.
//joinMailingList::Email->EitherString(IO())
constjoinMailingList=undefined;
Chapter09:MonadicOnions
96
Chapter10:ApplicativeFunctors
ApplyingApplicatives
Thenameapplicativefunctorispleasantlydescriptivegivenitsfunctionalorigins.
Functionalprogrammersarenotoriousforcomingupwithnameslike mappendor liftA4,
whichseemperfectlynaturalwhenviewedinthemathlab,butholdtheclarityofan
indecisiveDarthVaderatthedrivethruinanyothercontext.
Anyhow,thenameshouldspillthebeansonwhatthisinterfacegivesus:theabilitytoapply
functorstoeachother.
Now,whywouldanormal,rationalpersonsuchasyourselfwantsuchathing?Whatdoesit
evenmeantoapplyonefunctortoanother?
Toanswerthesequestions,we'llstartwithasituationyoumayhavealreadyencounteredin
yourfunctionaltravels.Let'ssay,hypothetically,thatwehavetwofunctors(ofthesametype)
andwe'dliketocallafunctionwithbothoftheirvaluesasarguments.Somethingsimplelike
addingthevaluesoftwo Containers.
//Wecan'tdothisbecausethenumbersarebottledup.
add(Container.of(2),Container.of(3));
//NaN
//Let'suseourtrustymap
constcontainerOfAdd2=map(add,Container.of(2));
//Container(add(2))
Wehaveourselvesa Containerwithapartiallyappliedfunctioninside.Morespecifically,
wehavea Container(add(2))andwe'dliketoapplyits add(2)tothe 3in Container(3)
tocompletethecall.Inotherwords,we'dliketoapplyonefunctortoanother.
Now,itjustsohappensthatwealreadyhavethetoolstoaccomplishthistask.Wecan
chainandthen mapthepartiallyapplied add(2)likeso:
Container.of(2).chain(two=>Container.of(3).map(add(two)));
Theissuehereisthatwearestuckinthesequentialworldofmonadswhereinnothingmay
beevaluateduntilthepreviousmonadhasfinisheditsbusiness.Wehaveourselvestwo
strong,independentvaluesandIshouldthinkitunnecessarytodelaythecreationof
Chapter10:ApplicativeFunctors
97
Container(3)merelytosatisfythemonad'ssequentialdemands.
Infact,itwouldbelovelyifwecouldsuccinctlyapplyonefunctor'scontentstoanother's
valuewithouttheseneedlessfunctionsandvariablesshouldwefindourselvesinthispickle
jar.
ShipsinBottles
apisafunctionthatcanapplythefunctioncontentsofonefunctortothevaluecontentsof
another.Saythatfivetimesfast.
Container.of(add(2)).ap(Container.of(3));
//Container(5)
//alltogethernow
Container.of(2).map(add).ap(Container.of(3));
//Container(5)
Thereweare,niceandneat.Goodnewsfor Container(3)asit'sbeensetfreefromthejail
ofthenestedmonadicfunction.It'sworthmentioningagainthat add,inthiscase,gets
partiallyappliedduringthefirst mapsothisonlyworkswhen addiscurried.
Wecandefine aplikeso:
Container.prototype.ap=function(otherContainer){
returnotherContainer.map(this.$value);
};
Chapter10:ApplicativeFunctors
98
Remember, this.$valuewillbeafunctionandwe'llbeacceptinganotherfunctorsowe
needonly mapit.Andwiththatwehaveourinterfacedefinition:
Anapplicativefunctorisapointedfunctorwithan apmethod
Notethedependenceonpointed.Thepointedinterfaceiscrucialhereaswe'llsee
throughoutthefollowingexamples.
Now,Isenseyourskepticism(orperhapsconfusionandhorror),butkeepanopenmind;this
apcharacterwillproveuseful.Beforewegetintoit,let'sexploreaniceproperty.
F.of(x).map(f)===F.of(f).ap(F.of(x));
InproperEnglish,mapping fisequivalentto apingafunctorof f.Orinproperer
English,wecanplace xintoourcontainerand map(f)ORwecanliftboth fand xinto
ourcontainerand apthem.Thisallowsustowriteinaleft-to-rightfashion:
Maybe.of(add).ap(Maybe.of(2)).ap(Maybe.of(3));
//Maybe(5)
Task.of(add).ap(Task.of(2)).ap(Task.of(3));
//Task(5)
Onemightevenrecognisethevagueshapeofanormalfunctioncallifviewedmidsquint.
We'lllookatthepointfreeversionlaterinthechapter,butfornow,thisisthepreferredwayto
writesuchcode.Using of,eachvaluegetstransportedtothemagicallandofcontainers,
thisparalleluniversewhereeachapplicationcanbeasyncornullorwhathaveyouand ap
willapplyfunctionswithinthisfantasticalplace.It'slikebuildingashipinabottle.
Didyouseethere?Weused Taskinourexample.Thisisaprimesituationwhere
applicativefunctorspulltheirweight.Let'slookatamorein-depthexample.
CoordinationMotivation
Saywe'rebuildingatravelsiteandwe'dliketoretrievebothalistoftouristdestinationsand
localevents.Eachoftheseareseparate,stand-aloneapicalls.
//Http.get::String->TaskErrorHTML
constrenderPage=curry((destinations,events)=>{/*renderpage*/});
Task.of(renderPage).ap(Http.get('/destinations')).ap(Http.get('/events'));
//Task("<div>somepagewithdestandevents</div>")
Chapter10:ApplicativeFunctors
99
Both Httpcallswillhappeninstantlyand renderPagewillbecalledwhenbothare
resolved.Contrastthiswiththemonadicversionwhereone Taskmustfinishbeforethe
nextfiresoff.Sincewedon'tneedthedestinationstoretrieveevents,wearefreefrom
sequentialevaluation.
Again,becausewe'reusingpartialapplicationtoachievethisresult,wemustensure
renderPageiscurriedoritwillnotwaitforboth Taskstofinish.Incidentally,ifyou'veever
hadtodosuchathingmanually,you'llappreciatetheastonishingsimplicityofthisinterface.
Thisisthekindofbeautifulcodethattakesusonestepclosertothesingularity.
Let'slookatanotherexample.
//$::String->IODOM
const$=selector=>newIO(()=>document.querySelector(selector));
//getVal::String->IOString
constgetVal=compose(map(prop('value')),$);
//signIn::String->String->Bool->User
constsignIn=curry((username,password,rememberMe)=>{/*signingin*/});
IO.of(signIn).ap(getVal('#email')).ap(getVal('#password')).ap(IO.of(false));
//IO({id:3,email:'gg@allin.com'})
signInisacurriedfunctionof3argumentssowehaveto apaccordingly.Witheach ap,
signInreceivesonemoreargumentuntilitiscompleteandruns.Wecancontinuethis
patternwithasmanyargumentsasnecessary.Anotherthingtonoteisthattwoarguments
endupnaturallyin IOwhereasthelastoneneedsalittlehelpfrom oftoliftitinto IO
since apexpectsthefunctionandallitsargumentstobeinthesametype.
Bro,DoYouEvenLift?
Let'sexamineapointfreewaytowritetheseapplicativecalls.Sinceweknow mapisequal
to of/ap,wecanwritegenericfunctionsthatwill apasmanytimesaswespecify:
constliftA2=curry((g,f1,f2)=>f1.map(g).ap(f2));
constliftA3=curry((g,f1,f2,f3)=>f1.map(g).ap(f2).ap(f3));
//liftA4,etc
Chapter10:ApplicativeFunctors
100
liftA2isastrangename.Itsoundslikeoneofthefinickyfreightelevatorsinarundown
factoryoravanityplateforacheaplimocompany.Onceenlightened,however,it'sself
explanatory:liftthesepiecesintotheapplicativefunctorworld.
WhenIfirstsawthis2-3-4nonsenseitstruckmeasuglyandunnecessary.Afterall,wecan
checkthearityoffunctionsinJavaScriptandbuildthisupdynamically.However,itisoften
usefultopartiallyapply liftA(N)itself,soitcannotvaryinargumentlength.
Let'sseethisinuse:
//checkEmail::User->EitherStringEmail
//checkName::User->EitherStringString
constuser={
name:'JohnDoe',
email:'blurp_blurp',
};
//createUser::Email->String->IOUser
constcreateUser=curry((email,name)=>{/*creating...*/});
Either.of(createUser).ap(checkEmail(user)).ap(checkName(user));
//Left('invalidemail')
liftA2(createUser,checkEmail(user),checkName(user));
//Left('invalidemail')
Since createUsertakestwoarguments,weusethecorresponding liftA2.Thetwo
statementsareequivalent,butthe liftA2versionhasnomentionof Either.Thismakes
itmoregenericandflexiblesincewearenolongermarriedtoaspecifictype.
Let'sseethepreviousexampleswrittenthisway:
liftA2(add,Maybe.of(2),Maybe.of(3));
//Maybe(5)
liftA2(renderPage,Http.get('/destinations'),Http.get('/events'));
//Task('<div>somepagewithdestandevents</div>')
liftA3(signIn,getVal('#email'),getVal('#password'),IO.of(false));
//IO({id:3,email:'gg@allin.com'})
Operators
InlanguageslikeHaskell,Scala,PureScript,andSwift,whereitispossibletocreateyour
owninfixoperatorsyoumayseesyntaxlikethis:
Chapter10:ApplicativeFunctors
101
--Haskell/PureScript
add<$>Right2<*>Right3
//JavaScript
map(add,Right(2)).ap(Right(3));
It'shelpfultoknowthat <$>is map(aka fmap)and <*>isjust ap.Thisallowsfora
morenaturalfunctionapplicationstyleandcanhelpremovesomeparenthesis.
FreeCanOpeners
Wehaven'tspokenmuchaboutderivedfunctions.Seeingasalloftheseinterfacesarebuilt
offofeachotherandobeyasetoflaws,wecandefinesomeweakerinterfacesintermsof
thestrongerones.
Forinstance,weknowthatanapplicativeisfirstafunctor,soifwehaveanapplicative
instance,surelywecandefineafunctorforourtype.
Thiskindofperfectcomputationalharmonyispossiblebecausewe'reworkingwithina
mathematicalframework.Mozartcouldn'thavedonebetterevenifhehadtorrentedAbleton
asachild.
Imentionedearlierthat of/apisequivalentto map.Wecanusethisknowledgetodefine
mapforfree:
Chapter10:ApplicativeFunctors
102
//mapderivedfromof/ap
X.prototype.map=functionmap(f){
returnthis.constructor.of(f).ap(this);
};
Monadsareatthetopofthefoodchain,sotospeak,soifwehave chain,wegetfunctor
andapplicativeforfree:
//mapderivedfromchain
X.prototype.map=functionmap(f){
returnthis.chain(a=>this.constructor.of(f(a)));
};
//apderivedfromchain/map
X.prototype.ap=functionap(other){
returnthis.chain(f=>other.map(f));
};
Ifwecandefineamonad,wecandefineboththeapplicativeandfunctorinterfaces.Thisis
quiteremarkableaswegetallofthesecanopenersforfree.Wecanevenexamineatype
andautomatethisprocess.
Itshouldbepointedoutthatpartof ap'sappealistheabilitytorunthingsconcurrentlyso
definingitvia chainismissingoutonthatoptimization.Despitethat,it'sgoodtohavean
immediateworkinginterfacewhileoneworksoutthebestpossibleimplementation.
Whynotjustusemonadsandbedonewithit,youask?It'sgoodpracticetoworkwiththe
levelofpoweryouneed,nomore,noless.Thiskeepscognitiveloadtoaminimumbyruling
outpossiblefunctionality.Forthisreason,it'sgoodtofavorapplicativesovermonads.
Monadshavetheuniqueabilitytosequencecomputation,assignvariables,andhaltfurther
executionallthankstothedownwardnestingstructure.Whenoneseesapplicativesinuse,
theyneedn'tconcernthemselveswithanyofthatbusiness.
Now,ontothelegalities...
Laws
Liketheothermathematicalconstructswe'veexplored,applicativefunctorsholdsome
usefulpropertiesforustorelyoninourdailycode.Firstoff,youshouldknowthat
applicativesare"closedundercomposition",meaning apwillneverchangecontainertypes
Chapter10:ApplicativeFunctors
103
onus(yetanotherreasontofavorovermonads).That'snottosaywecannothavemultiple
differenteffects-wecanstackourtypesknowingthattheywillremainthesameduringthe
entiretyofourapplication.
Todemonstrate:
consttOfM=compose(Task.of,Maybe.of);
liftA2(liftA2(concat),tOfM('RainyDaysandMondays'),tOfM('alwaysgetmedown'));
//Task(Maybe(RainyDaysandMondaysalwaysgetmedown))
See,noneedtoworryaboutdifferenttypesgettinginthemix.
Timetolookatourfavoritecategoricallaw:identity:
Identity
//identity
A.of(id).ap(v)===v;
Right,soapplying idallfromwithinafunctorshouldn'talterthevaluein v.Forexample:
constv=Identity.of('PillowPets');
Identity.of(id).ap(v)===v;
Identity.of(id)makesmechuckleatitsfutility.Anyway,what'sinterestingisthat,aswe've
alreadyestablished, of/apisthesameas mapsothislawfollowsdirectlyfromfunctor
identity: map(id)==id.
Thebeautyinusingtheselawsisthat,likeamilitantkindergartengymcoach,theyforceall
ofourinterfacestoplaywelltogether.
Homomorphism
//homomorphism
A.of(f).ap(A.of(x))===A.of(f(x));
Ahomomorphismisjustastructurepreservingmap.Infact,afunctorisjusta
homomorphismbetweencategoriesasitpreservestheoriginalcategory'sstructureunder
themapping.
Chapter10:ApplicativeFunctors
104
We'rereallyjuststuffingournormalfunctionsandvaluesintoacontainerandrunningthe
computationintheresoitshouldcomeasnosurprisethatwewillendupwiththesame
resultifweapplythewholethinginsidethecontainer(leftsideoftheequation)orapplyit
outside,thenplaceitinthere(rightside).
Aquickexample:
Either.of(toUpperCase).ap(Either.of('oreos'))===Either.of(toUpperCase('oreos'));
Interchange
Theinterchangelawstatesthatitdoesn'tmatterifwechoosetoliftourfunctionintotheleft
orrightsideof ap.
//interchange
v.ap(A.of(x))===A.of(f=>f(x)).ap(v);
Hereisanexample:
constv=Task.of(reverse);
constx='Sparklehorse';
v.ap(Task.of(x))===Task.of(f=>f(x)).ap(v);
Composition
Andfinallycompositionwhichisjustawaytocheckthatourstandardfunctioncomposition
holdswhenapplyinginsideofcontainers.
//composition
A.of(compose).ap(u).ap(v).ap(w)===u.ap(v.ap(w));
constu=IO.of(toUpperCase);
constv=IO.of(concat('&beyond'));
constw=IO.of('bloodbath');
IO.of(compose).ap(u).ap(v).ap(w)===u.ap(v.ap(w));
InSummary
Chapter10:ApplicativeFunctors
105
Agoodusecaseforapplicativesiswhenonehasmultiplefunctorarguments.Theygiveus
theabilitytoapplyfunctionstoargumentsallwithinthefunctorworld.Thoughwecould
alreadydosowithmonads,weshouldpreferapplicativefunctorswhenwearen'tinneedof
monadicspecificfunctionality.
We'realmostfinishedwithcontainerapis.We'velearnedhowto map, chain,andnow ap
functions.Inthenextchapter,we'lllearnhowtoworkbetterwithmultiplefunctorsand
disassembletheminaprincipledway.
Chapter11:TransformationAgain,Naturally
Exercises
Exercise
Writeafunctionthataddstwopossiblynullnumberstogetherusing`Maybe`and`ap`.
//safeAdd::MaybeNumber->MaybeNumber->MaybeNumber
constsafeAdd=undefined;
Exercise
Rewrite`safeAdd`fromexercise_btouse`liftA2`insteadof`ap`.
//safeAdd::MaybeNumber->MaybeNumber->MaybeNumber
constsafeAdd=undefined;
Forthenextexercise,weconsiderthefollowinghelpers:
constlocalStorage={
player1:{id:1,name:'Albert'},
player2:{id:2,name:'Theresa'},
};
//getFromCache::String->IOUser
constgetFromCache=x=>newIO(()=>localStorage[x]);
//game::User->User->String
constgame=curry((p1,p2)=>`${p1.name}vs${p2.name}`);
Chapter10:ApplicativeFunctors
106
Exercise
WriteanIOthatgetsbothplayer1andplayer2fromthecacheandstartsthegame.
//startGame::IOString
conststartGame=undefined;
Chapter10:ApplicativeFunctors
107
Chapter11:TransformAgain,Naturally
Weareabouttodiscussnaturaltransformationsinthecontextofpracticalutilityineveryday
code.Itjustsohappenstheyareapillarofcategorytheoryandabsolutelyindispensable
whenapplyingmathematicstoreasonaboutandrefactorourcode.Assuch,Ibelieveitis
mydutytoinformyouaboutthelamentableinjusticeyouareabouttowitnessundoubtedly
duetomylimitedscope.Let'sbegin.
CurseThisNest
I'dliketoaddresstheissueofnesting.Nottheinstinctiveurgefeltbysoontobemothers
whereintheytidyandrearrangewithobsessivecompulsion,butthe...wellactually,cometo
thinkofit,thatisn'tfarfromthemarkaswe'llseeinthecomingchapters...Inanycase,what
Imeanbynestingistohavetwoormoredifferenttypesallhuddledtogetheraroundavalue,
cradlingitlikeanewborn,asitwere.
Right(Maybe('b'));
IO(Task(IO(1000)));
[Identity('beethousand')];
Untilnow,we'vemanagedtoevadethiscommonscenariowithcarefullycraftedexamples,
butinpractice,asonecodes,typestendtotanglethemselvesuplikeearbudsinan
exorcism.Ifwedon'tmeticulouslykeepourtypesorganizedaswegoalong,ourcodewill
readhairierthanabeatnikinacatcafé.
ASituationalComedy
Chapter11:TransformAgain,Naturally
108
//getValue::Selector->TaskError(MaybeString)
//postComment::String->TaskErrorComment
//validate::String->EitherValidationErrorString
//saveComment::()->TaskError(Maybe(EitherValidationError(TaskErrorComment)
))
constsaveComment=compose(
map(map(map(postComment))),
map(map(validate)),
getValue('#comment'),
);
Thegangisallhere,muchtoourtypesignature'sdismay.Allowmetobrieflyexplainthe
code.Westartbygettingtheuserinputwith getValue('#comment')whichisanactionwhich
retrievestextonanelement.Now,itmighterrorfindingtheelementorthevaluestringmay
notexistsoitreturns TaskError(MaybeString).Afterthat,wemust mapoverboththe
Taskandthe Maybetopassourtextto validate,whichinturn,givesusback Eithera
ValidationErrororour String.Thenontomappingfordaystosendthe Stringinour
current TaskError(Maybe(EitherValidationErrorString))into postCommentwhich
returnsourresulting Task.
Whatafrightfulmess.Acollageofabstracttypes,amateurtypeexpressionism,polymorphic
Pollock,monolithicMondrian.Therearemanysolutionstothiscommonissue.Wecan
composethetypesintoonemonstrouscontainer,sortand joinafew,homogenizethem,
deconstructthem,andsoon.Inthischapter,we'llfocusonhomogenizingthemvianatural
transformations.
AllNatural
ANaturalTransformationisa"morphismbetweenfunctors",thatis,afunctionwhich
operatesonthecontainersthemselves.Typewise,itisafunction (Functorf,Functorg)=>
fa->ga.Whatmakesitspecialisthatwecannot,foranyreason,peekatthecontentsof
ourfunctor.Thinkofitasanexchangeofhighlyclassifiedinformation-thetwoparties
oblivioustowhat'sinthesealedmanilaenvelopestamped"topsecret".Thisisastructural
operation.Afunctorialcostumechange.Formally,anaturaltransformationisanyfunctionfor
whichthefollowingholds:
Chapter11:TransformAgain,Naturally
109
orincode:
//nt::(Functorf,Functorg)=>fa->ga
compose(map(f),nt)===compose(nt,map(f));
Boththediagramandthecodesaythesamething:Wecanrunournaturaltransformation
then mapor mapthenrunournaturaltransformationandgetthesameresult.Incidentally,
thatfollowsfromafreetheoremthoughnaturaltransformations(andfunctors)arenotlimited
tofunctionsontypes.
PrincipledTypeConversions
Asprogrammerswearefamiliarwithtypeconversions.Wetransformtypeslike Strings
into Booleansand Integersinto Floats(thoughJavaScriptonlyhas Numbers).The
differencehereissimplythatwe'reworkingwithalgebraiccontainersandwehavesome
theoryatourdisposal.
Let'slookatsomeoftheseasexamples:
Chapter11:TransformAgain,Naturally
110
//idToMaybe::Identitya->Maybea
constidToMaybe=x=>Maybe.of(x.$value);
//idToIO::Identitya->IOa
constidToIO=x=>IO.of(x.$value);
//eitherToTask::Eitherab->Taskab
consteitherToTask=either(Task.rejected,Task.of);
//ioToTask::IOa->Task()a
constioToTask=x=>newTask((reject,resolve)=>resolve(x.unsafePerform()));
//maybeToTask::Maybea->Task()a
constmaybeToTask=x=>(x.isNothing?Task.rejected():Task.of(x.$value));
//arrayToMaybe::[a]->Maybea
constarrayToMaybe=x=>Maybe.of(x[0]);
Seetheidea?We'rejustchangingonefunctortoanother.Wearepermittedtolose
informationalongthewaysolongasthevaluewe'll mapdoesn'tgetlostintheshapeshift
shuffle.Thatisthewholepoint: mapmustcarryon,accordingtoourdefinition,evenafter
thetransformation.
Onewaytolookatitisthatwearetransformingoureffects.Inthatlight,wecanview
ioToTaskasconvertingsynchronoustoasynchronousor arrayToMaybefrom
nondeterminismtopossiblefailure.Notethatwecannotconvertasynchronousto
synchronousinJavaScriptsowecannotwrite taskToIO-thatwouldbeasupernatural
transformation.
FeatureEnvy
Supposewe'dliketousesomefeaturesfromanothertypelike sortByona List.Natural
transformationsprovideanicewaytoconverttothetargettypeknowingour mapwillbe
sound.
//arrayToList::[a]->Lista
constarrayToList=List.of;
constdoListyThings=compose(sortBy(h),filter(g),arrayToList,map(f));
constdoListyThings_=compose(sortBy(h),filter(g),map(f),arrayToList);//lawappl
ied
Awiggleofournose,threetapsofourwand,dropin arrayToList,andvoilà!Our [a]isa
Listaandwecan sortByifweplease.
Chapter11:TransformAgain,Naturally
111
Also,itbecomeseasiertooptimize/fuseoperationsbymoving map(f)totheleftofnatural
transformationasshownin doListyThings_.
IsomorphicJavaScript
Whenwecancompletelygobackandforthwithoutlosinganyinformation,thatisconsidered
anisomorphism.That'sjustafancywordfor"holdsthesamedata".Wesaythattwotypes
areisomorphicifwecanprovidethe"to"and"from"naturaltransformationsasproof:
//promiseToTask::Promiseab->Taskab
constpromiseToTask=x=>newTask((reject,resolve)=>x.then(resolve).catch(reject)
);
//taskToPromise::Taskab->Promiseab
consttaskToPromise=x=>newPromise((resolve,reject)=>x.fork(reject,resolve));
constx=Promise.resolve('ring');
taskToPromise(promiseToTask(x))===x;
consty=Task.of('rabbit');
promiseToTask(taskToPromise(y))===y;
Q.E.D. Promiseand Taskareisomorphic.Wecanalsowritea listToArrayto
complementour arrayToListandshowthattheyaretoo.Asacounterexample,
arrayToMaybeisnotanisomorphismsinceitlosesinformation:
//maybeToArray::Maybea->[a]
constmaybeToArray=x=>(x.isNothing?[]:[x.$value]);
//arrayToMaybe::[a]->Maybea
constarrayToMaybe=x=>Maybe.of(x[0]);
constx=['elviscostello','theattractions'];
//notisomorphic
maybeToArray(arrayToMaybe(x));//['elviscostello']
//butisanaturaltransformation
compose(arrayToMaybe,map(replace('elvis','lou')))(x);//Just('loucostello')
//==
compose(map(replace('elvis','lou'),arrayToMaybe))(x);//Just('loucostello')
Theyareindeednaturaltransformations,however,since maponeithersideyieldsthesame
result.Imentionisomorphismshere,mid-chapterwhilewe'reonthesubject,butdon'tlet
thatfoolyou,theyareanenormouslypowerfulandpervasiveconcept.Anyways,let'smove
Chapter11:TransformAgain,Naturally
112
on.
ABroaderDefinition
Thesestructuralfunctionsaren'tlimitedtotypeconversionsbyanymeans.
Hereareafewdifferentones:
reverse::[a]->[a]
join::(Monadm)=>m(ma)->ma
head::[a]->a
of::a->fa
Thenaturaltransformationlawsholdforthesefunctionstoo.Onethingthatmighttripyouup
isthat head::[a]->acanbeviewedas head::[a]->Identitya.Wearefreeto
insert Identitywhereverwepleasewhilstprovinglawssincewecan,inturn,provethat
aisisomorphicto Identitya(see,Itoldyouisomorphismswerepervasive).
OneNestingSolution
Backtoourcomedictypesignature.Wecansprinkleinsomenaturaltransformations
throughoutthecallingcodetocoerceeachvaryingtypesotheyareuniformand,therefore,
joinable.
//getValue::Selector->TaskError(MaybeString)
//postComment::String->TaskErrorComment
//validate::String->EitherValidationErrorString
//saveComment::()->TaskErrorComment
constsaveComment=compose(
chain(postComment),
chain(eitherToTask),
map(validate),
chain(maybeToTask),
getValue('#comment'),
);
Sowhatdowehavehere?We'vesimplyadded chain(maybeToTask)and
chain(eitherToTask).Bothhavethesameeffect;theynaturallytransformthefunctorour
Taskisholdingintoanother Taskthen jointhetwo.Likepigeonspikesonawindow
Chapter11:TransformAgain,Naturally
113
ledge,weavoidnestingrightatthesource.Astheysayinthecityoflight,"Mieuxvaut
prévenirqueguérir"-anounceofpreventionisworthapoundofcure.
InSummary
Naturaltransformationsarefunctionsonourfunctorsthemselves.Theyareanextremely
importantconceptincategorytheoryandwillstarttoappeareverywhereoncemore
abstractionsareadopted,butfornow,we'vescopedthemtoafewconcreteapplications.As
wesaw,wecanachievedifferenteffectsbyconvertingtypeswiththeguaranteethatour
compositionwillhold.Theycanalsohelpuswithnestedtypes,althoughtheyhavethe
generaleffectofhomogenizingourfunctorstothelowestcommondenominator,whichin
practice,isthefunctorwiththemostvolatileeffects( Taskinmostcases).
Thiscontinualandtedioussortingoftypesisthepricewepayforhavingmaterializedthem-
summonedthemfromtheether.Ofcourse,impliciteffectsaremuchmoreinsidiousandso
herewearefightingthegoodfight.We'llneedafewmoretoolsinourtacklebeforewecan
reelinthelargertypeamalgamations.Nextup,we'lllookatreorderingourtypeswith
Traversable.
Chapter12:TraversingtheStone
Exercises
Exercise
Writeanaturaltransformationthatconverts`Eitherba`to`Maybea`
//eitherToMaybe::Eitherba->Maybea
consteitherToMaybe=undefined;
//eitherToTask::Eitherab->Taskab
consteitherToTask=either(Task.rejected,Task.of);
Exercise
Using`eitherToTask`,simplify`findNameById`toremovethenested`Either`.
Chapter11:TransformAgain,Naturally
114
//findNameById::Number->TaskError(EitherErrorUser)
constfindNameById=compose(map(map(prop('name'))),findUserById);
Asareminder,thefollowingfunctionsareavailableintheexercise'scontext:
split::String->String->[String]
intercalate::String->[String]->String
Exercise
WritetheisomorphismsbetweenStringand[Char].
//strToList::String->[Char]
conststrToList=undefined;
//listToStr::[Char]->String
constlistToStr=undefined;
Chapter11:TransformAgain,Naturally
115
Chapter12:TraversingtheStone
Sofar,inourcirqueduconteneur,you'veseenustametheferociousfunctor,bendingitto
ourwilltoperformanyoperationthatstrikesourfancy.You'vebeendazzledbythejuggling
ofmanydangerouseffectsatonceusingfunctionapplicationtocollecttheresults.Satthere
inamazementascontainersvanishedinthinairbyjoiningthemtogether.Atthesideeffect
sideshow,we'veseenthemcomposedintoone.Andmostrecently,we'veventuredbeyond
what'snaturalandtransformedonetypeintoanotherbeforeyourveryeyes.
Andnowforournexttrick,we'lllookattraversals.We'llwatchtypessoaroveroneanother
asiftheyweretrapezeartistsholdingourvalueintact.We'llreordereffectslikethetrolleysin
atilt-a-whirl.Whenourcontainersgetintertwinedlikethelimbsofacontortionist,wecanuse
thisinterfacetostraightenthingsout.We'llwitnessdifferenteffectswithdifferentorderings.
Fetchmemypantaloonsandslidewhistle,let'sgetstarted.
Typesn'Types
Let'sgetweird:
//readFile::FileName->TaskErrorString
//firstWords::String->String
constfirstWords=compose(join(''),take(3),split(''));
//tldr::FileName->TaskErrorString
consttldr=compose(map(firstWords),readFile);
map(tldr,['file1','file2']);
//[Task('hailthemonarchy'),Task('smashthepatriarchy')]
Herewereadabunchoffilesandendupwithauselessarrayoftasks.Howmightwefork
eachoneofthese?Itwouldbemostagreeableifwecouldswitchthetypesaroundtohave
TaskError[String]insteadof [TaskErrorString].Thatway,we'dhaveonefuturevalue
holdingalltheresults,whichismuchmoreamenabletoourasyncneedsthanseveralfuture
valuesarrivingattheirleisure.
Here'sonelastexampleofastickysituation:
Chapter12:TraversingtheStone
116
//getAttribute::String->Node->MaybeString
//$::Selector->IONode
//getControlNode::IO(Maybe(IONode))
constgetControlNode=compose(map(map($)),map(getAttribute('aria-controls')),$);
Lookatthose IOslongingtobetogether.It'dbejustlovelyto jointhem,letthemdance
cheektocheek,butalasa Maybestandsbetweenthemlikeachaperoneatprom.Ourbest
moveherewouldbetoshifttheirpositionsnexttooneanother,thatwayeachtypecanbe
togetheratlastandoursignaturecanbesimplifiedto IO(MaybeNode).
TypeFengShui
TheTraversableinterfaceconsistsoftwogloriousfunctions: sequenceand traverse.
Let'srearrangeourtypesusing sequence:
sequence(List.of,Maybe.of(['thefacts']));//[Just('thefacts')]
sequence(Task.of,newMap({a:Task.of(1),b:Task.of(2)}));//Task(Map({a:1,b:2
}))
sequence(IO.of,Either.of(IO.of('bucklemyshoe')));//IO(Right('bucklemyshoe'))
sequence(Either.of,[Either.of('wing')]);//Right(['wing'])
sequence(Task.of,left('wing'));//Task(Left('wing'))
Seewhathashappenedhere?Ournestedtypegetsturnedinsideoutlikeapairofleather
trousersonahumidsummernight.Theinnerfunctorisshiftedtotheoutsideandviceversa.
Itshouldbeknownthat sequenceisbitparticularaboutitsarguments.Itlookslikethis:
//sequence::(Traversablet,Applicativef)=>(a->fa)->t(fa)->f(ta)
constsequence=curry((of,x)=>x.sequence(of));
Let'sstartwiththesecondargument.ItmustbeaTraversableholdinganApplicative,which
soundsquiterestrictive,butjustsohappenstobethecasemoreoftenthannot.Itisthe t
(fa)whichgetsturnedintoa f(ta).Isn'tthatexpressive?It'sclearasdaythetwo
typesdo-si-doaroundeachother.Thatfirstargumentthereismerelyacrutchandonly
necessaryinanuntypedlanguage.Itisatypeconstructor(ourof)providedsothatwecan
invertmap-reluctanttypeslike Left-moreonthatinaminute.
Using sequence,wecanshifttypesaroundwiththeprecisionofasidewalkthimblerigger.
Buthowdoesitwork?Let'slookathowatype,say Either,wouldimplementit:
Chapter12:TraversingtheStone
117
classRightextendsEither{
//...
sequence(of){
returnthis.$value.map(Either.of);
}
}
Ahyes,ifour $valueisafunctor(itmustbeanapplicative,infact),wecansimply mapour
constructortoleapfrogthetype.
Youmayhavenoticedthatwe'veignoredthe ofentirely.Itispassedinfortheoccasion
wheremappingisfutile,asisthecasewith Left:
classLeftextendsEither{
//...
sequence(of){
returnof(this);
}
}
We'dlikethetypestoalwaysendupinthesamearrangement,thereforeitisnecessaryfor
typeslike Leftwhodon'tactuallyholdourinnerapplicativetogetalittlehelpindoingso.
TheApplicativeinterfacerequiresthatwefirsthaveaPointedFunctorsowe'llalwayshave
a oftopassin.Inalanguagewithatypesystem,theoutertypecanbeinferredfromthe
signatureanddoesnotneedtobeexplicitlygiven.
EffectAssortment
Differentordershavedifferentoutcomeswhereourcontainersareconcerned.IfIhave
[Maybea],that'sacollectionofpossiblevalueswhereasifIhavea Maybe[a],that'sa
possiblecollectionofvalues.Theformerindicateswe'llbeforgivingandkeep"thegood
ones",whilethelattermeansit'san"allornothing"typeofsituation.Likewise, EitherError
(TaskErrora)couldrepresentaclientsidevalidationand TaskError(EitherErrora)
couldbeaserversideone.Typescanbeswappedtogiveusdifferenteffects.
//fromPredicate::(a->Bool)->a->Eitheraa
//partition::(a->Bool)->[a]->[Eitheraa]
constpartition=f=>map(fromPredicate(f));
//validate::(a->Bool)->[a]->Eithera[a]
constvalidate=f=>traverse(Either.of,fromPredicate(f));
Chapter12:TraversingtheStone
118
Herewehavetwodifferentfunctionsbasedonifwe mapor traverse.Thefirst,
partitionwillgiveusanarrayof Leftsand Rightsaccordingtothepredicatefunction.
Thisisusefultokeeppreciousdataaroundforfutureuseratherthanfilteringitoutwiththe
bathwater. validateinsteadwillonlymoveforwardifeverythingishunkydory.Bychoosing
adifferenttypeorder,wegetdifferentbehavior.
WaltzoftheTypes
Timetorevisitandcleanourinitialexamples.
//readFile::FileName->TaskErrorString
//firstWords::String->String
constfirstWords=compose(join(''),take(3),split(''));
//tldr::FileName->TaskErrorString
consttldr=compose(map(firstWords),readFile);
traverse(Task.of,tldr,['file1','file2']);
//Task(['hailthemonarchy','smashthepatriarchy']);
Using traverseinsteadof map,we'vesuccessfullyherdedthoseunruly Tasksintoanice
coordinatedarrayofresults.Thisislike Promise.all(),ifyou'refamiliar,exceptitisn'tjusta
one-off,customfunction,no,thisworksforanytraversabletype.Thesemathematicalapis
tendtocapturemostthingswe'dliketodoinaninteroperable,reusableway,ratherthan
eachlibraryreinventingthesefunctionsforasingletype.
Let'scleanupthelastexampleforclosure(no,notthatkind):
//getAttribute::String->Node->MaybeString
//$::Selector->IONode
//getControlNode::IO(MaybeNode)
constgetControlNode=compose(chain(traverse(IO.of,$)),map(getAttribute('aria-contr
ols')),$);
Insteadof map(map($))wehave chain(traverse(IO.of,$))whichinvertsourtypesasit
mapsthenflattensthetwo IOsvia chain.
NoLawandOrder
Chapter12:TraversingtheStone
119
Wellnow,beforeyougetalljudgementalandbangthebackspacebuttonlikeagavelto
retreatfromthechapter,takeamomenttorecognizethattheselawsareusefulcode
guarantees.'Tismyconjecturethatthegoalofmostprogramarchitectureisanattemptto
placeusefulrestrictionsonourcodetonarrowthepossibilities,toguideusintotheanswers
asdesignersandreaders.
Aninterfacewithoutlawsismerelyindirection.Likeanyothermathematicalstructure,we
mustexposepropertiesforourownsanity.Thishasasimilareffectasencapsulationsinceit
protectsthedata,enablingustoswapouttheinterfacewithanotherlawabidingcitizen.
Comealongnow,we'vegotsomelawstosussout.
Identity
constidentity1=compose(sequence(Identity.of),map(Identity.of));
constidentity2=Identity.of;
//testitoutwithRight
identity1(Either.of('stuff'));
//Identity(Right('stuff'))
identity2(Either.of('stuff'));
//Identity(Right('stuff'))
Thisshouldbestraightforward.Ifweplacean Identityinourfunctor,thenturnitinsideout
with sequencethat'sthesameasjustplacingitontheoutsidetobeginwith.Wechose
Rightasourguineapigasitiseasytotrythelawandinspect.Anarbitraryfunctorthereis
normal,however,theuseofaconcretefunctorhere,namely Identityinthelawitselfmight
raisesomeeyebrows.Rememberacategoryisdefinedbymorphismsbetweenitsobjects
thathaveassociativecompositionandidentity.Whendealingwiththecategoryoffunctors,
naturaltransformationsarethemorphismsand Identityis,wellidentity.The Identity
functorisasfundamentalindemonstratinglawsasour composefunction.Infact,weshould
giveuptheghostandfollowsuitwithourComposetype:
Composition
Chapter12:TraversingtheStone
120
constcomp1=compose(sequence(Compose.of),map(Compose.of));
constcomp2=(Fof,Gof)=>compose(Compose.of,map(sequence(Gof)),sequence(Fof));
//Testitoutwithsometypeswehavelyingaround
comp1(Identity(Right([true])));
//Compose(Right([Identity(true)]))
comp2(Either.of,Array)(Identity(Right([true])));
//Compose(Right([Identity(true)]))
Thislawpreservescompositionasonewouldexpect:ifweswapcompositionsoffunctors,
weshouldn'tseeanysurprisessincethecompositionisafunctoritself.Wearbitrarilychose
true, Right, Identity,and Arraytotestitout.Librarieslikequickcheckorjsverifycan
helpustestthelawbyfuzztestingtheinputs.
Asanaturalconsequenceoftheabovelaw,wegettheabilitytofusetraversals,whichis
nicefromaperformancestandpoint.
Naturality
constnatLaw1=(of,nt)=>compose(nt,sequence(of));
constnatLaw2=(of,nt)=>compose(sequence(of),map(nt));
//testwitharandomnaturaltransformationandourfriendlyIdentity/Rightfunctors.
//maybeToEither::Maybea->Either()a
constmaybeToEither=x=>(x.$value?newRight(x.$value):newLeft());
natLaw1(Maybe.of,maybeToEither)(Identity.of(Maybe.of('barlowone')));
//Right(Identity('barlowone'))
natLaw2(Either.of,maybeToEither)(Identity.of(Maybe.of('barlowone')));
//Right(Identity('barlowone'))
Thisissimilartoouridentitylaw.Ifwefirstswingthetypesaroundthenrunanatural
transformationontheoutside,thatshouldequalmappinganaturaltransformation,then
flippingthetypes.
Anaturalconsequenceofthislawis:
traverse(A.of,A.of)===A.of;
Which,again,isnicefromaperformancestandpoint.
Chapter12:TraversingtheStone
121
InSummary
Traversableisapowerfulinterfacethatgivesustheabilitytorearrangeourtypeswiththe
easeofatelekineticinteriordecorator.Wecanachievedifferenteffectswithdifferentorders
aswellasironoutthosenastytypewrinklesthatkeepusfrom joiningthemdown.Next,
we'lltakeabitofadetourtoseeoneofthemostpowerfulinterfacesoffunctional
programmingandperhapsevenalgebraitself:Monoidsbringitalltogether
Exercises
Consideringthefollowingelements:
//httpGet::Route->TaskErrorJSON
//routes::MapRouteRoute
constroutes=newMap({'/':'/','/about':'/about'});
Exercise
Usethetraversableinterfacetochangethetypesignatureof`getJsons`toMapRoute
Route→TaskError(MapRouteJSON)
//getRoutes::MapRouteRoute->MapRoute(TaskErrorJSON)
constgetJsons=map(httpGet);
Wenowdefinethefollowingvalidationfunction:
//validate::Player->EitherStringPlayer
constvalidate=player=>(player.name?Either.of(player):left('musthavename'));
Exercise
Usingtraversable,andthe`validate`function,update`startGame`(anditssignature)to
onlystartthegameifallplayersarevalid
//startGame::[Player]->[EitherErrorString]
conststartGame=compose(map(always('gamestarted!')),map(validate));
Chapter12:TraversingtheStone
122
Finally,weconsidersomefile-systemhelpers:
//readfile::String->TaskErrorString
//readdir::String->TaskError[String]
Exercise
UsetraversabletorearrangeandflattenthenestedTasks&Maybe
//readFirst::String->TaskError(TaskError(MaybeString))
constreadFirst=compose(map(map(readfile('utf-8'))),map(safeHead),readdir);
Chapter12:TraversingtheStone
123
AppendixA:EssentialFunctionsSupport
Inthisappendix,you'llfindsomebasicJavaScriptimplementationsofvariousfunctions
describedinthebook.Keepinmindthattheseimplementationsmaynotbethefastestor
themostefficientimplementationoutthere;theysolelyserveaneducationalpurpose.
Inordertofindfunctionsthataremoreproduction-ready,haveapeakatramda,lodash,or
folktale.
NotethatsomefunctionsalsorefertoalgebraicstructuresdefinedintheAppendixB
always
//always::a->b->a
constalways=curry((a,b)=>a);
compose
//compose::((a->b),(b->c),...,(y->z))->a->z
functioncompose(...fns){
constn=fns.length;
returnfunction$compose(...args){
let$args=args;
for(leti=n-1;i>=0;i-=1){
$args=[fns[i].call(null,...$args)];
}
return$args[0];
};
}
curry
AppendixA:EssentialFunctionsSupport
124
//curry::((a,b,...)->c)->a->b->...->c
functioncurry(fn){
constarity=fn.length;
returnfunction$curry(...args){
if(args.length<arity){
return$curry.bind(null,...args);
}
returnfn.call(null,...args);
};
}
either
//either::(a->c)->(b->c)->Eitherab->c
consteither=curry((f,g,e)=>{
if(e.isLeft){
returnf(e.$value);
}
returng(e.$value);
});
identity
//identity::x->x
constidentity=x=>x;
inspect
AppendixA:EssentialFunctionsSupport
125
//inspect::a->String
functioninspect(x){
if(x&&typeofx.inspect==='function'){
returnx.inspect();
}
functioninspectFn(f){
returnf.name?f.name:f.toString();
}
functioninspectTerm(t){
switch(typeoft){
case'string':
return`'${t}'`;
case'object':{
constts=Object.keys(t).map(k=>[k,inspect(t[k])]);
return`{${ts.map(kv=>kv.join(':')).join(',')}}`;
}
default:
returnString(t);
}
}
functioninspectArgs(args){
returnArray.isArray(args)?`[${args.map(inspect).join(',')}]`:inspectTerm(arg
s);
}
return(typeofx==='function')?inspectFn(x):inspectArgs(x);
}
left
//left::a->Eitherab
constleft=a=>newLeft(a);
liftA*
//liftA2::(Applicativef)=>(a1->a2->b)->fa1->fa2->fb
constliftA2=curry((fn,a1,a2)=>a1.map(fn).ap(a2));
//liftA3::(Applicativef)=>(a1->a2->a3->b)->fa1->fa2->fa3->fb
constliftA3=curry((fn,a1,a2,a3)=>a1.map(fn).ap(a2).ap(a3));
AppendixA:EssentialFunctionsSupport
126
maybe
//maybe::b->(a->b)->Maybea->b
constmaybe=curry((v,f,m)=>{
if(m.isNothing){
returnv;
}
returnf(m.$value);
});
nothing
//nothing::()->Maybea
constnothing=()=>Maybe.of(null);
reject
//reject::a->Taskab
constreject=a=>Task.rejected(a);
AppendixA:EssentialFunctionsSupport
127
AppendixB:AlgebraicStructuresSupport
Inthisappendix,you'llfindsomebasicJavaScriptimplementationsofvariousalgebraic
structuresdescribedinthebook.Keepinmindthattheseimplementationsmaynotbethe
fastestorthemostefficientimplementationoutthere;theysolelyserveaneducational
purpose.
Inordertofindstructuresthataremoreproduction-ready,haveapeakatfolktaleorfantasy-
land.
NotethatsomemethodsalsorefertofunctionsdefinedintheAppendixA
Compose
constcreateCompose=curry((F,G)=>classCompose{
constructor(x){
this.$value=x;
}
inspect(){
return`Compose(${inspect(this.$value)})`;
}
//-----Pointed(ComposeFG)
staticof(x){
returnnewCompose(F(G(x)));
}
//-----Functor(ComposeFG)
map(fn){
returnnewCompose(this.$value.map(x=>x.map(fn)));
}
//-----Applicative(ComposeFG)
ap(f){
returnf.map(this.$value);
}
});
Either
classEither{
AppendixB:AlgebraicStructuresSupport
128
constructor(x){
this.$value=x;
}
//-----Pointed(Eithera)
staticof(x){
returnnewRight(x);
}
}
classLeftextendsEither{
getisLeft(){
returntrue;
}
getisRight(){
returnfalse;
}
staticof(x){
thrownewError('`of`calledonclassLeft(value)insteadofEither(type)');
}
inspect(){
return`Left(${inspect(this.$value)})`;
}
//-----Functor(Eithera)
map(){
returnthis;
}
//-----Applicative(Eithera)
ap(){
returnthis;
}
//-----Monad(Eithera)
chain(){
returnthis;
}
join(){
returnthis;
}
//-----Traversable(Eithera)
sequence(of){
returnof(this);
}
traverse(of,fn){
returnof(this);
AppendixB:AlgebraicStructuresSupport
129
}
}
classRightextendsEither{
getisLeft(){
returnfalse;
}
getisRight(){
returntrue;
}
staticof(x){
thrownewError('`of`calledonclassRight(value)insteadofEither(type)');
}
inspect(){
return`Right(${inspect(this.$value)})`;
}
//-----Functor(Eithera)
map(fn){
returnEither.of(fn(this.$value));
}
//-----Applicative(Eithera)
ap(f){
returnf.map(this.$value);
}
//-----Monad(Eithera)
chain(fn){
returnfn(this.$value);
}
join(){
returnthis.$value;
}
//-----Traversable(Eithera)
sequence(of){
returnthis.traverse(of,identity);
}
traverse(of,fn){
fn(this.$value).map(Either.of);
}
}
Identity
AppendixB:AlgebraicStructuresSupport
130
classIdentity{
constructor(x){
this.$value=x;
}
inspect(){
return`Identity(${inspect(this.$value)})`;
}
//-----PointedIdentity
staticof(x){
returnnewIdentity(x);
}
//-----FunctorIdentity
map(fn){
returnIdentity.of(fn(this.$value));
}
//-----ApplicativeIdentity
ap(f){
returnf.map(this.$value);
}
//-----MonadIdentity
chain(fn){
returnthis.map(fn).join();
}
join(){
returnthis.$value;
}
//-----TraversableIdentity
sequence(of){
returnthis.traverse(of,identity);
}
traverse(of,fn){
returnfn(this.$value).map(Identity.of);
}
}
IO
AppendixB:AlgebraicStructuresSupport
131
classIO{
constructor(fn){
this.unsafePerformIO=fn;
}
inspect(){
return`IO(?)`;
}
//-----PointedIO
staticof(x){
returnnewIO(()=>x);
}
//-----FunctorIO
map(fn){
returnnewIO(compose(fn,this.unsafePerformIO));
}
//-----ApplicativeIO
ap(f){
returnthis.chain(fn=>f.map(fn));
}
//-----MonadIO
chain(fn){
returnthis.map(fn).join();
}
join(){
returnthis.unsafePerformIO();
}
}
List
AppendixB:AlgebraicStructuresSupport
132
classList{
constructor(xs){
this.$value=xs;
}
inspect(){
return`List(${inspect(this.$value)})`;
}
concat(x){
returnnewList(this.$value.concat(x));
}
//-----PointedList
staticof(x){
returnnewList([x]);
}
//-----FunctorList
map(fn){
returnnewList(this.$value.map(fn));
}
//-----TraversableList
sequence(of){
returnthis.traverse(of,identity);
}
traverse(of,fn){
returnthis.$value.reduce(
(f,a)=>fn(a).map(b=>bs=>bs.concat(b)).ap(f),
of(newList([])),
);
}
}
Map
AppendixB:AlgebraicStructuresSupport
133
classMap{
constructor(x){
this.$value=x;
}
inspect(){
return`Map(${inspect(this.$value)})`;
}
insert(k,v){
constsingleton={};
singleton[k]=v;
returnMap.of(Object.assign({},this.$value,singleton));
}
reduceWithKeys(fn,zero){
returnObject.keys(this.$value)
.reduce((acc,k)=>fn(acc,this.$value[k],k),zero);
}
//-----Functor(Mapa)
map(fn){
returnthis.reduceWithKeys(
(m,v,k)=>m.insert(k,fn(v)),
newMap({}),
);
}
//-----Traversable(Mapa)
sequence(of){
returnthis.traverse(of,identity);
}
traverse(of,fn){
returnthis.reduceWithKeys(
(f,a,k)=>fn(a).map(b=>m=>m.insert(k,b)).ap(f),
of(newMap({})),
);
}
}
Maybe
Notethat Maybecouldalsobedefinedinasimilarfashionaswedidfor Eitherwith
twochildclasses Justand Nothing.Thisissimplyadifferentflavor.
AppendixB:AlgebraicStructuresSupport
134
classMaybe{
getisNothing(){
returnthis.$value===null||this.$value===undefined;
}
getisJust(){
return!this.isNothing;
}
constructor(x){
this.$value=x;
}
inspect(){
return`Maybe(${inspect(this.$value)})`;
}
//-----PointedMaybe
staticof(x){
returnnewMaybe(x);
}
//-----FunctorMaybe
map(fn){
returnthis.isNothing?this:Maybe.of(fn(this.$value));
}
//-----ApplicativeMaybe
ap(f){
returnthis.isNothing?this:f.map(this.$value);
}
//-----MonadMaybe
chain(fn){
returnthis.map(fn).join();
}
join(){
returnthis.isNothing?this:this.$value;
}
//-----TraversableMaybe
sequence(of){
this.traverse(of,identity);
}
traverse(of,fn){
returnthis.isNothing?of(this):fn(this.$value).map(Maybe.of);
}
}
AppendixB:AlgebraicStructuresSupport
135
Task
classTask{
constructor(fork){
this.fork=fork;
}
inspect(){
return'Task(?)';
}
staticrejected(x){
returnnewTask((reject,_)=>reject(x));
}
//-----Pointed(Taska)
staticof(x){
returnnewTask((_,resolve)=>resolve(x));
}
//-----Functor(Taska)
map(fn){
returnnewTask((reject,resolve)=>this.fork(reject,compose(resolve,fn)));
}
//-----Applicative(Taska)
ap(f){
returnthis.chain(fn=>f.map(fn));
}
//-----Monad(Taska)
chain(fn){
returnnewTask((reject,resolve)=>this.fork(reject,x=>fn(x).fork(reject,res
olve)));
}
join(){
returnthis.chain(identity);
}
}
AppendixB:AlgebraicStructuresSupport
136
AppendixC:PointfreeUtilities
Inthisappendix,you'llfindpointfreeversionsofratherclassicJavaScriptfunctions
describedinthebook.Allofthefollowingfunctionsareseeminglyavailableinexercises,as
partoftheglobalcontext.Keepinmindthattheseimplementationsmaynotbethefastestor
themostefficientimplementationoutthere;theysolelyserveaneducationalpurpose.
Inordertofindfunctionsthataremoreproduction-ready,haveapeakatramda,lodash,or
folktale.
Notethatfunctionsrefertothe curry& composefunctionsdefinedinAppendixA
add
//add::Number->Number->Number
constadd=curry((a,b)=>a+b);
chain
//chain::Monadm=>(a->mb)->ma->mb
constchain=curry((fn,m)=>m.chain(fn));
concat
//concat::String->String->String
constconcat=curry((a,b)=>a.concat(b));
eq
//eq::Eqa=>a->a->Boolean
consteq=curry((a,b)=>a===b);
filter
AppendixC:PointfreeUtilities
137
//filter::(a->Boolean)->[a]->[a]
constfilter=curry((fn,xs)=>xs.filter(fn));
flip
//flip::(a->b)->(b->a)
constflip=curry((fn,a,b)=>fn(b,a));
forEach
//forEach::(a->())->[a]->()
constforEach=curry((fn,xs)=>xs.forEach(fn));
head
//head::[a]->a
consthead=xs=>xs[0];
intercalate
//intercalate::String->[String]->String
constintercalate=curry((str,xs)=>xs.join(str));
join
//join::Monadm=>m(ma)->ma
constjoin=m=>m.join();
last
//last::[a]->a
constlast=xs=>xs[xs.length-1];
AppendixC:PointfreeUtilities
138
map
//map::Functorf=>(a->b)->fa->fb
constmap=curry((fn,f)=>f.map(fn));
match
//match::RegExp->String->Boolean
constmatch=curry((re,str)=>re.test(str));
prop
//prop::String->Object->a
constprop=curry((p,obj)=>obj[p]);
reduce
//reduce::(b->a->b)->b->[a]->b
constreduce=curry((fn,zero,xs)=>xs.reduce(fn,zero));
replace
//replace::RegExp->String->String->String
constreplace=curry((re,rpl,str)=>str.replace(re,rpl));
reverse
//reverse::[a]->[a]
constreverse=x=>Array.isArray(x)?x.reverse():x.split('').reverse().join('');
safeHead
AppendixC:PointfreeUtilities
139
//safeHead::[a]->Maybea
constsafeHead=compose(Maybe.of,head);
safeLast
//safeLast::[a]->Maybea
constsafeLast=compose(Maybe.of,last);
safeProp
//safeProp::String->Object->Maybea
constsafeProp=curry((p,obj)=>compose(Maybe.of,prop(p))(obj));
sequence
//sequence::(Applicativef,Traversablet)=>(a->fa)->t(fa)->f(ta)
constsequence=curry((of,f)=>f.sequence(of));
sortBy
//sortBy::Ordb=>(a->b)->[a]->[a]
constsortBy=curry((fn,xs)=>{
returnxs.sort((a,b)=>{
if(fn(a)===fn(b)){
return0;
}
returnfn(a)>fn(b)?1:-1;
});
});
split
//split::String->String->[String]
constsplit=curry((sep,str)=>str.split(sep));
AppendixC:PointfreeUtilities
140
take
//take::Number->[a]->[a]
consttake=curry((n,xs)=>xs.slice(0,n));
toLowerCase
//toLowerCase::String->String
consttoLowerCase=s=>s.toLowerCase();
toString
//toString::a->String
consttoString=String;
toUpperCase
//toUpperCase::String->String
consttoUpperCase=s=>s.toUpperCase();
traverse
//traverse::(Applicativef,Traversablet)=>(a->fa)->(a->fb)->ta->f
(tb)
consttraverse=curry((of,fn,f)=>f.traverse(of,fn));
unsafePerformIO
//unsafePerformIO::IOa->a
constunsafePerformIO=io=>io.unsafePerformIO();
AppendixC:PointfreeUtilities
141
AppendixC:PointfreeUtilities
142