Haskell - cs.princeton.edu · Haskell Another cool, typed, func1onal programming language • Like...

Preview:

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

Recommended