Upload
others
View
13
Download
1
Embed Size (px)
Citation preview
Haskellalazy,purelyfunc1onallanguage
COS326DavidWalker
PrincetonUniversityslidescopyright2013-2015DavidWalkerandAndrewW.Appel
permissiongrantedtoreusetheseslidesfornon-commercialeduca1onalpurposes
HaskellAnothercool,typed,func1onalprogramminglanguage• LikeOCamlinthat:
– itisafunc1onallanguagewithparametricpolymorphism• UnlikeOCamlinthatitis:
– pure:func1onswithtypea->bhavenoeffect!– lazy:fedoesnotevaluateerightaway;passesetof– hasaweakmodulesystem;usestypeclasses– hasaverysophis1catedtypesystemwithhigherkinds– hasseveralcoolextensionsforconcurrentandparallelprogrammingincludingtransac1onalmemory
Glasgowinthe1990s
PhilWadler(nowatU.Edinburgh)
SimonPeytonJones(nowatMicrosoZResearch,
Cambridge,England)
ghc standsfor“GlasgowHaskellCompiler”
Edinburghinthe1970sRobinMilner,LucaCardelli,LuisDamas,MadsToZe...createdMLlanguage
Whyarewege`ngallourfunc1onalprogramminglanguagesfromScotland?
CreatorsofHaskelllanguage
HASKELLBASICS
HaskellDefini1ons• MostlylikeOCaml,withafewsmallsyntac1cdifferences
– parametersareimmutablebydefault– letdeclara1onsintroducenewfunc1onsandvalue
– equivalently,wemightusea"where"defini1on:
fooz=lettriplex=x*3intriplezfooz=triplezwheretriplex=x*3
HaskellIndenta1on• Haskell,likePython,butunlikeJava,OCamlormathwrihenin
anotebook,hasseman1callymeaningfulindenta1on• Wrong:
copieskn=ifn==0then[]elsek:copiesk(n-1)
zapz=letx=zy=z+zinx+y
mustindentfunc1onbody
zapz=letx=zy=z+zinx+yindenty=...
indentz+z
HaskellIndenta1on• Haskell,likePython,butunlikeJava,OCamlormathwrihenin
anotebook,hasseman1callymeaningfulindenta1on• Right:
zapz=letx=zy=z+zinx+y
copieskn=ifn==0then[]elsek:copiesk(n-1)
zapz=letx=zy=z+zinx+y
beginningofxdefinesindenta1onlevel
HaskellTypes• Wehavetheop1onofdeclaringthetypeofadefini1onprior
tothedefini1onitself
zap::Int->Intzapz=letx=zy=z+zinx+y
justtobeannoying,Haskelluses"::"for"hastype"anduses":"for"cons"–theoppositeofML
Tuples• HaskellusestupleslikeML
– constructedbyenclosingasequenceofvaluesinparens:
– deconstructed(used)viapahernmatching:
(‘b’,4)::(Char,Integer)
easytoo::(Integer,Integer,Integer)->Integereasytoo(x,y,z)=x+y*z
Lists• ListsareverysimilartoMLlistsbutthetypeiswrihen[t]
insteadof"tlist"
• []istheemptylist(callednil)• 1:2:[]isalistwith2elements• Stringisasynonymfor[Char]• Wecanbuildlistsoflists:
[1,2,3]::[Integer][‘a’,‘b’,‘c’]::[Char]
[[1,2],[3],[8,9,10]]::[[Integer]]
Func1onsoverlists
--SumtheelementsofalistlistSum::[Integer]->IntegerlistSum[]=0listSum(x:xs)=x+listSumxs
commontowritefunc1onsusingmul1pleclauses,whereeachclausematchesonadifferentcase
Func1onsdeconstruc1nglists
--SumtheelementsofalistlistSum::[Integer]->IntegerlistSum[]=0listSum(x:xs)=x+listSumxs
length::[a]->Intlength[]=0length(x:xs)=1+lengthxs
lowercaseleher=anytypeatall(atypevariable)
uppercaseleher=aconcretetype
Func1onsdeconstruc1nglists
--SumtheelementsofalistlistSum::[Integer]->IntegerlistSum[]=0listSum(x:xs)=x+listSumxs
length::[a]->Intlength[]=0length(x:xs)=1+lengthxs
cat::[a]->[a]->[a]cat[]xs2=xs2cat(x:xs)xs2=x:(catxsxs2)
Func1onsdeconstruc1nglists
--SumtheelementsofalistlistSum::[Integer]->IntegerlistSum[]=0listSum(x:xs)=x+listSumxs
length::[a]->Intlength[]=0length(x:xs)=1+lengthxs
(++)::[a]->[a]->[a](++)[]xs2=xs2(++)(x:xs)xs2=x:(xs++xs2)
cat::[a]->[a]->[a]cat[]xs2=xs2cat(x:xs)xs2=x:(catxsxs2)
PURITY&SUBSTITUTIONOFEQUALSFOREQUALS
Subs1tu1onofEqualsforEquals• AkeylawaboutHaskellprograms:
• Forexample:
letx=<exp>in...x...x... ...<exp>...<exp>...
letx=4`div`2inx+5+x
(4`div`2)+5+(4`div`2)
9
=
==
*Note:notnecessarilythesamerun1me;(4`div`2)willbeevaluatedtwiceinsteadofonce.
Subs1tu1onofEqualsforEquals• We'dalsoliketousefunc1onalabstrac1onwithoutpenalty
• Andinsteadoftellingclientsaboutallimplementa1ondetails,
simplyexposekeylaws:
• Nowwecanreasonlocallywithintheclient:
halve::Int->Inthalven=n`div`2
(halve4)+y+(halve4)letx=halve4inx+y+x ==
Lemma1:foralln,ifevennthen(halven+halven)=n
(halve4)+(halve4)+y
4+y=
(subs1tu1on)
(arithme1c)
(Lemma1)
Computa1onalEffects• Whathappenswhenweaddmutabledatastructures?• ConsiderthisOCamlprogram:
• Welosealotofreasoningpower!
letx=ref0letfoo(y:int):int=x:=!x+1;arg+!x;
lety=foo3iny+y ≠ foo3+foo3
foo:int->int
Computa1onalEffects• Whathappenswhenweaddmutabledatastructures?• ConsiderthisOCamlprogram:
• Welosealotofreasoningpower!
letx=ref0letfoo(y:int):int=x:=!x+1;arg+!x;
lety=foo3iny+y ≠ foo3+foo3
foo:int->int
8 9
Computa1onalEffects• Whathappenswhenweaddmutabledatastructures?• ConsiderthisOCamlprogram:
• Welosealotofreasoningpower!
letfoo(y:int):int=print_inty;arg+!x;
lety=foo3iny+y ≠ foo3+foo3
foo:int->int
6prin1ng"3" 6prin1ng"33"
Computa1onalEffects• Afunc1onhasaneffectifitsbehaviorcannotbespecified
exclusivelyasarela1onbetweenitsinputanditsoutput– I/Oisaneffect– Animpera1veupdateofadatastructureisaneffect
• Whenfunc1onscannolongerbedescribedexclusivelyintermsoftherela1onshipbetweenargumentsandresults– many,manyfewerequa1onallawshold– Ingeneral,inOCaml,
– Ingeneral,inHaskell,
letx=<exp>in...x...x... ...<exp>...<exp>...≠
letx=<exp>in...x...x... ...<exp>...<exp>...=
Computa1onalEffects• Afunc1onhasaneffectifitsbehaviorcannotbespecified
exclusivelyasarela1onbetweenitsinputanditsoutput– I/Oisaneffect– Animpera1veupdateofadatastructureisaneffect
• Whenfunc1onscannolongerbedescribedexclusivelyintermsoftherela1onshipbetweenargumentsandresults– many,manyfewerequa1onallawshold– Ingeneral,inOCaml,
– Ingeneral,inHaskell,
letx=<exp>in...x...x... ...<exp>...<exp>...≠
letx=<exp>in...x...x... ...<exp>...<exp>...=
Thisiskindofmagical!Haskellhassubs1tu1onof
equalsforequalsANDALSOHASEFFECTS.
Buthow?
DIGRESSION:MONADSANDLISTCOMPREHENSIONS
ListcomprehensionsExample:Findall(a,b,c)suchthat1≤a≤b≤c≤25anda2+b2=c2pabc=a*a+b*b=c*ctriples=doa<-[1..25]b<-[a..25]c<-[b..25]guard(pabc)return(a,b,c)
Source:GregBacon,hhp://gbacon.blogspot.com/2009/07/programmable-semicolon-explained.html
ListcomprehensionsExample:Findall(a,b,c)suchthat1≤a≤b≤c≤25anda2+b2=c2pabc=a*a+b*b=c*ctriples=doa<-[1..25]b<-[a..25]c<-[b..25]guard(pabc)return(a,b,c)
Source:GregBacon,hhp://gbacon.blogspot.com/2009/07/programmable-semicolon-explained.html
Itlookslikesomesortof“iterator”feature;butHaskelldoesn’thavebuilt-in“iterators.”
It'sgotsomethingmoregeneral!Iteratorsariseasanaturalconsequenceof:
purefunc1onalprogramming+lazyevalua1on
+lists+anicenota1onformonadiccomputa/ons
Now,let’s“unpack”thistoseehowthatworks.
ListcomprehensionsExample:Findall(a,b,c)suchthat1≤a≤b≤c≤25anda2+b2=c2triples=do{a<-[1..25];b<-[a..25];c<-[b..25];guard(pabc);return(a,b,c)}
Source:GregBacon,hhp://gbacon.blogspot.com/2009/07/programmable-semicolon-explained.html
Analternatesyntaxusingsemi-colons.Thesemi-colonopera1onactuallydoesalotofwork.Itdoesmorethanjust"moveontothenextstatement".It"composes"thefunc1onsdefinedbyeachstatement.
ListcomprehensionsExample:Findall(a,b,c)suchthat1≤a≤b≤c≤25anda2+b2=c2triples=do{a<-[1..25];b<-[a..25];c<-[b..25];guard(pabc);return(a,b,c)}
Thisisjustthe“range”operatorforlists;[1..5]isjust[1,2,3,4,5].Easytoconstructthiswith“letrec”inML,orasarecursivefunc1oninHaskell.Nomagichere.
Listcomprehensions“a<-e;…”isjustanota1onalabbrevia1onforthemonadicbindoperatorthatyou’veseenbefore.Haskellmakesiteasytointroducenota1onalabbrevia1onstriples=do{a<-[1..25];b<-[a..25];c<-[b..25];guard(pabc);return(a,b,c)}
triples=[1..25]>>=\a->[a..25]>>=\b->[b..25]>>=\c->guard(pabc)>>return(a,b,c)
That’sλfromlambda-calculus;inMLyou’dwrite
“fun”
Listcomprehensions“a<-e;…”isjustanota1onalabbrevia1onforthemonadicbindoperatorthatyou’veseenbefore.Haskellmakesiteasytointroducenota1onalabbrevia1onstriples=do{a<-[1..25];b<-[a..25];c<-[b..25];guard(pabc);return(a,b,c)}
triples=[1..25]>>=\a->[a..25]>>=\b->[b..25]>>=\c->guard(pabc)>>return(a,b,c)
That’sλfromlambda-calculus;inMLyou’dwrite
“fun”
do{a<-e1;e2} e1>>=(\a->e2) e1>>=(funa->e2)== ==
ListMonadYou’veseenmonadsbefore,butletmeremindyou:
(*inOcamlnota1on,notHaskell!*)moduletypeMONAD=sigtype‘aMvalreturn:‘a->‘aMval(>>=):‘aM->(‘a->‘bM)->‘bMend
pronounced“bind”
Onewaytothinkofamonad'aM:Itisaspecialsortof"container"for'a.returnx :putsxintothecontainerc>>=f :extractsitemsfromc;thenpushesthemthroughf,whichgeneratesnewcontainerBind(c>>f)allowsyoutocomputewiththeitemsinsidethecontainer
ListMonadYou’veseenmonadsbefore,butletmeremindyou:
(*inOcamlnota1on,notHaskell!*)moduletypeMONAD=sigtype‘aMvalreturn:‘a->‘aMval(>>=):‘aM->(‘a->‘bM)->‘bMend
pronounced“bind”
Onewaytothinkofamonad'aM:Itisaspecialsortof"container"for'a.returnx :putsxintothecontainerc>>=f :extractsitemsfromc;thenpushesthemthroughf,whichgeneratesnewcontainerBind(c>>f)allowsyoutocomputewiththeitemsinsidethecontainer
Whenwediderrorprocessing,thecontainerwasanop1ontype:'aop1on.Atmostonethingwasinthecontainerata1me.
ListMonadHere,themonadwewantistheListmonadwithconcatMap
Onemorething:>>isjusta“bind”thatthrowsawayitsargument.let(>>)(m:‘aM)(f:‘bM):‘bM=m>>=fun_->f
(*inOcamlnota1on,notHaskell!*)moduletypeMONAD=sigtype‘aMvalreturn:‘a->‘aMval(>>=):‘aM->(‘a->‘bM)->‘bMend
ListMonadHere,themonadwewantistheListmonadwithconcatMap
(*inOcamlnota1on,notHaskell!*)moduleListMonad:MONADtype‘aM=‘alistletreturn(x:‘a)=[x]let(>>=)(as:‘alist)(f:‘a->‘blist):‘blist=reduceappendnil(mapfas)end
(*inOcamlnota1on,notHaskell!*)moduletypeMONAD=sigtype‘aMvalreturn:‘a->‘aMval(>>=):‘aM->(‘a->‘bM)->‘bMend
ListMonadHere,themonadwewantistheListmonadwithconcatMap
(*inOcamlnota1on,notHaskell!*)moduleListMonad:MONADtype‘aM=‘alistletreturn(x:‘a)=[x]let(>>=)(as:‘alist)(f:‘a->‘blist):‘blist=reduceappendnil(mapfas)end
(*inOcamlnota1on,notHaskell!*)moduletypeMONAD=sigtype‘aMvalreturn:‘a->‘aMval(>>=):‘aM->(‘a->‘bM)->‘bMend
letdoublea=[a;a]
[1;2;3]>>=double==reduceappendnil[[1;1];[2;2];[3;3]]==[1;1;2;2;3;3]
Exampleletfa=return(a,a)inreduceappendnil(mapf[1,2,3])
=
returnx=[x](>>=)xsf=reduceappendnil(mapfxs)
[1,2,3]>>=\a->return(a,a)
Exampleletfa=return(a,a)inreduceappendnil(mapf[1,2,3])
=
returnx=[x](>>=)xsf=reduceappendnil(mapfxs)
=letfa=return(a,a)inreduceappendnil[f1,f2,f3]
[1,2,3]>>=\a->return(a,a)
Exampleletfa=return(a,a)inreduceappendnil(mapf[1,2,3])
=
returnx=[x](>>=)xsf=reduceappendnil(mapfxs)
=letfa=return(a,a)inreduceappendnil[f1,f2,f3]
= reduceappendnil[return(1,1),return(2,2),return(3,3)]
[1,2,3]>>=\a->return(a,a)
Exampleletfa=return(a,a)inreduceappendnil(mapf[1,2,3])
=
returnx=[x](>>=)xsf=reduceappendnil(mapfxs)
=letfa=return(a,a)inreduceappendnil[f1,f2,f3]
= reduceappendnil[return(1,1),return(2,2),return(3,3)]
[1,2,3]>>=\a->return(a,a)
=reduceappendnil[[(1,1)],[(2,2)],[(3,3)]]
Exampleletfa=return(a,a)inreduceappendnil(mapf[1,2,3])
=
returnx=[x](>>=)xsf=reduceappendnil(mapfxs)
=letfa=return(a,a)inreduceappendnil[f1,f2,f3]
= reduceappendnil[return(1,1),return(2,2),return(3,3)]
[1,2,3]>>=\a->return(a,a)
=reduceappendnil[[(1,1)],[(2,2)],[(3,3)]]
[(1,1),(2,2),(3,3)]=
Example
letfa=[a..4]>>=\b->return(a,b)inreduceappnil(mapf[1,2,3])
=
return(x:‘a)=[x](>>=)(al:[a])(f:a->[b]):[b]=reduceappnil(mapfal)
=letfa=[a..4]>>=\b->return(a,b)inreduceappnil[f1,f2,f3]
= reduceappnil[[1,2,3,4]>>=\b->return(1,b),[2,3,4]>>=\b->return(2,b),[3,4]>>=\b->return(3,b)]
foo=[1..3]>>=\a->[a..4]>>=\b->return(a,b)
Example
letfa=[a..4]>>=\b->return(a,b)inreduceappnil(mapf[1,2,3])
=
return(x:‘a)=[x](>>=)(al:[a])(f:a->[b]):[b]=reduceappnil(mapfal)
=letfa=[a..4]>>=\b->return(a,b)inreduceappnil[f1,f2,f3]
= reduceappnil[[1,2,3,4]>>=\b->return(1,b),[2,3,4]>>=\b->return(2,b),[3,4]>>=\b->return(3,b)]
foo=[1..3]>>=\a->[a..4]>>=\b->return(a,b)
Example
foo=[1..3]>>=\a->[a..4]>>=\b->return(a,b)
=
return (x: ‘a) = [x] (>>=) (al: [a]) (f: a -> [b]) : [b] = reduce app nil (map f al)
=
reduceappnil[[1,2,3,4]>>=\b->return(1,b),[2,3,4]>>=\b->return(2,b),[3,4]>>=\b->return(3,b)]
reduceappnil[reduceappnil[return(1,1),return(1,2),return(1,3),return(1,4)],reduceappnil[return(2,2),return(2,3),return(2,4)],reduceappnil[return(3,3),return(3,4)]]
=reduceappnil[reduceappnil[[(1,1)],[(1,2)],[(1,3)],[(1,4)]],reduceappnil[[(2,2)],[(2,3)],[(2,4)]],reduceappnil[[(3,3)],[(3,4)]]]
Example
[1..3]>>=\a->[a..4]>>=\b->return(a,b)
=
return (x: ‘a) = [x] (>>=) (al: [a]) (f: a -> [b]) : [b] = reduce app nil (map f al)
=
=
reduceappnil[reduceappnil[[(1,1)],[(1,2)],[(1,3)],[(1,4)]],reduceappnil[[(2,2)],[(2,3)],[(2,4)]],reduceappnil[[(3,3)],[(3,4)]]]
reduceappnil[[(1,1),(1,2),(1,3),(1,4)],[(2,2),(2,3),(2,4)]],[(3,3),(3,4)]]]
[(1,1),(1,2),(1,3),(1,4),(2,2),(2,3),(2,4),(3,3),(3,4)]
Isitreallyaniterator?
doa<-[1..3]b<-[a..4]return(a,b)
= [(1,1),(1,2),(1,3),(1,4),(2,2),(2,3),(2,4),(3,3),(3,4)]
Itdoesn’tseemlike“inthespiritofaniterator”toreturnthewholelist;itshouldreturnthepairs(1,1);(1,2);…oneata'me,ondemand.
That’swhatwemeanbyaniterator.
Aha!EveryexpressioninHaskellislazy.Everythingison-demand.Thisreallyisaniterator!
Returningtothe“Pythagoreantriples”…
[1..25]>>=\a->[a..25]>>=\b->[b..25]>>=\c->guard(pabc)>>return(a,b,c)
doa<-[1..25]b<-[a..25]c<-[b..25]guard(pabc)return(a,b,c)
[1..25]>>=\a->[a..25]>>=\b->[b..25]>>=\c->guard(pabc)>>=\_->return(a,b,c)
guard::(MonadPlusm)=>Bool->m()guardTrue=return()guardFalse=mzero
The“zero”fortheListmonadis[]
“return()”intheListmonadis[()]
Exampleof“guard”
[1..5]>>=\a->guard(isprimea)>>=\_->return(a+1)
doa<-[1..5]guard(isprimea)return(a+1)
guardTrue=[()]guardFalse=[]
=
= fa=guard(isprimea)>>=\_->return(a+1)reduceappnil(mapf[1,2,3,4,5])
= reduceappnil[guard(isprime1)>>\_return(1+1),guard(isprime2)>>\_return(2+1),guard(isprime3)>>\_return(3+1),guard(isprime4)>>\_return(4+1),guard(isprime5)>>\_return(5+1)]
Exampleof“guard”
doa<-[1..5]guard(isprimea)return(a+1)
guardTrue=[()]guardFalse=[]
=
=
=
reduceappnil[guardFalse>>\_return(1+1),guardTrue>>\_return(2+1),guardTrue>>\_return(3+1),guardFalse>>\_return(4+1),guardTrue>>\_return(5+1)]
reduceappnil[foldappnil(map(\_return(1+1))[]),foldappnil(map(\_return(2+1))[()]),foldappnil(map(\_return(3+1))[()]),foldappnil(map(\_return(4+1))[]),foldappnil(map(\_return(5+1))[()])]
reduceappnil[[],[2+1],[3+1],[][5+1]]
= [2+1,3+1,5+1] [2+1,3+1,5+1]= [3,4,6]=
Returningtothe“Pythagoreantriples”…
doa<-[1..25]b<-[a..25]c<-[b..25]guard(pabc)return(a,b,c)
Thisiscalleda“listcomprehension”
Inmonadiccomputa1on,inanycollec/onmonad(notjustList),guardservesasafilterforcomprehensions
Therewasnomagichere(exceptforlazyfunc1onalprogramming).Monads,“bind”,“guard”,theListmonad,
arealldefinedasuser-levelfunc1onsintheHaskelllanguageitself.
HASKELLEFFECTSINPUTANDOUTPUT
I/OinHaskell• Haskellhasaspecialkindofvaluecalledanac1onthat
describesaneffectontheworld
• Pureac1ons,whichjustdosomethingandhavenointeres1ngresultarevaluesoftypeIO()
• Eg:putStrtakesastringandyieldsanac1ondescribingtheactofdisplayingthisstringonstdout
--writesstringtostdoutputStr::String->IO()--writesstringtostdoutfollowedbynewlineputStrLn::String->IO()
I/OinHaskell• Whendoac1onsactuallyhappen?
• Ac1onshappenundertwocircumstances:*1. theac1ondefinedbymainhappenswhenyourprogramis
executed(ie:whenghccompilesyourprogramandthenyourunit)
2. theac1ondefinedbyanyexpressionhappenswhenthatexpressioniswrihenattheghciinterpreterprompt(thisisprehysimilartothelastone–theexpressionisessen1alanen1reprogram)
*thereisoneothercircumstance:Haskellcontainssomespecial,unsafefunc1onsthatwillperformI/O,mostnotablySystem.IO.Unsafe.unsafePerformIOsoIliedwhenearlierwhenIsaidtheequa1onholdsingeneralforHaskellprograms...
I/OinHaskell
main::IO()main=putStrLn“Helloworld”
hello.hs:
$ghchello.hs[1of1]CompilingMain(hello.hs,hello.o)Linkinghello.exe...$./hello.exehelloworld!
inmyshell:
bar::Int->IO()barn=putStrLn(shown++“isasupernumber”)main::IO()main=bar6
bar.hs:
$ghcii.shGHCi,version7.0.3:hhp://www.haskell.org/ghc/:?forhelpLoadingpackageghc-prim...linking...done.Loadingpackageinteger-gmp...linking...done.Loadingpackagebase...linking...done.Loadingpackageffi-1.0...linking...done.Prelude>:lbar[1of1]CompilingMain(bar.hs,interpreted)Ok,modulesloaded:Main.*Main>bar1717isasupernumber*Main>main6isasupernumber*Main>
inmyshell:
Ac1ons• Ac1onsaredescrip1onsofeffectsontheworld.Simply
wri1nganac1ondoesnot,byitselfcauseanythingtohappen
hellos::[IO()]hellos=[putStrLn“Hi”,putStrLn“Hey”,putStrLn“Topofthemorningtoyou”]main=hellos!!2
Prelude>:lhellos...*Main>mainTopofthemorningtoyou*Main>
bar.hs:
inmyshell:𝑙 !! 𝑛
gives 𝑛th element th element of list 𝑙
Ac1ons• Ac1onsarejustlikeanyothervalue--wecanstorethem,
passthemtofunc1ons,rearrangethem,etc:
hellos::[IO()]hellos=[putStrLn“Hi”,putStrLn“Hey”,putStrLn“Topofthemorningtoyou”]main=sequence_(reversehellos)
Prelude>:lhellos...*Main>mainTopofthemorningtoyouHeyHI
baz.hs:
inmyshell:
sequence_::[IO()]->IO()
CombiningAc1ons• Theinfixoperator>>takestwoac1onsaandbandyieldsan
ac1onthatdescribestheeffectofexecu1ngathenexecu1ngbaZerward
• Tocombinemanyac1ons,usedonota1on:
howdy::IO()howdy=putStr“how”>>putStrLn“dy”
bonjour::IO()bonjour=doputStr“Bonjour!”putStr“”putStrLn“Commentcava?”
QuickAside:BacktoSEQEQ*• Dowes1llhaveit?Yes!
leta=PutStrLn"hello"indoaa
doPutStrLn"hello"PutStrLn"hello"
=
*SEQEQ=subs1tu1onofequalsforequals
anac1onmadeupofdoingaandthendoingaagainwhereaisthePutStrLn"hello"ac1on
anac1onmadeupofdoingthePutStrLn"hello"ac1onandthendoingthePutStrLn"hello"ac1onagain
InputAc1ons• Someac1onshaveaneffectandyieldaresult:
• Whatcanwedowiththesekindsofac1ons?– wecanextractthevalueandsequencetheeffectwithanother:
--getalineofinputgetLine::IOString--getallofstandardinputun1lend-of-fileencounteredgetContents::IOString--getcommandlineargumentlistgetArgs::IO[String]
InputAc1ons• Someac1onshaveaneffectandyieldaresult:
• Whatcanwedowiththesekindsofac1ons?– wecanextractthevalueandsequencetheeffectwithanother:
--getalineofinputgetLine::IOString--getallofstandardinputun1lend-of-fileencounteredgetContents::IOString--getcommandlineargumentlistgetArgs::IO[String]
dos<-getLineputStrLns
InputAc1ons• Someac1onshaveaneffectandyieldaresult:
• Whatcanwedowiththesekindsofac1ons?– wecanextractthevalueandsequencetheeffectwithanother:
--getalineofinputgetLine::IOString--getallofstandardinputun1lend-of-fileencounteredgetContents::IOString--getcommandlineargumentlistgetArgs::IO[String]
dos<-getLineputStrLns
shastypestring getLinehastypeIOstring
InputAc1ons• Someac1onshaveaneffectandyieldaresult:
• Whatcanwedowiththesekindsofac1ons?– wecanextractthevalueandsequencetheeffectwithanother:
--getalineofinputgetLine::IOString--getallofstandardinputun1lend-of-fileencounteredgetContents::IOString--getcommandlineargumentlistgetArgs::IO[String]
dos<-getLineputStrLns
ThinkoftypeIOStringasaboxcontainingacomputa1onthatwilldosomeworkandthenproduceastring.getLineissuchabox.the<-getstheStringoutofthebox
s
The"do"packagesupthegetLineandtheputStrLnac1onsupintoabiggercompositebox
s
InputAc1ons
main::IO()main=doputStrLn“What’syourname?”s<-getLineputStr“Hey,“putStrsputStrLn“,coolname!”
• Awholeprogram:
importSystem.IOimportSystem.EnvironmentprocessArgs::[String]->StringprocessArgs[a]=aprocessArgs_=""echo::String->IO()echo""=putStrLn"BadArgs!"echofileName=dos<-readFilefileNameputStrLn"Hereitis:"putStrLn"***********"putStrsputStrLn"\n***********"main::IO()main=doargs<-getArgsletfileName=processArgsargsechofileName
importmodules
containsreadFile
containsgetArgs,getProgName
<-nota1on:RHShastypeIOTLHShastypeT
letnota1on:RHShastypeTLHShastypeT
SEQEQ(Again!)• Recall:s1++s2concatenatesStrings1withStrings2• Avalidreasoningstep:
lets="hello"indoputStrLn(s++s) = do
putStrLn("hello"++"hello")
SEQEQ(Again!)• Recall:s1++s2concatenatesStrings1withStrings2• Avalidreasoningstep:
• Avalidreasoningstep:
lets="hello"indoputStrLn(s++s) = do
putStrLn("hello"++"hello")
=dolets="hello"putStrLn(s++s)
doputStrLn("hello"++"hello")
SEQEQ(Again!)• Recall:s1++s2concatenatesStrings1withStrings2• Avalidreasoningstep:
• Avalidreasoningstep:
• Wait,whataboutthis:
lets="hello"indoputStrLn(s++s) = do
putStrLn("hello"++"hello")
=dolets="hello"putStrLn(s++s)
doputStrLn("hello"++"hello")
dos<-getLineputStrLn(s++s)
doputStrLn(getLine++getLine)≠
wrongtype:getLine::IOString
SEQEQ(Again!)• Invalidreasoningstep?
lets=getLineindoputStrLn(s++s)
doputStrLn(getLine++getLine)=?
SEQEQ(Again!)• Invalidreasoningstep?
lets=getLineindoputStrLn(s++s)
doputStrLn(getLine++getLine)
wrongtype:s::IOString
wrongtype:getLine::IOString
=?
SEQEQ(Again!)• Invalidreasoningstep?
• TheHaskelltypesystemshowsx<-eisdifferentfromletx=e
– xhasadifferenttypeineachcase– letx=eenablessubs1tu1onofeforxinwhatfollows– x<-edoesnotenablesubs1tu1on--ahemp1ngsubs1tu1onleavesyouwithcodethatwon'teventypecheckbecausexandehavedifferenttypes(typeTvs.typeIOT)
lets=getLineindoputStrLn(s++s)
doputStrLn(getLine++getLine)
wrongtype:s::IOString
wrongtype:getLine::IOString
=?
Magic?TheListmonadwasnot“magic.”Listcomprehensions,“iterators”,guards,couldallbeexpressedinthepurefunc1onallanguagewithnomagicextensions.TheIOmonadismagic.Butallthemonadiccombinatorsformanipula1ngit,sequencing,etc.,arenotmagic;theyareexpressibleinthepurefunc1onallanguage.
HASKELLEFFECTS:REFERENCES
ComingBacktoReferencesAgain• RememberthisOCamlfunc1on:
• Weno1cedthat:
• WhatifwewritesomethingsimilarinHaskell?
letx=ref0letfoo(y:int):int=x:=!x+1;arg+!x;
lety=foo3iny+y ≠ foo3+foo3
foo:int->int
:int
ComingBacktoReferencesAgain• RememberthisOCamlfunc1on:
• Weno1cedthat:
• WhatifwewritesomethingsimilarinHaskell?
letx=ref0letfoo(y:int):int=x:=!x+1;arg+!x;
lety=foo3iny+y ≠ foo3+foo3
foo:int->int
:int
x::Refintfoo::int->intfooy=x:=readx+1;arg+!x;
doesn'ttypecheck
HaskellTypesTellYouWheretheEffectsAren't!
foo :: int -> int
Haskellfunc/ontypesarepure--totallyeffect-free
Haskell’stypesystemforces*purityonfunc1onswithtypea -> b • noprin1ng• nomutabledata• noreadingfromfiles• noconcurrency• nobenigneffects(likememoiza1on)
*exceptforunsafePerformIO
exp :: int Samewithexpressions.Expressionswithjusttypeinthavenoeffect!
foo :: int -> int totallypurefunc/on
<code> :: IO int
suspended(lazy)computa/onthatperformseffectswhenexecuted
HaskellTypesTellYouWheretheEffectsAren't!
foo :: int -> int totallypurefunc/on
<code> :: IO int
suspended(lazy)computa/onthatperformseffectswhenexecuted
bar :: int -> IO int totallypurefunc/onthatreturnssuspendedeffec@ulac/on
HaskellTypesTellYouWheretheEffectsAren't!
print :: string -> IO ()
reverse :: string -> string
reverse “hello” :: string
print (reverse “hello”) :: IO ()
thetypesystemalwaystellsyouwhenaneffecthashappened–effectscan’t“escape”theI/Omonad
HaskellTypesTellYouWheretheEffectsAren't!
References
read :: Ref a -> IO a
(+) :: int -> int -> int
r :: Ref int
(read r) + 3 :: int
Doesn’t type check
References
read :: Ref a -> IO a
(+) :: int -> int -> int
r :: Ref int
do
x <- read r return (x + 3)
the"return"ac1onhasnoeffect;itjustreturnsitsvalue
createsacomposi1onac1onthatreadsareferenceandproducesthevalueinthatreferenceplus3
MutableState
Haskellusesnew,read,andwrite*func1onswithintheIOMonadtomanagemutablestate.
main = do r <- new 0 -- let r = ref 0 in inc r -- r := !r+1; s <- read r -- s := !r; print s
inc :: Ref Int -> IO () inc r = do
v <- read r -- v := !r
write r (v+1) -- r := !v +1
new :: a -> IO (Ref a) read :: Ref a -> IO a write :: Ref a -> a -> IO ()
*actuallynewRef,readRef,writeRef,…
MONADS
TheBiggerPicture• Haskell'stypesystem(atleastthepartofitthatwehaveseen
sofar)isverysimilartotheMLtypesystem– eg:typingrulesforpureprimi1ves(func1ons,pairs,lists,etc)aresimilarinbothcases:• iff:t1->t2ande:t1then(fe):t2• ife1:t1ande2:t2then(e1,e2):(t1,t2)
• WhatHaskellhasdonethatisspecialis:
– ithasbeenverycarefultogiveeffec�ulprimi1vesspecialtypesinvolving"IOt"
– allowscomposi1onofac1onswithtype"IOt"• theIOdatatype+itscomposi1onfunc1onsarecalledamonad
– ithassyntaxforprogrammingwithmonads(donota1on)– MLcoulddothosethingstoo(ie:thesedecisionsdonotdependuponalanguagehavelazyevalua1on)
Wecantalkaboutwhatmonadsarebyreferringtotheirinterface
Recallaninterfacedeclaressomenewabstracttypesandsomeopera1onsovervalueswiththoseabstracttypes.Forexample:
moduletypeCONTAINER=sigtype‘at(*thetypeofthecontainer*)valempty:‘atvalinsert:‘a->‘at->‘atvalremove:‘at->‘aop1on*‘atvalfold:(‘a->‘b->‘b)->‘b->‘at->‘bend
There are lots of different implementations of such containers: queues, stacks, sets, randomized sets, ...
Wecantalkaboutwhatmonadsarebyreferringtotheirinterface
Recallaninterfacedeclaressomenewabstracttypesandsomeopera1onsovervalueswiththoseabstracttypes.Forexample:
moduletypeCONTAINER=sigtype‘at(*thetypeofthecontainer*)valempty:‘atvalinsert:‘a->‘at->‘atvalremove:‘at->‘aop1on*‘atvalfold:(‘a->‘b->‘b)->‘b->‘at->‘bend
There are lots of different implementations of such containers: queues, stacks, sets, randomized sets, ...
Interfaces can come with some equations one expects every implementation to satisfy. eg:
fold f base empty == base
The equations specify some, but not all of the behavior of the module (eg: stacks and queues remove elements in different orders)
MonadsAmonadisjustapar1cularinterface.Twoviews:
– (1)interfaceforaverygenericcontainer,withopera1onsdesignedtosupportcomposi/onofcomputa1onsoverthecontentsofcontainers
– (2)interfaceforanabstractcomputa1onthatdoessome“bookkeeping”ontheside.By“bookkeeping”wemean“effects”.Onceagain,thesupportforcomposi1oniskey.
– sincefunc1onalprogrammersknowthatfunc1onsaredata,thetwoviewsactuallycoincide
MonadsAmonadisjustapar1cularinterface.Twoviews:– (1)interfaceforaverygenericcontainer
• supportscomposi/onofcomputa1onsoverthecontentsofcontainers
– (2)interfaceforanabstractcomputa1onthatdoessome“bookkeeping.”• bookkeepingiscodefor“hasaneffect”.Onceagain,thesupportforcomposi1oniskey.
– sincefunc1onalprogrammersknowthatfunc1onsaredata,thetwoviewsactuallycoincide
Manydifferentkindsofmonads:– monadsforhandling/accumula1ngerrors– monadsforprocessingcollec1onsenmasse– monadsforloggingstringsthatshouldbeprinted– monadsforcoordina1ngconcurrentthreads(seeOCamlAsynclibrary)– monadsforbacktrackingsearch– monadsfortransac/onalmemory
MonadsAmonadisjustapar1cularinterface.Twoviews:– (1)interfaceforaverygenericcontainer
• supportscomposi/onofcomputa1onsoverthecontentsofcontainers
– (2)interfaceforanabstractcomputa1onthatdoessome“bookkeeping.”• bookkeepingiscodefor“hasaneffect”.Onceagain,thesupportforcomposi1oniskey.
– sincefunc1onalprogrammersknowthatfunc1onsaredata,thetwoviewsactuallycoincide
Becauseamonadisjustapar1cularinterface(withmanyusefulimplementa1ons),youcanimplementmonadsinanylanguage– But,Haskellisfamousforthembecauseithasaspecialbuilt-insyntaxthat
makesmonadspar1cularlyeasyandeleganttouse– F#,Scalahaveadoptedsimilarsyntac1cideas– MonadsalsoplayaveryspecialroleintheoveralldesignoftheHaskell
languageduetotheconfinementofeffects• Monadsaretherightwaytoenablebotheffectsandsubs1tu1onofequalsforequals
Whatisthemonadinterface?
+someequa1onsspecifyinghowreturnandbindarerequiredtointeract
Considerfirstthe“containerinterpreta1on”:
§ ‘aMisacontainerforvalueswithtype‘a
§ returnxputsxinthecontainer
§ bindcftakesthevaluesincoutofthecontainerandappliesftothem,forminganewcontainerholdingtheresults
- bindcfisoZenwrihenas:c>>=f
moduletypeMONAD=sigtype‘aMvalreturn:‘a->‘aMval(>>=):‘aM->(‘a->‘bM)->‘bMend
TheOp1onsasaContainer
moduleOp1onMonad=structtype‘aM=‘aop1onletreturnx=Somexlet(>>=)cf=matchcwithNone->None|Somev->fvend
put value ina containertake value v out
of a container cand then apply f, producing a new container
moduletypeMONAD=sigtype‘aMvalreturn:‘a->‘aMval(>>=):‘aM->(‘a->‘bM)->‘bMend
TheOp1onsasaContainer
typefile_name=stringvalread_file:file_name->stringMletconcatf1f2=readfilef1 >>=(funcontents1->readfilef2 >>=(funcontents2->return(contents1^contents2)
put value ina containertake value v out
of a container cand then apply f, producing a new container
using the option container:
moduletypeMONAD=sigtype‘aMvalreturn:‘a->‘aMval(>>=):‘aM->(‘a->‘bM)->‘bMend
moduleOp1onMonad=structtype‘aM=‘aop1onletreturnx=Somexlet(>>=)cf=matchcwithNone->None|Somev->fvend
Book-keepingInterpreta1on:TheOp1onMonadasPossiblyErroneousComputa1on
moduletypeMONAD=sigtype‘aMvalreturn:‘a->‘aMval(>>=):‘aM->(‘a->‘bM)->‘bMend
moduleErrorMonad=structtype‘aM=‘aop1onletreturnx=Somexlet(>>=)cf=matchcwithNone->None|Somev->fvendtypefile_name=string
valread_file:file_name->stringMletconcatf1f2=readfilef1 >>=(funcontents1->readfilef2 >>=(funcontents2->return(contents1^contents2)
setting upbookkeepingfor error processing
compose bookkeeping:check to see iferror has occurred, if so return None,else continue
using the error monad:
ListsasContainers
moduletypeMONAD=sigtype‘aMvalreturn:‘a->‘aMval(>>=):‘aM->(‘a->‘bM)->‘bMend
moduleListMonad=structtype‘aM=‘alistletreturnx=[x]let(>>=)cf=List.flahen(List.mapfc)end
random_sample:unit->intMmonte_carlo:int->int->int->resultletexperiments:resultM=random_sample()>>=(funs1->random_sample()>>=(funs2->random_sample()>>=(funs3->return(monte_carlos1s2s3)
put elementinto listcontainer
apply f to all elements of the list c, creating a list of lists and then flatten results in to single list
using the list monad:
ListsasContainers
moduletypeMONAD=sigtype‘aMvalreturn:‘a->‘aMval(>>=):‘aM->(‘a->‘bM)->‘bMend
moduleListMonad=structtype‘aM=‘alistletreturnx=[x]let(>>=)cf=List.flahen(List.mapfc)end
random_sample:unit->intMmonte_carlo:int->int->int->resultletexperiments:resultM=random_sample()>>=(funs1->random_sample()>>=(funs2->random_sample()>>=(funs3->return(monte_carlos1s2s3)
using the list monad:
one result;no nondeterminismcompose many
possible results (c)with a nondeterministiccontinuation f
AContainerwithaStringontheSide(aka:Alogging/prin1ngmonad)
moduletypeMONAD=sigtype‘aMvalreturn:‘a->‘aMval(>>=):‘aM->(‘a->‘bM)->‘bMend
moduleLoggingMonad=structtype‘aM=‘a*stringletreturnx=(x,“”)let(>>=)cf=let(v,s)=cinlet(v’,s’)=fvin(v’,s^s’)endrecord:(‘a->‘b)->‘a->string->‘bM
letrecordfxs=(fx,s)letdox=recordreadx“readit”>>=(funv->recordwritev“wroteit”>>=(fun_->recordwritev“wroteitagain”>>=(fun_->returnv
nothing loggedyetconcatenate the
log of c withthe log producedby running f
using the logging monad:
AContainerwithaStateontheSide(aka:Alogging/prin1ngmonad)
moduletypeMONAD=sigtype‘aMvalreturn:‘a->‘aMval(>>=):‘aM->(‘a->‘bM)->‘bMend
moduleStateMonad=structtypestate=addressintmaptype‘aM=‘a*(state->state)letreturnx=???let(>>=)cf=???letreada=???letwriteav=???end
MonadLawsJustlikeoneexpectsanyCONTAINERtobehaveinapar1cularway,onehasexpecta1onsofMONADs.LeZiden1ty:“returndoesnothingobservable”(1) returnv>>=f==fv
Rightiden1ty:“returns1lldoesn’tdoanythingobservable”(2)m>>=return==mAssocia1vity:“composingmwithffirstandthendoinggisthesameasdoingmwiththecomposi1onoffandg”(3)(m>>=f)>>=g==m>>=(funx->fx>>=g)
BreakingtheLawJustlikeoneexpectsanyCONTAINERtobehaveinapar1cularway,onehasexpecta1onsofMONADs.LeZiden1ty:“returndoesnothingobservable”(1) returnv>>=f==fv
module LoggingMonad = struct type ‘a M = ‘a * string let return x = (x, “start”) let (>>=) c f = let (v, s) = c in let (v’,s’) = f v in (v’, s ^ s’) end
return 3 >>= fun x -> return x == (3,”start”) >>= fun x -> return x == (3, “start” ^ “start”) == (3, “startstart”)
(fun x -> return x) 3 == return 3 == (3, “start”)
BreakingtheLawWhataretheconsequencesofbreakingthelaw?Well,ifyoutoldyourfriendyou’veimplementedamonadandtheycanuseitinyourcode,theywillexpectthattheycanrewritetheircodeusingequa1onslikethisone:returnx>>=f==fxIfyoutellyourfriendyou’veimplementedthemonadinterfacebutnoneofthemonadlawsholdyourfriendwillprobablysay:Ok,tellmewhatyourfunc1onsdothenandpleasestopusingthewordmonadbecauseitisconfusing.ItislikeyouareclaimingtohaveimplementedtheQUEUEinterfacebutinsertandremoveareFirst-In,First-Outlikeastack.InHaskellorF#orScala,breakingthemonadlawsmayhavemoresevereconsequences,becausethecompileractuallyusesthoselawstodosometransforma1onsofyourcode.
HASKELLWRAPUP
BynowyoushouldappreciatethesignificanceofHaskell’slogo,combiningthelambdaλandthemonadicbindoperator>>=.
AMonadicSkin
InlanguageslikeMLorJava,thereisnowaytodis1nguishbetween:– purefunc1onswithtypeint->int– effec�ulfunc1onswithtypeint->int
InHaskell,theprogrammercanchoosewhentoliveintheIOmonadandwhentoliveintherealmofpurefunc1onalprogramming.
– Counter-point:Wehaveshownthatitisusefultobeabletobuildpureabstrac1onsusingimpera1veinfrastructure(eg:laziness,futures,parallelsequences,memoiza1on).Youcan’tdothatinHaskell(withoutescapingthetypesystemviaunsafeI0)
Interes1ngperspec1ve:ItisnotHaskellthatlacksimpera1vefeatures,butrathertheotherlanguagesthatlacktheabilitytohaveasta1callydis1nguishablepuresubset.
Acheckedpure-impuresepara1onfacilitatesreasoningaboutprograms,especiallyconcurrentones.
TheCentralChallenge
Arbitrary effects
No effects
Safe
Useful
Useless
Dangerous
TheChallengeofEffects
Arbitrary effects
No effects
Useful
Useless
Dangerous Safe
Nirvana
Plan A �(everyone else)
Plan B �(Haskell)
TwoBasicApproaches:PlanA
Examples• Regions• Ownershiptypes• Rust
– followingfromresearchlanguageslikeVault,Spec#,Cyclone
Arbitrary effects
Default = Any effect �Plan = Add restrictions
TwoBasicApproaches:PlanB
Twomainapproaches:• Domainspecificlanguages
(SQL,Xquery,Googlemap/reduce)
• Wide-spectrumfunc1onallanguages+controlledeffects(e.g.Haskell)
Value oriented programming
Types play a major role
Default = No effects�Plan = Selectively permit effects
LotsofCrossOver
Arbitrary effects
No effects
Useful
Useless
Dangerous Safe
Nirvana
Plan A �(everyone else)
Plan B �(Haskell)
Envy
accordingtoSimonPeytonJones
LotsofCrossOver
Arbitrary effects
No effects
Useful
Useless
Dangerous Safe
Nirvana
Plan A �(everyone else)
Plan B �(Haskell)
Ideas; e.g. Software Transactional Memory
Arewethereyet?
No effects
Useful
Useless
Dangerous Safe
Nirvana
Plan B �(Haskell)
IsHaskellhere?
Orhere?
Someissues:
• Can’tencapsulateeffec�ulcomputa1oninsidepure-
func1onaltypes.
• Lazythunkscangiveaperformancepenalty
AnAssessmentandaPredic1on
One of Haskell’s most significant contributions is to take purity seriously, and relentlessly pursue Plan B. Imperative languages will embody growing (and checkable) pure subsets.
-- Simon Peyton Jones