Upload
others
View
1
Download
0
Embed Size (px)
Citation preview
TitlePageCopyrightPageAnnotatedListofKeywordsAnnotatedListofSectionsPrefaceI.PureVersusImpureIaScopeExampleIbImpureCode
II.FunctionalProgrammingandtheRealWorldIIaDecomposedHaskellProgramIIbQuestionIIcAnswer
III.Monadsandthe(>>=)CombinatorIIIaQuestionIIIbAnswer
IV.ThereturnFunctionIVagetIntIVbaskIVcreturn_or_recursIVdQuestionIVeAnswer
V.MimickingCompositionofFunctionsVaQuestionVbAnswer
VI.ThedoBlockVIaScopingIndoBlockVIbJava-likeScopingVIcEquivalentMonadicExpressionVIdSubstitution1VIeSubstitution2VIfSubstitution3VIgMonadicExpressionEquivalenttodoBlockVIhEquivalenceStep1VIjEquivalenceStep2VIkEquivalenceStep3VIlEquivalenceStep4VImEquivalenceStep5VInQuestionVIoAnswer
VII.TheListMonadVIIaNesteddoBlockLoopVIIbDefiningpairsIVIIcDefiningpairsIIVIIdDefiningpairsIIIVIIeQuestionVIIfAnswerVIIgProgramTestingpairs
VIII.IntroductiontoMonadTransformersVIIIa'HelloWorld'VIIIb'HelloWorld'UsingLisTVIIIcABitAboutGHCIVIIIdGHCIExampleVIIIeNonloopExampleVIIIfQuestionVIIIgAnswer
IX.IntrototheStateMonadIXaStateMonad(>>=)IXbQuestionIXcAnswer
X.MoreAbouttheStateMonadXaDeriving>>fortheStatemonadXbTestinggetandputXcQuestionXdAnswer
XI.AStateTExample.XIaRepeatednext_coinsXIbDefinitionofrecursXIcDefinitionofdispenseXIdQuestionXIeAnswer
XII.MonadsXIIaDefinitionofMonadXIIbMonadicAxiomsXIIcQuestionXIIdAnswer
AbouttheAuthor
FromSimpleIOtoMonadTransformersbyJAdrianZimmer
Readerdiscussionencouragedatthisebooklet'swebpage.
PublishedbyBonsaiReadsanddistributedbyAmazon.
CoverDesign:JenniferMelot
Copyright2014byJAdrianZimmerAllrightsreserved.Permissiontocopyisgrantedtothepurchaserforpersonaluseprovidedpurchasertakesreasonablestepstoprohibitfurthercopyingbyothers.
Thisisversion1.2.Seefordifferencesbetweenversions.
Therearetwotablesofcontents.Oneisanannotatedlistofsections.Theotherhasalistingforeverymajorcodesegment.
AnnotatedListofKeywordsmonad
EstablishingamindsetforunderstandingHaskell'smonadsisthepurposeofthispublication.
Haskell
ReadersareexpectedtoknowhowtowritesimplerecursiveprogramsintheHaskellprogramminglanguage.
functionalprogramming
Functionalandimperativeprogrammingarecompared.
IO
SomeexamplesinvolveconsoleIOinHaskell.
listmonad
Haskell'sListmonadisdescribed.
statemonad
Haskell'sStatemonadisdescribed.
monadtransformer
Examplesofmonadtransformersaregiven.
smallscreen
Thismonographhasbeenformattedforpagingreadersonsmallorlargescreens.
Manycodeexamplesarehardcodedintotheirownpages.Haskellsourcecodehasbeenkepttoshortlinelengths.
AnnotatedListofSectionsI.PureVersusImpure
Mathematicalfunctionsversusside-effectsincomputerprograms.
II.FunctionalProgrammingandtheRealWorld
Haskell'swayofdealingwiththerealworld.
III.Monadsandthe(>>=)Combinator
AnintuitivelookathowmonadsworkusingtheIOmonad's(>>=)combinator.
IV.ThereturnFunction
Passingvaluestomonadcreatingfunctions.
V.MimickingCompositionofFunctions
Both(>>)and(>>=)mustbeassociative.
VI.ThedoBlock
Syntacticsugarwhichenablesimperativeprogramminginapurelyfunctionallanguage.
VII.TheListMonad
Makingmapmoreflexible.
VIII.IntroductiontoMonadTransformers
Awaytocombinedifferentkindsofmonads—demonstratedwiththeListTtransformer.
IX.IntrototheStateMonad
CreatingyourownIO-likemonadtooperateonaminiworldofyourchoice.
X.MoreAbouttheStateMonad
Herewediscussinteriorobjectsandstates
XI.AStateTExample.
Embeddinglogicinacoinchanger.
XII.Monads
Hereisacompletedefinitionofamonadandhowithelpsusdrawsomeconclusionsabouttheinteriorobjectsofthereturnfunction.
Preface“FromSimpleIOtoMonadTransformers”isanintroductiontoHaskellmonads.Iwroteitforyouifyou
areawarethatfunctioncompositionmakesupsomekindofalgebraicsystem,
knowsomeHaskell,
haveseenadescriptionorexampleofHaskell'smonads,and
arenotyetcomfortablewiththeconcept.
IfyouneedsomehelpwithHaskellbeforereadingthisthenturntothefirstfivechaptersof“LearnYouaHaskellfortheGreatGood!”orreadin“HaskellHardandFast”throughsection3.2.2.
Mygoalistohelpyoudevelopanintuitivenotionofmonadthatisaccurateenoughtobeuseful.Suchintuitioncanbedifficulttoachievebecausetheconceptisabstractandexplanationsareoftenaimedatthemathematicallysophisticated.Whentheyarenot,theytendtobeofthe“Seehowtodothis!Wasn'tthateasy?”variety.
Neitherapproachworksverywell.Whatyouneedishelpunderstandinghowabstractionsorganizedetails.Contrarytopopularopinion,wecannotcreateabstractionsbyignoringdetails.Whatabstractionsdoiscontrolthetimeandplaceforsuchthinking.EdsgerDijkstraoncereactedtothetop-downprogrammingmovement–whichhehelpedstart–bywritingthathehimselfdidnotcreateprogramspurelyfromthetopdown.Hemerelyorganizedthemthatwayforclarityandcorrectness.
HereIdiscussmonadsbylookingatthemfromboththeoutsideinandtheinsideout.Thefocusissolelyonmonads.Whileitistruethattheconceptsoffunctorandapplicativetypecanbesteppingstonesonthewaytomonads,Ihaveomittedthoseconceptsintheinterestofbrevity.Onceyouhaveunderstoodmonadsitshouldnotbedifficulttofillinsimplerconcepts.
IntheinterestofprioritizingclearerexplanationsovercontentIhavealsoomittedadescriptionofalmostallofHaskell'smonads.Youwillfindplentyofdocumentationelsewhere.ThemonadsIhavechosentopresentwillgiveyouamentalmodelformakingsenseofthatdocumentation.Youcouldsaymysolegoalistolubricateyourmindforthinkingaboutthesethings.
Mostsectionshaveoneworkedexercise.ItisOKifyoucannotworkit.Justpausingtothinkaboutitwillgiveyouabetterunderstandingthanmerelyreadingthroughanexamplewould.
Aboutversion1.2:
Unlikeversions1.0and1.1whicharealmostidentical,version1.2hasarewriteofthematerialontheListandStatemonadsaswellastheListTandStateTmonadtransformers.AadditionalsectionontheStatemonadhasbeenadded.Somesmallerrorsinthesesectionshavebeencorrected.Ifyouhaveaversionearlierthan1.2andwanttoseealistofthecorrectionsmadein1.2,lookhere.
JAdrianZimmer
I.PureVersusImpureApurelyfunctionalprogramcanbeseenasamathematicalfunction.Thedefinitionofthisfunctionexplainshowtoevaluateoneexpression.Theexplanationmayinvolvetheevaluationofotherfunctions.Eachexplanationhasreferentialtransparencywhichmeansthatifyouseethesameexpressiontwicewithinonecontextthethingitrepresentswillbethesame.
Thiscontrastswiththemorecommonimperativeprogrammingparadigm.Imperativeprogramsareasequenceofcommands.Someofthesecommandschangethevaluesassignedtovariablesandperformothersideeffectssothatreferentialtransparencyislacking.
Asanexampleofreferentialtransparencyconsiderthisdefinitionofafactorialfunction
f0=1
fx=x*(f(x-1))
Toevaluatef3,xhasthefixedmeaningof3.Itdoesn'talterthisvalueduringtheevaluationoff3.Insteadtimeoutistakentoevaluatef2.Duringthatevaluationxhasafixedmeaningof2.Eachevaluationoffisaseparatecontext.
Considerthiscodewhichsetsupacontextinwhichx,y,andfaredefinedandinwhichf3istobeevaluated.
letx=1
y=2
f0=x
fx=(x+y)*(f(x-1))
inf3
Inthepartwherethedefinitionsofx,y,andfresidethereisanothercontextcontainingtheactualdefinitionofffornonzeroarguments.Thexinthedefinitionoffisnotthesameasthexwhichisboundto1.Beingaparameterthisxonlyhasavaluewhenaparticularfxisbeingevaluated.
Iassumereadersknowthattheyinthisdefinitionoffrepresents2.Thisisnormalstaticscopingofprogramminglanguages.Youwillsometimeshearthatyisfreeinthisdefinitionoff.Thisisbecauseasfarasthedefinitionoffisconcernedycanbeanyvalueforwhichtheplusoperatorworks.Howeverthisparticularcontextiscontainedinanoutercontextwhereyisboundto2.Sinceyisnotaparameterordefinedinaletexpressionitkeepsthebindingfromtheoutercontext.
ConsiderthisexamplewhichsupposesafunctionreadALinethatreturnsthenextlineofinputenteredintotheconsole.
x=readALine
printx
y=readALine
printyIftherewerereferentialtransparencywecouldconcludethat
x
equalsy.Thatisn'tnecessarilytruebecausethereisaside
effecttoevaluatingreadALine.Somethingchangessothata
differentlineisreadeachtimereadALineisevaluated.In
thelanguageoffunctionalprogrammingreadALineiscalled
“impure”.
Inallthreeexampleswehavetothinkwhatvaluesoursymbolsand
expressionsrepresent.Intheexampleswithf,thereare
contextualrulestohelpus.Theserulesdonotvaryfromprogram
toprogram.Afterwebecomecomfortablewiththem,wedon'thaveto
thinkaboutwhichvaluesofxappearwhere.Theexamplewith
readALineisdifferent.Insuchexampleswemustalwaysthink
abouthowvalueschangeascontrolflowsaroundoursourcecode.
Nothavingtothinkconsciouslyabouthowvalueschangeisthe
reasonreferentialtransparencymakesacomputerprogrameasierto
read.Butthereisapricebecausereferentialtransparencyalso
makesaprogrammoredifficulttoapplytotherealworld.The
difficultyinaccommodatingareadALinefunctionisanexample
ofthis.
Thusfunctionalprogrammingprovidesuswithadilemma.Ononehand
welikethesimplicityandclarityofapurelyfunctionalprogram.
Ontheotherwedonotlikethecomplicationsthatarisewhenwe
wanttodoIOorplaywithothersideeffects.
ThismonographisaboutHaskell'swayofdealingwiththatdilemma.
II.FunctionalProgrammingandtheRealWorldTounderstandhowafunctionalprogramcanhandlesideeffects,thinkofasideeffectaschangingsomethingintheworldoutsideoftheprogram'sdataspace.Imaginethattheworldflickersfromonerealitytoanotherasthingschange.Aprogramwhichchangestheworldthenissomethinglikethis
my_progold_world-->new_world
Insteadofchangingtheworlditmakesanewone,flickeringfromchangetochangeontheway.
AHaskellprogramisaworldremakingfunctionwhosecomponentpartsareworldremakingfunctions.Hereisanexample.
be_nice=
putStrLn"HelloWorld"
say_hello=
putStrLn"Nicetomeetyou!"
main=say_hello>>be_niceThefunctionsbe_nice,say_hello,andmaincanall
beseenashavingthistypeWorld->WorldThefunction(>>)combines
say_helloandbe_niceto
createmain.Wecanviewitstypesignatureas(>>)::
(World->World)->
(World->World)->
(World->World)Thinkof(>>)as“then”becauseweare
talkingabout
oneworldchangingactionafteranother.Leavingaside,for
themoment,preciselyhow(>>)works,wecanunderstand
howtogetsequentialitywithinafunctionalparadigmbyimagining
thatourprogramhasbeendefinedthiswaybe_niceworld=
(putStrLn"HelloWorld")world
say_helloworld=
(putStrLn"Nicetomeetyou!")world
mainworld=say_hello(be_niceworld)Onlywecannotwriteitthatway
becauseHaskell
abstractstheconceptofworldawayfromtheprogrammer.
AHaskellprogrammerismoreconcernedwithcreatingaprogramthan
withremakingworlds.Infact,theworldisfullofthingsthe
Haskellprogrammeroughtnotbeplayingwith.Soitisbetter
tomake(>>)applytosomesimpledatatypethantomakeit
refertoworldremakingfunctions.Haskellprovidesaparameterized
type,IOaforthispurpose.AsanexamplesputStrLn"hi"::IO()
putStrLn::String->IO()Thetypefor(>>)is(>>)::IOa->IOb-
>IObThisismuchsimplerthantheversionabovewhichusesworld
remaking
functions.MoreoverthisisHaskell.
YoumayhavenoticedthatIOneedsaparameterbeforeit
becomesarealtype.Suchathingisusuallycalleda
“parameterized”type.Iprefertothinkofitasametatype.I
willdiscussthepurposeofthisparameterinSectionIII.
AnotherwaytothinkofIOisthatitisatype
constructor.Giveitanargumentanditconstructsatype.
Insections
SectionVIIIand
SectionIXyouwillseeauseformonadtypeconstructorsthattake
morethanoneparameter.
TheIOmetatypeisknownasamonad.Theconceptofmonadisuseful
becauseit
emphasizescertainsimilaritiesbetweenotherwisedifferentpartsof
ourprograms.Thesesimilaritiesgiveusaframeworkinwhichto
understandnewthings.Onceyougetusedtotheframework,ithelps
withtheunderstanding.
Onewaytothinkofamonadmistoseeitasaworkshopin
whichwecancreateandworkwithobjectsoftypema.
Allmonadicworkshopsmhavea(>>)function.Its
generaltypeis
(>>)::Monadm=>
ma->mb->mbanditisoftencalleda“combinator”.Thetypesignature
for(>>)particularizesto(>>)::IOa->IOb->IObintheIOmonad.
Iusetheterm“object”foranythingaHaskellprogramcan
manipulate.Thisincludesfunctions.Objectswhosetypeisof
theformmawheremisamonadIwillcall“monadic
objects”.
Althoughthesyntaxrequirementsfor(>>)arethesameinallworkshops,the
behaviorof(>>)differs.Onewaytothinkofthiswouldbe
toimaginethateachworkshopexistsinitsownuniversewithits
ownphysicallaws.Thisofcoursewouldimplythatthe(>>)
youseeinoneworkshopwouldn'tnecessarilybehavethesameasin
another.Itdoesnotimplythatthephysicallawswithinanyone
workshoparecompletelyarbitrary.Asyoureadon,youwillsee
thatthesimilaritiesbetweenonemonadandanothergodeeper
thanmeresyntax.
Eachworkshophasitsowntoolsformanipulatingitsobjects.The
(>>)combinatorisonlyoneoffourthatmustbepresentin
allworkshops.
QuestionWhatwillthefollowingprogramdo?
main=
say_hello>>be_nice>>main
AnswerThefunctionmainwillwritethefollowingontheconsole
HelloWorld
Nicetomeetyou!
HelloWorld
Nicetomeetyou!
...
andsoonuntiltheuserstopsitwithctl-c.
III.Monadsandthe(>>=)CombinatorWehaveseentwofunctions,putStrLnand(>>),belongingtotheIOmonad.Ofthese,only(>>)isrequiredofallmonads.BothputStrLnanditscoworkergetLinehelpgivetheIOmonaditsuniquecapabilities.ThissectionintroducesasecondfunctionrequiredofallmonadsandalsoprovidesaratherconcreteexplanationoftheparameterainIOa.
Supposewewanttoechouserinput.WewouldwanttousethegetLinefunctionwhichobtainsalineofuserinputfromtheconsole.ButwecannotwriteputStrLngetLinebecausethetypesignaturesdon'tmatch;getLineproducesanIOStringandputStrLnrequiresaString.Thetypesignaturesdon'tmatchbecauseofHaskell'swayofdealingwiththepotentialimpurityofgetLine.HaskellhasgetLinereturnamonadicobjectinsteadofastring.
BecausegetLinerepresentsamonadicobjectitisopaque.Thatmeansthestringitobtainscannotbeseen.ThepotentialimpurityofgetLineissquashed.ThestringthatgetLineobtainsisstilltherebutunavailableinanycontextthatwouldviolatereferentialtransparency.Saidbluntly,codethatinvokesgetLinecannottellthedifferencebetweendifferentevaluationsofgetLine.
Thissituationissomethinglikeridingabicycleandhavingacarturnleftimmediatelyinfrontofyou.Yourimmediateconcernisavoidingthecar.Ifthecarhitsyou,thepolicewillbeconcernedwithfindingthedriver.Whenyoudealwithamonadicobject,itissimilarlyopaque.Buttherearewaysits“door”canbeopenedandthenyouaredealingwithsomethingontheinteriorratherthantheobjectasawhole.
IwillcallthestringobtainedbyanexecutionofgetLineaninteriorobject.ItisinteriortothemonadicobjectwhichgetLinereturns.
Themonadiccombinator(>>=)producesthecontextwhereaninteriorobjectcanbeviewed.Itstypesignatureis(>>=)::Monadm=>ma->(a->mb)->mbwheremisanymonadandthetypesaandbarearbitrary.
Anapplicationof(>>=)wouldlooklikemon>>=fwheremonisoftypemaandfisoftypea->mb.Itisworthemphasizingthataandbcan,butneednot,bedifferenttypes.
Itisf'sjobtomakeanobjectmon2outofoneofmon'sinteriorobjects.Itis(>>=)'sjobtodecidewhattodowiththosemon2s.
ReturningtotheIOmonadconsidermain=getLine>>=putStrLnHereisanexplanationofhowmainisexecuted.
TheruntimesystemexecutesgetLineonaninitialworld_1.DuringthisexecutiongetLinefetchesaninputlinesfromtheconsole.Itreturnsaworld_2whoseinteriorvalueiss.
TheruntimesystemevaluatesputStrLnonsproducingaworldremakingfunction.
Finallytheruntimesystemexecutestheworldremakingfunctionproducedinstep2ontheworldcreatedinstep1.Thiscreatesaworldinwhichthestringshasbeenwrittenontheconsolescreen.
YouwillbenefitbycomparingthisdescriptionwithasimilaronefortheStatemonadwhenyougetaroundtoreadingSectionIX.
Amajordifferencebetween(>>=)and(>>)isthat(>>=)canmakeuseofinteriorobjectsand(>>)cannot.That(>>)willignoreanyargumentgiventoitisexpressedinthisdefinition.
(>>)mon1mon2=mon1>>=\_->mon2
Thisisthenormalwaytodefine(>>)andwewillassumeitfromnowon.
Because(>>)isdefinedintermsof(>>=),(>>=)isthemoreimportantcombinatortotalkabout.
QuestionIftheusertypes
First
Second
whatwillthefollowingprogramwrite?
line=
(\ln->
(getLine>>=putStrLn)>>
putStrLnln)
main=
putStrLn"Gimme2lines">>
(getLine>>=line)
AnswerOfcourselinecouldhavebeendefinedthiswaylineln=(getLine>>=putStrLn)>>putStrLnlnItgetsastringandsavesthatstringuntilitdoesthe
getLine>>=putStrLn
thing.Thenitwritesthestring,sotheresultofexecutingmainis:GimmeTwoLinesFirstSecondSecondFirstHereissourcecodeyoucancompileandrunmain=getLine>>=putStrLn
IV.ThereturnFunctionLet'sturntoasomewhatmorecomplicatedexamplebydefiningafunctiongetIntwhichinsiststhattheuserinputanonnegativeinteger.SuchafunctionwillbelikegetLineinthatitwillcreateamonadicobjectwithasingleinteriorobject.OnlyinthiscasethemonadicobjectwillbeanIOIntandtheinteriorobjectwillbepreparedbyus.Haskell'sreturnfunctiongivesusthenecessarycapability.Itsgeneraltypesignatureis
return::Monadm=>a->ma
Allformsofreturncreateamonadicobjectwhoseinteriorobjects(ifany)areofthesametypeastheirparameters.
FortheIOmonadtheinteriorobjectofreturnispreciselytheargumentpassedtoit.
OurgetIntfunctioncanbedefinedasfollowswhereaskputsamessageontheconsoleaskingforanonnegativeintegerandreturn_or_recurseeitherreturnsanIntorrestartsgetInt.
getInt=
ask>>
getLine>>=
return_or_uecurse
Theaskfunctioniseasytowrite
ask::IO()
ask=putStrLn
"Pleaseenteranonnegint"
AssumingabooleanvaluedfunctionisNonNegInt,whichreturnsTrueexactlywhenitisgivenanonnegativeinteger,thereturn_or_recursefunctionisalsoeasytowrite.
return_or_recurse::String->IOInt
return_or_recurses=
ifisNonNegInts
thenreturn(reads)
elsegetInt
Haskell'sreadfunctionispolymorphicandhasversionsforconvertingfromStringtovariouskindsofnumbers.HaskellknowswhichreadtousethistimebecauseitknowsthatthisreadissupposedtoproduceanInt.WithoutsomethingthatcluesHaskellinonthedesiredtypetherewouldbedifficulties.
Whereistheclue?Itliesinthereturntypeofreturn_or_recurse.ThatmakesthereturntypeofthisparticularreturnintoIOIntandsoittakesanIntargument.
ForcompletenesshereishowisNonNegIntmightbedefined
importData.Char
isNonNegInt::String->Bool
isNonNegIntx
|lengthx==0=
False
|otherwise=
letc:cs=xin
isDigitc&&
(cs==""||isNonNegIntcs)
Aboutthename“return”:
Thename“return”makessenseinrecurse_or_returnbecausegetIntwillbeinvokedbyHaskell'sruntimeIOsystemandourreturnisreturningtothatsystem.
Formostmonadshowever“return”isapoorwordtousebecausewhatisforemostinourmindsisthatreturniscreatinganewmonadicobject.
Trynottoletnamesconfuseyou.Inprogrammingasinmathematicswemustcreatenamesfornewandsometimescomplicatedconcepts.Normallanguageisinadequateforthisandourwordchoicescanbemisleading.
Themathematicianwhowrotethewellknownchildren'sbook“ThroughtheLookingGlass”wassurelythinkingofthisnamingdifficultywhenhehadHumptyDumptyjustifyhisuseoftheword“glory”toaconfusedAlicethisway:“itmeansjustwhatIchooseittomean—neithermorenorless.”
Bytheway,youmayaswellthinkofthe(>>=)combinatoras“thenwith”.
QuestionDefineafunctionfsuchthatthisprogramwillwrite“HelloWorld”.
main==f"HelloWorld">>=putStrLn
Answer
f=return
V.MimickingCompositionofFunctionsWehaveseenacloseconnectionbetweencompositionofworldremakingfunctionsandthe(>>)combinator.InHaskellwewrite
say_hello>>be_nice
insteadof
be_nice.say_hello
buttheunderlyingthoughtprocessisn'tsoverydifferent.
Functioncompositionisassociative.Thatisfortypecompatiblefunctionsf,g,andh
(f.g).h=f.(g.h)
The.operatorisenoughlikethatthe(>>)and(>>=)combinatorsthatwewouldlikethelattertobeassociativetoo.Since(>>=)isthemoreimportant,wewanttoassertsomethinglike
(mon1>>=f)>>=g==
mon1>>=(f>>=g)
tobetrue.Ofcourse,itisnotevensyntacticallycorrect:fmustbeamonadcreatingfunctionontheleftsideofthe==butamonadicobjectontherightside.
Wecanfixthisbymakingfintoamonadicobject.Allittakesistoevaluatefonanargumentx.Itiseasytofindanappropriatexbecauseforanyf
f==\x->fx
Keepingthisinmindandrealizingthatghasnothingtodowiththatxwecanwriteanassociativityaxiomfor(>>=)thisway
(mon>>=f)>>=g==
mon>>=(\x->fx>>=g)
Makinguseofthefact(>>=)bindsfromtheleftandlettingmrepresentan
arbitrarymonadicobjectratherthananarbitrarymonad,thisaxiomisusuallywritten
m>>=f>>=g==
m>>=(\x->fx>>=g)
Usingthenormaldefinitionof(>>)itispossibletoprovefromthisaxiomthat,foranythreetypecompatiblemonadicobjects,mon1,mon2,andmon3
(mon1>>mon2)>>mon3==
mon1>>(mon2>>mon3)
WewilltakethissimilaritywithfunctioncompositionabitfurtherinSectionXII.
QuestionIsitpossibletoapplyassociativitytoremovethexfromthis?
main=
getLine>>=
(\x->
(getLine>>=
putStrLn>>
returnx)
>>=putStrLn
)
AnswerNo.Yourfirstthoughtmaybetogoforsomethinglikethismain=getLine>>=(getLine>>=putStrLn)>>=putStrLnWhichiseasiertotalkaboutifwewriteitthiswayf=getLine>>=putStrLn
main=getLine>>=f>>=putStrLnThisapproachdoesnotcarrythefirstgetLine'sinteriorobjectovertothelastputStrLn.Moreoveritissyntacticallyincorrect.Tomakethisworkfwouldhavetobesomethinglikef=\x->getLine>>=putStrLn>>returnxwhichofcoursedoesnotgetridofthex.
Thiskindofsituationiswherethedoblockshines.
VI.ThedoBlockHereisanexampleofthedoblock.
main=
do
line1<-getLine
putStrLnline1
line2<-getLine
putStrLnline2
Yourvisualexpectationsarecorrect:
therearestatementsbindingvaluestovariables,
theentireblockconsistsofstatementsthatareexecutedinthetop-downorderpresented,and
variablescopingmakessense:line1andline2arenotavailableoutsidethedoblockbutareavailabletoallstatementsfollowingtheirassignments.
Majordifferenceswithanormalimperativeprogramminglanguagearethatvariablesarebound(theycannotbereassigned)andloopslooksomewhatweird.MoreaboutloopsinSectionVII.
Concerningthescopingrules,inthefollowingthevariablexisnotbeingreassigned.Insteadtherearetwovariableswiththesamename.
main=
do
x<-getLine
dox<-getLine
putStrLnx
putStrLnx
Thatdoblockfollowsanalogousscopingrulestothoseyouareusedtofromsuchthingsas
{Stringx=getLine()
{Stringx=getLine()
putStrLn(x)
}
putStrLn(x)
}
Sincedoblockshaveanequivalentmonadicexpression,therestofthissectionisdevotedtogivingyousomeunderstandingofthatequivalence.
Forexample,thismonadicexpressionisequivalenttotherecentlyshowndoblock.
main=
getLine>>=
(\x->
(getLine>>=putStrLn)
>>putStrLnx
)
Ihavecreatedthreesubstitutionrulestogiveyouanintuitiveunderstandingofwhydoblocksarejustsyntacticsugarforunderlyingexpressionsinvolving(>>)and(>>=).Myrulesaresomewhatsloppy.Weneedintuitionheremorethanprecisemath.Inparticularweneedtounderstandthatwhatlookslikevariableassignmentisreallyjustparameterpassing.
Althoughsloppy,theserulescanworkforyoualmostallthetime.Makesurethatyouarenotmixinguptwodifferentmonadswithonesubstitutionandbecarefulnottoallowasymbolthatisusedintwodifferentwaystobecomeusedinjustoneway.Finally,cooperatewiththetypechecker.
Substitution1Sequencing:Withinadoblockandformonadicobjectsmon1andmon2theseareequivalent
mon1>>mon2
mon1
mon2
Substitution2Conversionbetweenparameterpassingandvariablebinding:Withinadoblockandforamonadicobjectmonandatypecompatiblefunctionfthefollowingareequivalent
mon>>=f
mon>>=(\x->fx)
x<-mon
fx
Substitution3Establishmentofdoblock:Amonadicobjectmonisoftenequivalenttodomon.
Nowlet'susethesubstitutionrulestoshowwhythesetwoareequivalent.
main=
do
x<-getLine
dox<-getLine
putStrLnx
putStrLnxandmain=
getLine>>=
(\x->
(getLine>>=putStrLn)
>>putStrLnx
)Webeginwiththefirstformandapplythesubstitutionrules
to
converttothesecond.
Startbyapplyingrule2toget
main=
do
x<-getLine
dogetLine>>=putStrLn
putStrLnx
Nowapplyrule3toget
main=
do
x<-getLine
getLine>>=putStrLn
putStrLnx
Nowapplyrule1toget
main=
do
x<-getLine
(getLine>>=putStrLn)
>>putStrLnx
Notingthat
(getLine>>=putStrLn)>>putStrLnx
isonemonadicexpression,rule2canbeappliedagaintoget
main=
do
getLine>>=
(\x->
(getLine>>=putStrLn)
>>putStrLnx
)
Finallyrule3getsuswherewewanttobe.
main=
getLine>>=
(\x->
(getLine>>=putStrLn)
>>putStrLnx
)
QuestionUsethesubstitutionrulestoconvertthethefollowingmonadicexpressiontodoblockform
main=
(getLine>>=putStrLn)>>
(getLine>>=putStrLn)
Hint:Thisissimplerthantheexample.
AnswerIntroduceadoblockwithrule3.
main=
do
(getLine>>=putStrLn)>>
(getLine>>=putStrLn)Thenuserule1.
main=
do
(getLine>>=putStrLn)
(getLine>>=putStrLn)Finallyapplyrule2twice.
main=
doline1<-getLine
putStrLnline1
line2<-getLine
putStrLnline2
Whichistheexamplewestartedthissectionwith.
VII.TheListMonadListswiththeirhead:tailpatternmatchingarenotintrinsicallymonadic.Haskellletsusmanipulateliststhewaywewouldexpecttodoinanyfunctionallanguage.ThinkoftheListmonadasaddingalittlesomethingtothenormallistprocessingfunctions.
ThereturnmethodintheListmonadisdefinedthisway
returnx=[x]
Theinteriorobjectsofalistarejusttheelementsofthelistandreturnxhasjustoneinteriorobject,xitself.
The(>>=)combinatorisdefinedthisway
(>>=)monf=
concat(mapfmon)
Whichmeansthat
mon>>=f
isthelistformedbyapplyingftoeachofmon'sinteriorvaluesandconcatenatingthoseoftheresultinglistswhicharenonempty.
Here'sasimpleexample
[1,2]>>=(\x->[x+1])
whichproducesthisListmonad(akalist)
[2,3]
Ifyouwantedtocompileandrunthiscode,youcouldwritethissourcefile
main=
putStrLn(show([1,2]>>=(\x->[x+1])))
Listmonadsexemplifythekindoffreedomwegetwhenatypeisbothmonadicandnonmonadic.Wecanmakelistswithboththe[]constructorandwithreturn.The[x+1]abovecouldhavebeenwrittenreturn(x+1).Doingthat,applyingsubstitutionrule3,andthinkingofreturn(x+1)asafunctionofxgives
do[1,2]>>=
(\x->return(x+1))
Thensubstitutionrule2leadsustoanequivalentdoblock
dox<-[1,2]
return(x+1)
Whatisinterestingisthatthedoblocklooksalotlikealoopwhosecontrolvariablexiteratesovertheinteriorelementsofthelist[1,2].Inmonadicterms
\x->return(x+1)
isevaluatedforeachoftheinteriorobjectsin[1,2].
CreatingloopsindoblockslikethismaybethemainvalueoftheListmonad.Suchdoblockstakelists(whichistosayListmonadicobjects)andturnthemintolists.Thisiswhatthemapfunctiondoesinmanyprogramminglanguages.Thedoblockhoweverismoreflexible.
Foramoreillustrativeexample,wewillwriteafunctionpairsthatformsthecrossproductoftwolists.Forexample,wewant
pairs[1,2]['a','b']
toevaluateto
[(1,'a'),(1,'b'),(2,'a'),(2,'b')]
Ofcourse,wearegoingtotakeadvantageoftheloop-likebehaviorof
dox<-lst
<loopbodyforxhere>
Andweneedaninnerloopaswell.
Thisdoubleloopwillproducethedesiredorderedpairs
pairslst1lst2=do
x<-lst1
do
y<-lst2
return(x,y)
Wecanusethesubstitutionrulestoproduceanicerversion.Thinkingofreturn(x,y)asafunctionofyandapplyingrule2gives
pairslst1lst2=do
x<-lst1
do
lst2>>=(\y->return(x,y))
Thenapplyingrule3gives
pairslst1lst2=do
x<-lst1
lst2>>=(\y->return(x,y))
Applyingrule2againwegetthisprettyversion
pairslst1lst2=
do
x<-lst1
y<-lst2
return(x,y)
Normally,youwouldnotapplythesubstitutionrulestogetthisfinalversion.Youwouldjustwriteit.Ihaveusedtherulesheretoemphasizethattheimperative-stylethinkingthatcomesnaturallywithdoblocksisactuallybasedonmonadicexpressions.It'sabitamazingisn'tit?
QuestionHowwillthisexecute?
pairsgetLinegetLine
Hint:Thegeneraltypesignatureofpairsis
pairs::
Monadm=>
ma->mb->m(a,b)
Itjusthappensthatinthewayyousawit,pairswasinstantiatedtothelistmonad.
AnswerSincetheargumentstopairsarebothoftypeIOString,weknowthatinthiscasethetypeofpairsinstantiatesto
pairs::
IOString->
IOString->
IO(String,String)
KnowingthattheIOmonad,nottheListmonad,isinvolvedwilltellusthatlst1andlst2havesingleinteriorobjects.Noloopingeffectwillbeapparent.
So,iftheinputlineswere“line1”and“line2”wewouldexpecttoget
("line1","line2")
Hereisaprogramyoucancompileandexecutetotestthis.InitIhaveextractedtheinteriorobjectfrompairsgetLinegetLine,convertedittoastringandprintedit.
pairs::Monadm=>mt->mu->m(t,u)
pairslst1lst2=do
x<-lst1
y<-lst2
return(x,y)
main=pairsgetLinegetLine>>=(\z->putStrLn(showz))This
program,ofcourse,silentlyasksfortwolinesofinput.Asa
furtherexercise,youmightwanttochangeitsothatitrequests
inputwitha"Input?"prefix.Thenexecutionwouldlooklike
Input?line1
Input?line2
("line1","line2")
VIII.IntroductiontoMonadTransformersHaskellcomesbundledwithlotsofmonads.Itcanbedifficulttogetthemtoplaytogether.Forexample,ifyoutrythis
main=
do
x<-[1,2]
y<-["a","b"]
putStrLn(show(x,y))
youwillgetatypeerrorbecausethefirsttwostatementsrequirethedoblocktobeworkingwithaListmonadwhereasthethirdstatementrequiresthedoblocktobeworkingwithanIOmonad.
Haskellincludes“transformer”versionsofmanymonadswhichcanbeusedtosolvesuchproblems.Atransformerversionofamonadmletsyoucreateaversionofmthatcanbeusedinconjunctionwithdifferentmonad.Atransformerisnotamonad,butaconstructorofmonads.
Yes,thisupsthelevelofabstraction.Monadsconstructtypes.Transformersconstructmonads.Butonceyouhaveacceptedthenotionofa“constructorofwhatchamacallits”,thenotionofa“constructorofwhatchamacallitconstructors”isn'tmuchofastretch.
Tobemoreexplicitatransformermtofamonadnisametatypewithtwotypeparametersmanda.Thefirstparametermmustbeamonad.Whenitissubstituted,mtmbecomesanothermonad.Whenbothparametersaresubstituted,mtmabecomesamonadicobject.Themonadicobjectwillactlikeoneofn'sobjectswithrespectto(>>=)andreturnbutcanbeusedlikethewrapperminwaysyettobespecified.
ThetransformerfortheListmonadhasametatypesomethinglikethis
typeListTma=ListT(m[a])
WhenmisgivenaspecificvalueinListma,yougetamonadwhichactslikea
Listmonadwithrespectto(>>=)andreturnbutwhoselistiswrappedinthemonadyougavetom.
ThedefinitionofListTisactuallywrittenwithanaccessorforthemonadicobjectoftypem[a].Thisisthewayitreallylooks
newtypeListTma=
ListT{runListT::m[a]}
(Herenewtypeisusedbecausetypemerelynamesanaliasforanothertypeandwecannotspecifyaccessorwhennaminganalias.)
SincetheonlyListTmIwillbewritingaboutisListTIO,itisconvenienttogiveitaname:
typeLio=ListTIO
SinceaLiomonadicobject's(>>=)andreturnactlikeaList,itsinteriorobjectscomefromthelistwhichtheIOmonadwraps.Putanotherway,iftheIO'sinteriorobjectis['one','two']thentheLio'sinteriorobjectsare'one'and'two'.
ActuallyusingtheIOmonadwithinLioisnotstraightforward.Thiswillnotwork
hello::Lio()
hello=do
putStrLn"Thisiswrong!"
TheproblemisthatputStrLnproducesanIO()andthisdoblockneedsaLio().Wesolvethatproblemby“lifting”theputStrLnactionintothecontainingLio.
Soifwewanttowrite“HelloWorld”fromwithinLio,hereisthewaytodoit.
hello::Lio()
hello=do
lift$
putStrLn"HelloWorld"
Sofarsogood,butliftingdoesnotsolvetheentireproblem.NoticethathellocannotgoatthetoplevelofaprogrambecauseitisnotanIOmonad.AwayofextractingLio'sIOmonadisneeded.
ExtractinganIOfromaLioiswhattherunListTaccessorisfor.Hereisaprogramyoucanrun
importControl.Monad.List
typeLio=ListTIO
hello::Lio()
hello=do
lift$putStrLn"HelloWorld"
main=runListThello
Ifcompiled,theoutputofthismainwillbe
HelloWorld
ThismonographisnotabouttheGHCIinterpreterbut,forthoseofyouwhoareworkingwithGHCI,hereisatranslationofthisoneprogramtoaformtheinterpreterwouldliketosee
>importControl.Monad.List
>typeLio=ListTIO
>lethello=dolift$putStrLn"HelloWorld"
>runListThelloFromthisyouwillgetHelloWorld
[()]
whichshowsboththedesiredoutputandtheinteriorobjectofthemonadthatiscreated.Theoutput,ofcourse,isasideeffecdtthattheIOmonadistryingtohide.The[()]isaninteriorobjectoftheIOmonad.You'llseeonereasonwhythatisimportantinthissection'sexercise.
WeirdlyGHCIthrowsthatinteriorobjectawayifitis()andshowsittoyouotherwise.
Bytheway:
Mostofthesourcecodeyouputintoa.hsfilecanbeexecutedinGHCI.Foranexamplesupposehello.hscontainedimportControl.Monad.List
typeLio=ListTIO
hello=dolift$putStrLn"HelloWorld"
main=runListThelloIhavepurposelyleftthetypesignatureforhellooffbecauseIwantGHCItotellmewhatitis.
HereisaGHCIsessioninwhichIcheckthetypeofhello
Prelude>:lhello.hs
[1or1]CompilingMain
Ok,modulesloaded:Main.
*Main>main
HelloWorld
[()]
*Main>:thello
hello::ListTIO()
Nowlet'sexperimentabit.Ifwerunthis
importControl.Monad.List
typeLio=ListTIO
test::Lio()
test=do
x<-return[1,2]
y<-ListT$return['a','b']
lift$putStrLn(show(x,y))
main=runListTtest
weget
([1,2],'a')
([1,2],'b')
Doesitseemstrangethatyiteratesthroughthelist['a','b']butxdoesnotiteratethroughthelist[1,2]?
Thereasonisthis:thereturnusedinthexassignmentexistswithinadowhichmustproduceanLio().ItactslikeList'sreturn.Thereforeitsinteriorobjectisthesingleelementofthelist[[1,2]].
ThereturnintheassignmenttoyisdifferentinthatitmustproducesomethingfortheListTconstructor,thatistosayitmustproduceanIOa.ThereforeitactslikeanIOreturnandsoitproducesamonadicobjectwhoseinteriorobjectis['a','b'].TheListTconstructorthenbuildstheLiomonadicobjectanditsinteriorobjectsare'a'and'b'.
Thereforexiscomingfromalistwhosesingleelementis[1,2]andyiscomingfromalistwhoseelementsare'a'an'b'.
Whew!YoureallydohavetopayattentiontoHaskell'stypesystemwhenfiguringoutwhatreturndoes.
QuestionRecallingtherecenttestexample,whathappensifyoudothis?
runListT(test>>test)Explain.
Hint:Thisoneistricky.Youwoulddowelltoreviewthepairs
example
andtheexerciseoftheprevioussection.Afterthat,thinkabout
what
theinteriorobjectoftheIOwrapperintestis.Evenif
youstillhavetoreadtheanswer,youwillbebetterpreparedto
understanditifyoudothisreviewandmakethisattempt.
Hereisagoodplacetoslowdownandmakesureyoureallygrokwhat's
goingon.Ifyoudon't,thingswillonlygetmoreconfusingasyou
goforward.
AnswerTheansweris([1,2],'a')([1,2],'b')([1,2],'a')([1,2],'b')([1,2],'a')([1,2],'b')Tounderstand,noticethatrunListTtestdoesnotonlyinvokeputStrLntwicebutalsoreturnsthislist[(),()].(IfyouareintoGHCI,youcancheckthisbyloadingtest.hssourcecodeandevaluatingmain.)Hencetesthastwointeriorobjects,both().Armedwiththisinformationyouknowthatthefirsttestwilloutput([1,2],'a')([1,2],'b')andproducetwointeriorobjects.The(>>)combinatorwillignoretheinteriorobjectsbutwillbeevaluatethesecondtesttwiceanyway.Eachofthoseevaluationswillhavethesideeffectofprintingtheabovetwolines.
IX.IntrototheStateMonadTheStatemonadisreminiscentoftheIOmonad.TheIOmonadhidesworldremakingfunctionswhereastheStatemonadwrapsstateremakingfunctions.Buthidingismorecompletethanwrapping.TheIOmonadhidesdetailsfromtheprogrammer.TheStatemonadallowstheprogrammertowrapdetailsinanabstraction.
Inbothmonadsthis
mon1>>mon2
worksbyevaluatingmon1andpassingtheremadeworldtomon2.
BothanIOaandaStateawrapafunction.Thefunctioncreatesanewworldorstateandaninteriorobjectoftypea.Haskellletsusdescribesuchathingwith
s->(a,s)
wheresisthestateandaistheinteriorobject.WiththisinmindwecandescribeaStatemonadthisway
newtypeStatesa=
State{runState::s->(a,s)}
TheStatemonadwrapsastateremakingfunctionandgivesitaname,runState.
Note:
Therearetwoparameters.Likeamonadtransformer,theso-calledStatemonadisnotamonadbutratheraconstructorofmonads.Weneedtoprovideanargumentforthestateparameter,asinStatesbeforewehaveanactualmonad.However,thiswon'tpreventusfromsaying“Statemonad”.
Evaluationof
mon>>=f
createsanewStatemonadwithanewstateremakingfunction.Thatfunctioncanlaterbeappliedtosomeinitialstate.Thisismuchlikeevaluating(>>=)intheIOmonadwhereanewworldremakingfunctioniscreatedthatisthenevaluatedby
theruntimesystemwhentheprogramisexecuted.
Hereisthedefinitionof(>>=)fortheStatemonad
(>>=)mf=
State$
\s->
leth=runStatem
(x,newSt)=hs
g=runState(fx)
in
gnewSt
Inwordshereishow(>>=)isevaluated
1. Haskellevaluatesm'sstateremakingfunctionrunStateonsomestatecreatinganewstatenewStandinteriorvaluex.
2. HaskellevaluatesfonxproducinganewStatemonadicobjectfx.Thismonadicobjectcontainsastateremakingfunctionthatwillbeimportantinthenextstep.
3. FinallyHaskellevaluatesthestateremakingfunctioncreatedinstep2onthestatecreatedinstep1.Thiscreatesanewstateandinteriorvaluewhicharereturnedasapair.
Asequenceof(>>=)operationslikethis
moninitial_state>>=
f1>>=f2…>>=fn
involvesrepeatedrecreationofa
(interiorobject,state)
pairbasedonthepreviousstateandinteriorobject.
ItwillbehelpfultolookbackatSectionIIItocontrastitwiththeway(>>=)worksintheIOmonad.ThedifferenceliesmostlyinthefactwemustdefinethedetailsofhowourStatemonadsworkwhereaswehavenoaccesstotheimplementationdetailsoftheIOmonad.
TheIOmonadhasnoanalogsfortherunStatefunctionandtheStateconstructor.IntheStatemonadtheyareinversesofeachother.WiththemyoucanmakeallkindsofextrafunctionsofakindthatwouldbeimpossiblewiththeIOmonad.YoucannotplaywiththerealworldthatisencapsulatedbytheIOmonad.
Thatsaid,itistimetoadmitthattheStatemonadisnolongerdirectlydefinedintheHaskellAPI.Haskell'sStatemonadiscurrentlydefinedintermsofitsStateTtransformer.Thedetailsdon'treallymattertotheHaskellprogrammerandIleaveadiscussionofStateTuntilSectionXII.ForpurposesofusingtheStatemonadallweneedtoknowisthatthereisnoStateconstructor.IthasbeenreplacedwithastatefunctionwhichdoestheconstructionusingaStateTmonad.
WecanviewstateasareplacementforState.Howeverthissimplesubstitutiondoesnotapplytopatternmatching.WhentherewasanormalStateconstructor,itwaspossibletowrite
Stater=stm
toextractastateremakingfunctionrfromaStatemonadicobjectstm.SincethereisnoStateconstructor,thatisnolongerpossible.Write
r=runStatestm
instead.
NeverthelessIwillspeakoftheStatemonadandusethestatefunctionasitsconstructor.
QuestionCreateasimpleStatemonadicobjectstate_obj::State[Char][Char]
whoserunStatefunctionisaconstantfunctionthatmapsanythingtothepair("one","two").
Thendefineafunctionshow_result::Showa=>State[Char]a->IO()whichwrites
First:"one"
Second:"two"
byextractingtheinteriorobjectandstatefromitsargument.YourcompleteanswerwillappearinthistemplateimportControl.Monad.State…main=show_resultstate_objNotethatshow_resultwillhavetorunstate_obj'sstateremakingfunction.Itdoesn'tmatterwhatargumentyougiveitbecauseyouaregoingtothrowtheargumentaway.Iused“anything”.
AnswerimportControl.Monad.State
state_obj::State[Char][Char]state_obj=state(\s->("one","two"))
show_result::Showa=>State[Char]a->IO()show_resultso=(putStrLn("First:"++(show(fstran))))>>(putStrLn("Second:"++(show(sndran))))whereran=runStateso"anything"
main=show_resultstate_objNotetheshow_resultfunctioncouldhavebeendeclaredshow_result::State[Char][Char]->IO()andwrittenwithout(show…).Ididnotdoitthatwaybecauselaterwewillneedshow_resulttobeflexibleenoughtoworkwhentheinteriorobjectis().
X.MoreAbouttheStateMonadLikeothermonads,theStatemonadhasareturnfunction.Itisdefinedthiswayreturn::a->Statesareturnx=state$\st->(x,st)TheaxiomsinSectionXIIprettymuchinsistthatxbetheinteriorobjectofreturnx.Let'stesttoseeifthatistrueoftheStatemonad.
Interiorobjectsarethosemadeavailablethrough>>=.Inawayyoucouldthinkofthemas“exterior”objects.Amonad'swayofexposingsomethingtotheoutsideworldis>>=andtheso-calledinteriorobjectsarewhatitexposes.
Toseewhat>>=provides,allweneedistoapplyshow_resultfromthepreviousexercisetoreturn"interior"asinmain=show_result(return"interior")ThismainwillproduceFirst:"interior"Second:"anything"
Whichshowsthattheinteriorobjectofreturn"interior"is“interior”andthestatehasremainedwhatshow_resultpassedtorunState.
WhatmakestheStatemonadinterestingisnotsomuchitsinteriorobjectbutratheritsabilitytomanipulateastateinthebackground.Theexerciseinthissectionwillinvolvedoingthatdirectlybuttherearelotsoftimes,especiallywhenusingtheStateTtransformer,youwillwanthelperfunctionsforstatemanipulation.ItishelpfultolookintothatnowbeforewegetintothecomplicationsoftheStateTmonad.
Hereisafunctionforgettingthestate.
get::Statess
get=state$\st->(st,st)Thisfunctionsimplycopiesthestateit
isgivenintotheinterior
objectposition.Thatmakesitavailablethrough>>=.
Sincegetisamonadicobjectandnotafunction,wewritem>>getnot
m>>=getBecausegetworkswithastatecomingfromrunStatem,
itmayseemstrangetouse>>.Tounderstand,let'slookat
howthestandarddefinitionof>>(>>)m1m2==m1>>=(\_->m2)mesheswiththedefinitionof>>=fortheStatemonad.
m1>>m2
==m1>>=(\_->m2)
==state$
\s->
let
(iObj,newSt)=(runStatem1)s
g=runState((\_->m2)iObj)
in
gnewSt
==state$
\s->
let
newSt=snd((runStatem1)s)
g=runStatem2
in
gnewSt
=state$
\s->
(runStatem2)(snd(runStatem1s))
Substitutingmform1andgetform2causesm>>gettoreduceto
state$
\s->(\st->(st,st))(snd(runStatems))
Inshort
m>>get
is
state$
\s->(snd(runStatems),snd(runStatems))
whichtellsusthe>>operatorworksonthestateevenasitignorestheinteriorobject.
Hereistheputfunctionforreplacingthestate.
put::s->States()
putnewState=state$\s->((),newState)
Wecantestgetandputwiththisprogram
importData.Functor.Identity
importControl.Monad.State
--show_resultandstate_objfrom
--answertoquestioninprevious
--section
main=(show_result(state_obj>>get))>>
(show_result(return"interior">>=put))
Theoutputforis
First:"two"
Second:"two"
First:()
Second:"interior"
andyoucanseethatgethasalteredtheinteriorobjectandputhasalteredboththestateandtheinteriorobject:
Youwillfindgetandputusefulbutsometimesitiseasiertowritefunctionsthataccessthestatedirectly.Thatiswhatyouwilldointhissection'sexercise.InthisexerciseweusetheStatemonadtoimplementastack.Thepushandpopfunctionscouldbewrittenwithgetandputbutitissimplerwithout.
Implementingastackinfunctionallanguagesistrivialbecauseitiseasytoaddto,orremovefrom,thefrontofalist.Sowhybotherwithamonad?Onereasonmightbetopermityourpushingandpoppingtohappeninadoblock.
InthisexercisewesetupastackthatisimplementedwithalistbutwhosepushandpopwillworkinaStatemonad'sdoblock.TheenablingfunctioninthisexampleisprocessAsStackwhichtakesalistandaStackmonadandthenusestheStackmonadtoprocessthelist.Thetypesignatureis
processAsStack::
[a]->Stacka->[a]
TheStackargumentofcoursecanbeadoblock.
HereisatypedefinitionforStack.
typeStacka=State[a]
andhereisadefinitionofprocessAsStack
processAsStack::[a]->Stacka()->[a]
processAsStacklstprocess=
snd(runStateprocesslst)
QuestionFinishthisprogramwhichcreatesastackandthenusesadoblocktoalterthelistofchars“ct”sothatitis“cat”.
Youwillneedtoimplementthepushandpopfunctions.
importData.Functor.Identity
importControl.Monad.State
typeStacka=State[a]
processAsStack::
[a]->Stacka()->[a]
processAsStacklstprocess=
snd(runStateprocesslst)
pop::Stackaa
pop=state$\(x:xs)->(x,xs)
--removesxfromthelist
push::a->Stacka()
--addstheargumenttothelist
printListlst=putStrLn(showlst)
main=
printList$
processAsStack
"ct"
$do
x<-pop
push'a'
pushx
DoyouunderstandhowHaskellknowswhatkindofthingpopis?ThedoblockispassedasthesecondargumenttoprocessAsStacksoHaskellknowsthedoblockisaStackamonadicobject.ThedoblockthereforewillhavearunStatefunctionthatmapsalistofastothekindofthingwrappedinaStackawhichisalistofas(becauseaStackaisaState[a]whichcontainsalistofas).
Sinceinitsunsugaredform,thedoblockbeginswith
pop>>=…
popitselfmustbeafunctionthattakesalistandproducesalist.
Answer
importData.Functor.Identity
importControl.Monad.State
typeStacka=State[a]
processAsStack::
[a]->Stacka()->[a]
processAsStacklstprocess=
snd(runStateprocesslst)
pop::Stackaa
pop=state$\(x:xs)->(x,xs)
push::a->Stacka()
pushx=state$\lst->((),x:lst)
printListlst=putStrLn(showlst)
main=
printList$
processAsStack
"ct"
$do
x<-pop
push'a'
pushx
XI.AStateTExample.TheStateTtransformerissimilartotheListTtransformer.Asyoumightexpect,theimportantdifferenceisthatStateTcreatesmonadsthatactlikeaStatemonadinsteadoflikeaListmonad.
WithListTmthemwrapsanentirelistbutwithStateTmthemwrapsonlythenewstate.Here'sthemetatypedefinition
newtypeStateTsma=
StateT{runStateT::s->m(a,s)}
AsexpectedofaStateTtransformerthefunctions(>>=),return,get,andputarewrittentoworklikeaStatemonad.InfacttheimplementationofStateusestheStateTmonadratherthanstartfromscratch.
Alsoasexpected,theliftfunctionpermitsfunctionsbelongingtothewrappermonadmtoworkwithinaStateTsmmonad.
Forthelastexampleinthismonographlet'stryforsomethingratherpractical.Towit,let'swriteafunctionmake_changewhichdistributeschangeforagivenamountusingcoinsofspecifieddenominations.
Ausefuldatatypeforuswillbe
typeCoins=[Int]
whereaCoinslistshowsthedenominationsofallthecoinswhichmightbeusefultous.Thealgorithmwewilladoptrequiresthatthesedenominationsbelistedbydecreasingfacevalueswith1attheveryend.Inthissimplifiedexample,weassumeaplentifulsupplyofcoinsofalldenominations.
Weassumethatwehavealreadycreatedafunctiondispensethatwillcausesomecoinmachinetodispenseacoinofaspecifieddenomination.ImplementingdispensewouldprobablyinvolvecallingaClanguagefunctionandiswaybeyondthescopeofthismonograph.Insteadwewillfakeitbyprintingoutamessageforeachcointhatisdispensed.
Beforewejumpintocodeweoughttothinkabitaboutthealgorithmwewishto
code.Itwillchooseonecoinatatimetodistributeanddecreasetheamountlefttodistributeaccordingly.ItwillbeconvenienttopullthecoinoflargestdenominationoftheCoinslistwhenitisnolongeruseful.
Sowewillbeworkingwithapairconsistingoftheamountlefttodispenseandalistofcoinsthatmightbeusefulindoingthedispensing.Sincethatlistwillendwithacoinofdenomination1,itwillalwaysbepossibletodothedispensing.Thegutsofouralgorithmwillbeafunctionnext_coinwhichdoessomethinglikethis
(amt,coins)->(amt,coins)
Eachstepthroughthesequenceeither
dispensesacoinanddecreasesamtaccordingly,or
removesthelargestcoinfromcoins.
wherethefirstalternativetakesprecedencewheneveramtisnotsmallerthanthelargestcoin.
Hereisanexampleofhowthesequencerepresentscoindispensing.Startwith(11,[10,1])whichmeanswemustmakechangefor11centswithadimeandapenny.Thefirststepisto(1,[10,1])representingthatadimehasbeendispensed.Thesecondstepisto(1,[1])representingthatwehaverecognizeddimesarenolongeruseful.Thelaststepisto(0,[1])representingthatapennyhasbeendispensed.
Now,howtoputthisalgorithmintoHaskell?Somethinglikenext_coin>>=next_coin>>=....next_coinseemsliketheformwewillwant.Let'sconsiderhowthisrepetitionofnext_coinswillendandwhetherthechoiceofhavingthestatebethecoinsratherthantheamountwascorrect.
Withinthenext_coinfunction,wemustmakeuseofboththeamountandthecoins.Thereitdoesn'tseemtomatterwhichistheinteriorobjectandwhichisthestate.Theinteriorobjectisavailableasaparameterandthestateisavailablethroughgetandput.
Outsideofnext_coin,itisadifferentstory.Thereallweneedtoworkwithistheamount.Weneedittoknowwhentostoptherepetitionofnext_coin.Thereisnoneedtoknowanythingaboutthecoinswhenimplementingourimaginedsequenceofnext_coinsabove.Bymakingtheamountlefttodistributebenext_coin'sinteriorobject,wecanplanafunctionresponsibleforrepeatingnext_coinswhichwillhaveaccesstotheamountlefttodistributethrough>>=.
Remark:
Ifyoudofuzzyreasoninglikethisandgetitwrong.Youcanalwaysbackupandtryagain.
Nowwecannaildownsomeofthetypedefinitions:SincedispenserequirestheIOmonad,wewilldefineourstatemachinetypeusingIOasthewrappermonadinStateT.Thedefinitionis
typeStMacha=StateTCoinsIOaRemark:
WhynotStateTCoinsIOInt?Forthesamereason
show_resultwasdefinedwitharatherthan[Char],
namelytheinteriorobjectwillneedtobe()atsomepoint.
Continuingwithourtypedefintions:substitutinginthe
StateTdefinitionweseethattherunStateTfunctionisrunStateT::Coins-
>IO(a,Coins)Moreoverwenowhavetypesfornext_coinanddispense
next_coin::Int->StMachInt
dispense::Int->StMach()
Sincenext_coinistoberepeateduntilitisgivenanamountof0,wecanwriterecurs::Int->StMachIntrecursamt=ifamt==0thenreturnamtelsenext_coinamt>>=recursSorecurscanbeusedthesameplacesnext_coincan.Onlyitwillrepeatnext_cointherightnumberoftimes.
Althoughwewon'tbeimplementingthedispensefunction,wewillneedastubtorunourprogram.ThebasicideaistodothisputStrLn("dispense"++(showi)++"cents")butthatwillnotworkinthecontextofaStMachmonad.WeneedtolifttothewrappingIOmonadthisway:
dispense::Int->StMach()
dispensei=
(lift$
putStrLn
"("dispense"++(showi)++cent)
)
where
cents=ifi==1
then"cent"
else"cents"
Thegutsofourcoinchangerwillbeamake_changefunctionwhichtakesalistofcoinsandanamounttobemadeintochange.
make_change::Coins->Int->IO()
Thismake_changefunctionwillworkwiththemonadicobjectrecursamtwhichmustbegiventheamountwewantdistributedandwhoserunStateTfunctionisreadytodothedistributingprovideditispassedthestartinglistofcoinswehaveavailable.Itisalmosteasytodefinemake_change
make_changecoinsamt=--notquiteright
runStateT(recursamt)coins
exceptthatthemonadicobjectproducedthiswayisanIOIntandnotanIO()asrequired.Asmalltweakfixesthat
make_changecssamt=
runStateT(recursamt)css>>return()
Allthatisleftistowritemainandnext_coin.Becausenext_coinworkswithbothamountandcoinstheimplementationwillhavetoworkwithStateT'sgetandput.RecalltheyaredefinedtoworkasifStateTwereaStatemonad.
Question
Finishthecoinchangingprogrambyfillinginthedetailsfornext_coinandmain.
importData.Functor.Identity
importControl.Monad.Trans.Class
importControl.Monad.State
typeCoins=[Int]
typeStMacha=StateTCoinsIOa
next_coin::Int->StMachInt
next_coinamt=
--finishthis
recurs::Int->StMachInt
recursamt=
ifamt==0
thenreturnamt
elsenext_coinamt>>=recurs
dispense::Int->StMach()
dispensei=
(lift$
putStrLn
("dispense"++(showi)++cents)
)
where
cents=ifi==1then"cent"else"cents"
make_change::Coins->Int->IO()
make_changecssamt=
runStateT
(recursamt)css>>return()
main=
doputStrLn"Enteranamount:"
--finishthis
Youwillneedafunctionwhichconverts[Char]stoInts.Itiscalledread.Haskellwillknowwhichreadtousebecauseyouwillputitinacontextwhereitgetsa[Char]andmustproduceanInt.
Ifyouhavetroublewiththedoblockinmain,rememberitmustbeanIOdoblock,notaStateTIOdoblock.
AnswerimportData.Functor.Identity
importControl.Monad.Trans.Class
importControl.Monad.State
typeCoins=[Int]
typeStMacha=StateTCoinsIOa
next_coin::Int->StMachInt
next_coinamt=
doc:cs<-get
ifamt>=c
then
dispensec>>
(return(amt-c))
else
putcs>>
(returnamt)
recurs::Int->StMachInt
recursamt=
ifamt==0
thenreturnamt
elsenext_coinamt>>=recurs
dispense::Int->StMach()
dispensei=
(lift$
putStrLn
("dispense"++(showi)++cents)
)
where
cents=ifi==1then"cent"else"cents"
make_change::Coins->Int->IO()
make_changecssamt=
runStateT
(recursamt)css>>return()
main=
doputStrLn"Enteranamount:"
line<-getLine
make_change
[25,10,5,1]
(readline)
XII.MonadsSofarIhaveleftoutoneoffunctionsamonadisrequiredtohaveandavoidedexplainingtwoofthethreeaxiomsthatanyimplementationshouldsatisfy.
Themissingfunctionis
fail::Monadm=>String->ma
Thisfunctionexistsindependentoftheaxiomsandisforerrorhandling.Haskellhasotherwaysofhandlingerrorsandthefailfunctionseemstobeusedverylittle.
Thetwoadditionalaxiomsensurethatreturnactssomethinglikeanalgebraicidentitywithrespecttothe(>>=)combinator.Recallthatforadditiontheidentityis0,formultiplicationitis1,andforcompositionoffunctionstheidentityis
id::a->a
idx=x
Thepatternlooksthesameforallthreeoftheseexamples.
x+0==0+x==x
x1==1x==x
f.id==id.f==f
Formonadsthepatternisabitmorecomplexbecauseunlikenormalalgebraicoperators(>>=)doesnottakethesamekindofthingontheleftandtheright.Ontheleftamonadicobjectisneededandontherightamonadmakingfunctionisneeded.Evensotherightidentityaxiomlooksprettynormal:
mon>>=return==mon
Togetreturnontheleftofthe>>=combinatorwemustmakeitintoamonadicobjectbyapplyingittosomeobjectx.Theleftidentityaxiomlookslikethis
returnx>>=f==fx
whichprettymuchsaysthatanyfwillbehavethesameifitsargumentispassedthroughreturnasitwillifitsargumentisuseddirectly.Thisisnottheusualkindofequalityweseeforidentityaxiomsbutthemeaningiseffectivelythesame.
TogetherwiththeassociativeaxiomofSectionV,therightandleftidentityaxiomsmakeuptheruleseveryimplementationofamonadmustfollow.
DefinitionofMonad
Hereisacompletedefinitionofamonadinoneplace.A“monad”misaHaskellmetatyperequiringoneargumenttobecomeanactualtype.Fourfunctionsmustbeassociatedwithm:
return::a->ma
(>>=)::ma->(a->mb)->mb
(>>)::ma->mb->mb
fail::String->ma
The(>>=)andreturnfunctionsmustsatisfythefollowingaxioms:
mon>>=f>>=g==
mon>>=(\x->fx>>=g)
returnx>>=f==fx
mon>>=return==mon
wheremon,f,andgareanyentitieswiththesetypes:
mon::ma
f::a->ma
g::a->ma
(Ascustomary,aandbrepresentarbitraryHaskelltypes.)
Inaddition(>>)isusuallydefinedwith
(>>)mon1mon2==
mon1>>=(\_->mon2)
andfailissometimesimportantforerrorreporting.
QuestionForgettingtheactualdefinitionofreturnfortheListmonad,usetheidentityaxiomstoshowthat
returnx
musthavexasitssoleinteriorobject.Inotherwords,giventhatwewanttheelementsofalisttobetheinteriorobjects,nootherdefinitionofreturnwillsatisfytheaxioms.
Hint:Myanswerinvolvestwoproofsbycontradiction.Oneshowsthatreturnxmusthaveaninteriorobjectandtheothershowsitcannothaveaninteriorobjectotherthanx.
AnswerWeshowreturnxmusthavesomeinteriorobjectbyassumingotherwiseandfindingamonforwhichthisaxiom
mon=mon>>=return
iscontradicted.
Letxbeanarbitraryvalue,thenamonadicobjectthatworksforusis
mon=[x]
Ourassumptionthatreturnxhasnointeriorobjecttranslatesintoassertingthatreturnxisanemptylist.Therefore
mon>>=return==
concat(mapreturn[x])==
concat[[]]==
[]
whichcannotbebecausemonis[x].Thusweknowthatreturncannotreturnanemptylistforanyxbecauseotherwisetheaxiomwouldbeviolated.
Weshowthatreturnxcannothaveaninteriorobjectotherthanxbyassumingthereisanotherinteriorobjectyandshowingthatthisaxiom
fx=returnx>>=f
iscontradictedforsomef.
Letxbeanarbitraryvalue.Afunctionthatworksforusis
fx=[x]
Thenthefirststepofevaluating
returnx>>=f
istoapplyftotheelementsofreturnxwithmap.Thiswillproducealistcontaininga[y].Applyingconcattothislistwillproduceanotherlist,againcontainingy.Howevertheaxiomsaysthissecondlistmustbeequaltofxwhichcontainsnoy.Thiscontradictionshowsthatforanyxthereisanfforwhichtheexistenceofanothervalueyamongreturnx'sinteriorvalueswouldleadtoacontradictionofamonadicaxiom.
AbouttheAuthorJAdrianZimmer
ZimmerisaPh.D.mathematicianwhoretiredknowingmoreaboutcomputersciencethanaboutmathematics.Hehastaughtatalllevelsfromhighschoolthoughgraduateschool.Hisexpertiseliesmoreinexplainingthingsthaninanyonespecialty.
Hishobbiesincludebiking,kayaking,andwatchingthekindofmoviesthatmakeonethinkaboutthevariouswaystobehuman.
(Seealsojazimmer.net.)