ArchitectureofSo-wareSystemsFunc5onalProgramming
JanMichelfeit
2017
Func5onalProgramming
• WhyshouldIcare?• Whatisit?• Prac5calfunc5onalprogramming
• Func5onalprinciplesinso-warearchitecture
• Advancedtopics• Takeaways
WHYFUNCTIONALPROGRAMMING?
What’swrongwiththesesnippets?DateFormatformat=newSimpleDateFormat("yyyy-MM-dd");ExecutorServicethreadPool=Executors.newFixedThreadPool(5);List<Future<Date>>results=newArrayList<>();for(inti=0;i<10;i++){results.add(threadPool.submit(()->format.parse("2017-10-22")));}for(Future<Date>result:results){System.out.println(result.get());}
classPerson{privateStringname;//…@Overridepublicbooleanequals(Objecto){…}@OverridepublicinthashCode(){…}}Set<Person>set=newHashSet<>();Personp=newPerson();set.add(p);p.setName("Daniel");
voidprintItems(Iterator<String>items){intitemsCount=Iterators.size(items);for(inti=0;i<itemsCount;i++){System.out.println(items.next());}}
What’swrongwiththesesnippets?DateFormatformat=newSimpleDateFormat("yyyy-MM-dd");ExecutorServicethreadPool=Executors.newFixedThreadPool(5);List<Future<Date>>results=newArrayList<>();for(inti=0;i<10;i++){results.add(threadPool.submit(()->format.parse("2017-10-22")));}for(Future<Date>result:results){System.out.println(result.get());}
classPerson{privateStringname;//…@Overridepublicbooleanequals(Objecto){…}@OverridepublicinthashCode(){…}}Set<Person>set=newHashSet<>();Personp=newPerson();set.add(p);p.setName("Daniel");
voidprintItems(Iterator<String>items){intitemsCount=Iterators.size(items);for(inti=0;i<itemsCount;i++){System.out.println(items.next());}}
Doesset.contains(p)returnstrue?
Doesthisprintanything?
Doesthisthrowanexcep5on?
What’swrongwiththesesnippets?DateFormatformat=newSimpleDateFormat("yyyy-MM-dd");ExecutorServicethreadPool=Executors.newFixedThreadPool(5);List<Future<Date>>results=newArrayList<>();for(inti=0;i<10;i++){results.add(threadPool.submit(()->format.parse("2017-10-22")));}for(Future<Date>result:results){System.out.println(result.get());}
classPerson{privateStringname;//…@Overridepublicbooleanequals(Objecto){…}@OverridepublicinthashCode(){…}}Set<Person>set=newHashSet<>();Personp=newPerson();set.add(p);p.setName("Daniel");
voidprintItems(Iterator<String>items){intitemsCount=Iterators.size(items);for(inti=0;i<itemsCount;i++){System.out.println(items.next());}}
set.contains(p)returnsfalsebecauseofhashCode()behavior
NoSuchElementExcep5on(Iteratorreuse)
Works…orNumberFormatExcep5on:mul5plepoints…orNumberFormatExcep5on:Forinputstring:"101101.E101E22”…orArrayIndexOutOfBoundsExcep5on:-1(DateFormatisnotthread-safe)
What’swrongwiththesesnippets?
• Whatdidtheexampleshaveincommon?– Mutablestate
• Doyoulikeglobalvariables?• ShouldStringbemutable?
Mutablestatecanmakethingsreallyhardtoreasonabout,debug,…
Func5onalProgramming
• Basicidea:– avoidmutablestateandside-effects– composeprogramsfromfuncDonsthatalwaysgivethesameresultforthesamearguments
• Advantages– leadstocodethatissafer,modular,composable– easiertoreasonabout,test,anddebug– wellsuitedforparallelizaDon
WHATISFUNCTIONALPROGRAMMING?
BasicTerminologyImmutability
– Datastructure(object)isimmutableifit's(observable)statecannotbemodifiedaReritiscreated.
– Examples(Java):String,ImmutableList(Guavalibrary),anyclasswithallfieldsfinal&immutable
ReferenDaltransparency– Anexpressioneisreferen5allytransparentifforallprogramspeveryoccurrenceofeinpcanbereplacedwiththeresultofevaluaDnge,withoutaffec5ngtheobservableresultofp.
– E.g.,replacealloccurrencesof“1+2”with“3”– Allowscrea5onoflocalstate,aslongasit'snotobservable
BasicTerminologyPurefuncDon
– FuncDonfispureiftheexpressionf(x1,…,xn)isreferen5allytransparentforallreferen5allytransparentinputsx1,…,xn
– Func5onoutputmaydependonlyonarguments,notonexternalmutablestate
– Typically"noside-effects"-onlyobservableoutputshouldbethereturnvalue
– Example:mathema5calfunc5ons(sin,max,+,...)
Sideeffect– Modifiesstateoutsideofitsscope,orhasanobservableinterac5on
withitscallingfunc5onsortheoutsideworld– examples
• reassigningavariable,modifyingadatastructureinplace• throwinganexcep5onorhal5ngwithanerror(dependsoncontext)• userinterac5on• reading/wri5ngafile
ProgrammingParadigmsImperaDveProgramming
• Program:sequenceofcommandschangingstate• Commandsusuallydon'thavevalue=>dataexchangedthroughstate
• “Func5ons”–subrou5nes(unitofmodularity)• Func5oninvoca5oncangivedifferentresultsatdifferent5mes– dependingonthestateoftheexecu5ngprogram
• Closertohardware/tradi5onalprogrammerthinking
ProgrammingParadigms(Pure)FuncDonalProgramming
• Modelscomputa5onastheevaluaDonofexpressions,usingpurefuncDonsandimmutabledata
• Avoidsmutablestateandside-effects
ProgrammingParadigms(Pure)FuncDonalProgramming
• BasedonLambdaCalculus– Turing-completecomputa5onmodel
• (Closertohumanthinking(unlessobfuscated))• SpecialcaseofdeclaraDveprogramming– expressesthelogicofacomputa5onwithoutdescribingitscontrolflow
– describingwhattheprogrammustaccomplish,ratherthanhow
– e.g.,HTML,Excel,mostpartsoffunc5onallanguages
FuncDonsasfirst-classciDzens– canbepassedasargumentstootherfunc5onsorbereturnedasaresultofafunc5on
– func5onsaccep5ngand/orreturningfunc5onsarehigher-orderfuncDons
List("list","of","words").map(word=>word.length)//List(4,2,5)
valf=(x:Int)=>-xvalg=f.compose((x:Int)=>x*x)g(3)//-9
TypicalFeaturesofFunc5onalLanguages
Func5on(String)=>Int
TypicalFeaturesofFunc5onalLanguagesLazyevaluaDon– expressionevalua5ondelayedun5lthevalueisneeded
– evalua5onorderisirrelevantwithpurefunc5ons(noside-effectcaneverchangeexpressionvalue)--infinitedatastructurenumsFromn=n:numsFrom(n+1)take5(numsFrom0)--result:[0,1,2,3,4]
Source.fromFile("numbers.txt").map(line=>line.toInt).exists(n=>n<0)//fileisreadonlyuntilfirstnegativenumber
Func5onalProgrammingAdvantagesParallelwithstructuredvs.unstructuredprogramming:• Structuredprogrammingforbids
– goto– mul5pleentriesorexitsfromablockofcode
=>seeminglylesspower?
But:– encouragesmodulardesign=>simpler,smallermodules
• easier,quickertocode• easiertoreuse• easiertotest
– mathema5callymoretractable(easiertoanalyze,tooling)
Func5onalProgrammingAdvantagesIFPforbidsside-effectsinfunc5ons,mutablevariables• seeminglylesspower?
But:• Lackofsideeffectseliminatesamajorsourceofbugs• Evalua5onorderirrelevant
– parallelizaDonfriendly– enablesprac5callazyevaluaDon
• Higherorderfunc5ons&lazyevalua5onareanew"glue"forcomposiDonofmodules
Func5onalProgrammingAdvantagesIIFPforbidsside-effectsinfunc5ons,mutablevariables• seeminglylesspower?
But:• encouragesevenbeXermodularizaDon,composability• easiertesDng(inputvaluesdetermineoutput,avoidssetupofstate)• declaraDve(capturesinten5on),usuallymoreconsciousand
understandable(unlessobfuscated)• immutabledatastructures-thread-safe,cacheable• mathema5callymoretractable• easierdebugging,...
PRACTICALFUNCTIONALPROGRAMMING
Sum-Impera5vely
• Whyisthisnotfunc7onal?• Mutablevariablesresult,i(butnoexternallyvisiblestate)
privateintsum(List<Integer>list){intresult=0;for(inti=0;i<list.size();i++){result+=list.get(i);}returnresult;}
Sum-func5onally
defsum(list:List[Int]):Int={if(list.empty)0elselist.head+sum(list.tail)}
privateintsum(List<Integer>list){intresult=0;for(inti=0;i<list.size();i++){result+=list.get(i);}returnresult;}
Func5onalversion?(Hint:thinkofmathema7calinduc7on)
Count-func5onally
defcount(list:List[Int]):Int={if(list.empty)0else1+count(list.tail)}
Func5onalversionofcompu5nglistsize?
defsum(list:List[Int]):Int={if(list.empty)0elselist.head+sum(list.tail)}
Generalizing…
• Whatwasspecificforsum()?– 0and+
defsum(list:List[Int]):Int={if(list.empty)0elselist.head+sum(list.tail)}
Fold
deffoldRight(list:List[Int],initial:Int,op:(Int,Int)=>Int):Int={if(list.isEmpty)initialelseop(list.head,foldRight(list.tail,initial,op))}
UsingFold
defsum(list:List[Int])=foldRight(list,0,(a,b)=>a+b)defprod(list:List[Int])=foldRight(list,1,(a,b)=>a*b)defcount(list:List[Int])=foldRight(list,0,(a,b)=>1+b)defexists(list:List[Int],condi5on:Int=>Boolean)=foldRight(list,false,(n,b)=>condiDon(n)||b)defcopy(list:List[Int])=foldRight(list,List(),(n,list)=>n+:list)
Fold-Observa5ons
• foldRight()isahigher-levelfunc5on• sum(),count(),…arecomposedfromsmallerreusablebuildingblocks
• IsthispossibleinJava?– yes,butfunc5onallanguagemadethemodulariza5oneasierandmoreobvious
– funcDonalthinkingismoreimportantthanapar5cularlanguage
• No5cethesimilaritywithalgebra(monoid)
TypicalOpera5onsonCollec5onsImperaDveprogramming:add(),get(),set()/put()FuncDonalProgramming(examplesforTraversable[T]inScala–e.g.,List[T];simplified)• map(f:A=>B):Traversable[B]• flatMap(f:A=>Traversable[B]):Traversable[B]• filter(f:A=>Boolean):Traversable[B]• foldLeR(zero:B)(op:(B,A)=>B):B• foldRight(),fold()• head:A• tail:Traversable[A]• groupBy(f:A=>K):Map[K,Traversable[A]]• ++(t:Traversable[A]):Traversable[A]• ...manymore-take(),drop(),takeWhile(),slice(),zip(),grouped(),...• Methodsdonotchangeinputs,butreturnnewcollecDonsasresult!• ComputaDonmaybelazy
Collec5onsOpera5ons-Examples
defsum(list:List[Int])=list.foldRight(0)((a,b)=>a+b)defprod(list:List[Int])=list.foldRight(1)((a,b)=>a*b)defcount(list:List[Int])=list.foldRight(0)((a,b)=>1+b)defexists(list:List[Int],condi5on:Int=>Boolean)=list.foldRight(false)((n,b)=>condiDon(n)||b)defcopy(list:List[Int])=list.foldRight[List[Int]](List())((n,list)=>n+:list)
ProperScalasyntaxforsum(),prod(),…
Collec5onsOpera5ons-ExamplesPrintallmiddlenames(JavaScript)
varnames=["JamesPaulMcCartney","WilliamBradleyPin","LauraWitherspoon","HannahDakotaFanning"];names.map(func5on(name){returnname.split("");}).filter(func5on(parts){returnparts.length>=3;}).map(func5on(parts){returnparts[1];}).forEach(log)//forEachnotfunc5onal!!
Collec5onsOpera5ons-ExamplesGetlengthofthelongestword(Scala)
vallines=List("Scalacollections","havenicemethods")lines.flatMap(line=>line.split("").toList)//List(Scala,collections,have,nice,methods).map(word=>word.length)//List(5,11,4,4,7).fold(-1)(Math.max)//11
lines.map(line=>line.split("").toList)//List(List(Scala,collections),List(have,nice,methods)).map(x=>x.length)//List(2,3)
FP-orientedLanguages• Haskell• Scala• F#• Erlang• R• …• Somefeaturesintradi5onalOOlanguages– Java8,LINQ,C++11
GoingParallel-Impera5veHowtomakethisparallel?Generalapproach:1. (Recursively)splittosubtasks2. Executesubtasksinparallel3. Re-combineresultsofsubtasks
privateintsum(List<Integer>list){intresult=0;for(inti=0;i<list.size();i++){result+=list.get(i);}returnresult;}
GoingParallel-Impera5veDoesthiswork?No!Accesstomutablevariableresultisnotsynchronized
– Resultswillbenon-determinis5c
privateintresult;publicintsum(List<Integer>list){result=0;for(Integern:list){threadPool.submit(()->result+=n);}//...waitfortasksfinished...returnresult;}
GoingParallel-Func5onal
InScala:Internally:1. Recursivelysplitslist2. Foldseachpartinparallel3. Appliesfoldopera5ontopar5alresults
list.par.sum
list.par.fold(0)((a,b)=>a+b)
GoingParallel-Func5onal• Combiningpar5alresultsmaybenon-determinis5c
– E.g.List(1,2,3,4):(((0+1)+2)+3)+4or((0+1)+2)+((0+3)+4)
• =>Foldopera5onmustbeassociaDve– (a+b)+c=a+(b+c)– remembermonoid
• No5ceweusedfold()insteadoffoldRight() – fold() expectsassocia5veopera5on
• Exercise:tryparallelizingimpera5ve&func5onalQuickSort
GoingParallel-Takeaways
• Paralleliza5oncangetcomplex,frameworkshelp• Sideeffects¶llelismmayleadtonon-determinism• ParallelaccesstomutablestaterequiressynchronizaDon
– e.g.AtomicInteger, ConcurrentHashMap,synchronized,…
– synchroniza5oniscostly• Non-associa5vere-combina5onofresultsmayleadtonon-determinism
– (commuta5vityisnotrequired)
• Paralleliza5onwithimmutabledataismucheasier&efficient– avoidssynchroniza5on
FUNCTIONALPRINCIPLESINSOFTWAREARCHITECTURE
Func5onalPrinciples
• Statelessness• Immutability¶lleliza5on• Func5onalAPIs
MapReduce
• Frameworkforparallelprocessingofbigdata(mul5-terabyte)onlargeclustersofcommodityhardware
• ExecutesMapReducejobs1. Splitinputdata(onadistributedfilesystem)into
records2. Processeachrecordwithamaptask
(consideringdatalocality)
3. Mergeresultswithareducetask
• map:(K1,V1)=>List[(K2,V2)];• reduce:(K2,List[V2])=>List[V3]
MapReduce-Observa5ons
• Distributedcomputa5on– nosharedmemory=>cannotsharestate
• Mapandreducearehigher-levelfunc5ons– mappersandreducersarefirstclassci5zens,preferablyimmutable
• Theprinciplecanbeappliedevenwhenprogrammingwiththreads– basicallyparallelmap()+fold()(orreduce())
MapReduce–Example
• Wordcount– Map:foreachwordemittuple(word,1)– Reduce:Sum1sforeachword
• Higherlevelframeworkso-enusedinprac5ce-e.g.Scalding
classWordCountJob(args:Args)extendsJob(args){TypedPipe.from(TextLine(args("input"))).flatMap{line=>line.toLowerCase.split("\\s+")}.groupBy{word=>word}//useeachwordforakey.size//ineachgroup,getthesize.write(TypedText.tsv[(String,Long)](args("output")))}
StatelessComponents• ServiceStatelessnessprinciple
– “Guidelinesinfavorofmakingtheservicestatelessbyshi-ingawaythestatemanagementoverheadfromtheservicestosomeotherexternalarchitecturalcomponent”(wiki)
• Statecanbeexternalizedtoadedicatedcomponent(database,distributedin-memorycache,...)
• Why– lowercomponentcomplexity– makeshighavailabilityandhorizontalscalingeasier
• E.g.,consideracustomer-facingwebserverands5ckysessions
• Whynot– canincreaseoverallsystemcomplexity– affectsperformance,response5mes
Func5onalFrameworkAPIS
E.g.,D3.js
d3.selectAll("p").style("color","white");
varparagraphs=document.getElementsByTagName("p");for(vari=0;i<paragraphs.length;i++){varparagraph=paragraphs.item(i);paragraph.style.setProperty("color","white",null);}
d3.selectAll("p").style("color",function(d,i){returni%2?"#fff":"#eee";});
ADVANCEDTOPICS
Side-effectsinPrac5ce
• FPdiscouragesside-effects– Butwhatabouttheuser?Whatcantheprogramdo?
• Somelanguagesallowside-effectsasprogrammer’sresponsibility
• PureFPlanguages(e.g.,Haskell)allowonlyexplicitside-effectswrappedasmonads
Monad
• RepresentsacomputaDonwithasequenDalstructureandpossibleside-effect– Defineswhatitmeanstochainopera5onstogether
• Allowsrefactoringside-effectsoutoffunc5ons• “Promise”toproduceavalueofacertaintype• Allowssepara5ngcomputa5ondescrip5onandexecu5on
Monad
• Formally:typeconstructor,bind&returnopera5ons,monadlaws[1],[2]
• Informally:– genericdatastructureM[A] – withconstructorof(): A => M[A] – Opera5on flatMap() : (A=>M[B]) => M[B]
• E.g.,Optional<T>inJava:– static <T> Optional<T> of(T value) – Optional<U> flatMap( Function<T, Optional<U>> mapper)
Monad-Examples
• I/OMonadinHaskell–wrapsallcomputa5onswithaglobaleffect– getChar :: IO Char
• purefunc5onthatreturnsaside-effec5ngcomputa5on• doesnotnecessarilycauseanimmediateeffect• =>canbeusedinanotherpurefuncDon
– putChar :: Char -> IO () • Scala– Option[T], Future[T], Set[T], List[T]
• LINQoperators-e.g.,M<T> SelectMany(this M<S> src, Func<S, M<T>> f)
Monad-ExamplesobjectOrderService{defloadOrder(username:String):Future[Order]=???}objectPurchaseService{defpurchase(order:Order):Future[PurchaseResult]=???deflogPurchase(result:PurchaseResult):Future[LogResult]=???}vallogResult=OrderService.loadOrder("customerUsername").flatMap(order=>PurchasingService.purchase(order)).flatMap(result=>PurchaseService.logPurchase(result))
ReplacingImpera5veLoops
publicintfactorial(intn){intresult=1;while(n>0){result*=n--;}returnresult;}
Howtoreplacemutablevariables?deffactorial(n:Int)={if(n==1)1elsen*factorial(n-1)}
Recursion
• Evalua5on:– factorial(4)– 4 * factorial(3)– 4 * (3 * factorial(2))– 4 * (3 * (2 * factorial(1)))– 4 * (3 * (2 * 1))– 4 * (3 * (2))– 4 * (6)– 24
• Stacksizedependentoninputargument
deffactorial(n:Int)={if(n==1)1elsen*factorial(n-1)}
TailRecursion• TailrecursivefuncDon-recursiveac5onasthelastac5on
– variablesonthestackwillnolongerbeused• =>Compilercanreplacerecursionwithaloop• Howtopassintermediateresults?
– “Accumulator”argument
• Evalua5on:– factorial(4,1)->factorial(3,4)->factorial(2,12)->factorial(1,24)->24
deffactorial(n:Int,accumulator:Int=1)={if(n==1)accumulatorelsefactorial(n–1,accumulator*n)}
CONCLUSION&TAKEAWAYS
Func5onalProgramming-Comparison
ImperaDve FuncDonalBasicunit Command ExpressionComputa5on Commandexecu5on Expressionevalua5onMutability Mutabledata ImmutabledataSide-effects Allowed ExternalizedviaMonadsFunc5on Unitofmodularity;
candependonexternalstate
Purefunc5on;cannotdependonexternalstate
Programdescribes
Howtoaccomplishatask
Whattoaccomplish
Cons&Pixalls• Func5onallanguagemaybemoredistanttoatradi7onalprogrammer's
thinking– Func5onalcodecanbeobfuscatedbytheprogrammer(bewareof“write-only”code)
• Possiblestackoverflowsifrecursionusedwrong– Usetailrecursionifthelanguagesupportsit
• Nega5veimpactonperformanceifusedwrong– Non-tailrecursionhasacost– Memoryalloca5ons&garbagecollec5onofmanyimmutableobjects(but:short-lived
immutableobjectsmaybebenerforGCthanlong-livedmutableobjects)– hnp://flyingfrogblog.blogspot.cz/2016/05/disadvantages-of-purely-func5onal.html
• Somealgorithmshardtowriteefficiently– SomeproblemssolvableinO(n)5mewithstatemuta5oncanrequireΩ(nlogn)5mein
pure,non-lazyfunc5onallanguage
• Difficulttopredictthe5meandspacecostsofevalua5ngalazyfunc5onalprogram• Butremember:prematureopDmizaDonistherootofallevil[1],[2]
WhyFunc5onalProgramming
• Features– Avoidsmutablestateandside-effects– New"glue"forcomposi5on:func5onsasfirst-classci5zens,lazyevalua5on
– Declara5ve• Paralleliza5onfriendly• Thread-safe,cacheabledatastructures• Purefunc5onsaresafer,modular,composable• Easiertoreasonabout,test,anddebug
FurtherReading• Func5onalProgramminginScala(Chiusano&Bjarnason)
(book)• Seriesofar5clesbyLiborŠkarvada• WhyFunc5onalProgrammingManers(Hughes)(paper)• Func5onalprogramminginJavaScriptinEloquentJavaScript(book
chapter)• Haskelldocumenta5on• ComparisonofFunc5onal,Declara5veandImpera5ve
programmingonStackOverflow• ProgramminginScala(2008,bookavailableonline)• Scalacheatsheets:one,another
TheEnd…