moduleSolutions.IOimportData.List1importData.StringimportSystem.File%defaulttotal---------------------------------------------------------------------------------- Pure Side Effects?---------------------------------------------------------------------------------- 1rep:(String->String)->IO()repf=dos<-getLineputStrLn(fs)-- 2coveringrepl:(String->String)->IO()replf=do_<-repfreplf-- 3coveringreplTill:(String->EitherStringString)->IO()replTillf=dos<-getLinecasefsofLeftmsg=>putStrLnmsgRightmsg=>do_<-putStrLnmsgreplTillf-- 4dataError:TypewhereNotAnInteger:(value:String)->ErrorUnknownOperator:(value:String)->ErrorParseError:(input:String)->ErrordispError:Error->StringdispError(NotAnIntegerv)="Not an integer: "++v++"."dispError(UnknownOperatorv)="Unknown operator: "++v++"."dispError(ParseErrorv)="Invalid expression: "++v++"."readInteger:String->EitherErrorIntegerreadIntegers=maybe(Left $ NotAnIntegers)Right $ parseIntegersreadOperator:String->EitherError(Integer->Integer->Integer)readOperator"+"=Right(+)readOperator"*"=Right(*)readOperators=Left(UnknownOperators)eval:String->EitherErrorIntegerevals=let[x,y,z]:=forget $ splitisSpaces|_=>Left(ParseErrors)Rightv1:=readIntegerx|Lefte=>LefteRightop:=readOperatory|Lefte=>LefteRightv2:=readIntegerz|Lefte=>LefteinRight $ opv1v2coveringexprProg:IO()exprProg=replTillprogwhereprog:String->EitherStringStringprog"done"=Left"Goodbye!"progs=Right.eitherdispErrorshow $ evals-- 5coveringreplWith:(state:s)->(next:s->String->Eitherress)->(dispState:s->String)->(dispResult:res->s->String)->IO()replWithstatenextdispStatedispResult=do_<-putStrLn(dispStatestate)input<-getLinecasenextstateinputofLeftresult=>putStrLn(dispResultresultstate)Rightstate'=>replWithstate'nextdispStatedispResult-- 6dataAbort:TypewhereNoNat:(input:String)->AbortDone:AbortprintSum:Nat->StringprintSumn="Current sum: "++shown++"\nPlease enter a natural number:"printRes:Abort->Nat->StringprintRes(NoNatinput)_="Not a natural number: "++input++". Aborting..."printResDonek="Final sum: "++showk++"\nHave a nice day."readInput:Nat->String->EitherAbortNatreadInput_"done"=LeftDonereadInputns=caseparseInteger{a=Integer}sofNothing=>Left $ NoNatsJustv=>ifv>=0thenRight(castv+n)elseLeft(NoNats)coveringsumProg:IO()sumProg=replWith0readInputprintSumprintRes---------------------------------------------------------------------------------- Do Blocks, Desugared---------------------------------------------------------------------------------- 1ex1a:IOStringex1a=dos1<-getLines2<-getLines3<-getLinepure $ s1++reverses2++s3ex1aBind:IOStringex1aBind=getLine>>=(\s1=>getLine>>=(\s2=>getLine>>=(\s3=>pure $ s1++reverses2++s3)))ex1aBang:IOStringex1aBang=pure $ !getLine++reverse!getLine++!getLineex1b:MaybeIntegerex1b=don1<-parseInteger"12"n2<-parseInteger"300"Just $ n1+n2*100ex1bBind:MaybeIntegerex1bBind=parseInteger"12">>=(\n1=>parseInteger"300">>=(\n2=>Just $ n1+n2*100))ex1bBang:MaybeIntegerex1bBang=Just $ !(parseInteger"12")+!(parseInteger"300")*100-- 2dataList01:(nonEmpty:Bool)->Type->TypewhereNil:List01Falsea(::):a->List01Falsea->List01neahead:List01Truea->ahead(x::_)=xweaken:List01nea->List01Falseaweaken[]=[]weaken(h::t)=h::tmap01:(a->b)->List01nea->List01nebmap01_[]=[]map01f(x::y)=fx::map01fytail:List01Truea->List01Falseatail(_::t)=weakent(++):List01ne1a->List01ne2a->List01(ne1||ne2)a(++)[][]=[](++)[](h::t)=h::t(++)(h::t)xs=h::weaken(t++xs)concat':List01ne1(List01ne2a)->List01Falseaconcat'[]=[]concat'(x::y)=weaken(x++concat'y)concat:{ne1,ne2:_}->List01ne1(List01ne2a)->List01(ne1&&ne2)aconcat{ne1=True}{ne2=True}(x::y)=x++concat'yconcat{ne1=True}{ne2=False}x=concat'xconcat{ne1=False}{ne2=_}x=concat'xnamespaceList01export(>>=):{ne1,ne2:_}->List01ne1a->(a->List01ne2b)->List01(ne1&&ne2)bas>>=f=concat(map01fas)---------------------------------------------------------------------------------- Working with Files---------------------------------------------------------------------------------- 1namespaceIOErrexportpure:a->IO(Eitherea)pure=pure.Rightexportfail:e->IO(Eitherea)fail=pure.Leftexportlift:IOa->IO(Eitherea)lift=mapRightexportcatch:IO(Eithere1a)->(e1->IO(Eithere2a))->IO(Eithere2a)catchiof=doLefterr<-io|Rightv=>purevferrexport(>>=):IO(Eitherea)->(a->IO(Eithereb))->IO(Eithereb)io>>=f= Prelude.do
Rightv<-io|Lefterr=>failerrfvexport(>>):IO(Eithere())->Lazy(IO(Eitherea))->IO(Eitherea)iou>>ioa= Prelude.do
Right_<-iou|Lefterr=>failerrioacoveringcountEmpty'':(path:String)->IO(EitherFileErrorNat)countEmpty''path=withFilepathReadpure(go0)wherecoveringgo:Nat->File->IO(EitherFileErrorNat)gokfile=doFalse<-lift(fEOFfile)|True=>purek"\n"<-fGetLinefile|_=>gokfilego(k+1)file-- 2coveringcountWords:(path:String)->IO(EitherFileErrorNat)countWordspath=withFilepathReadpure(go0)wherecoveringgo:Nat->File->IO(EitherFileErrorNat)gokfile=doFalse<-lift(fEOFfile)|True=>pureks<-fGetLinefilego(k+length(wordss))file-- 3coveringwithLines:(path:String)->(accum:s->String->s)->(initialState:s)->IO(EitherFileErrors)withLinespathaccumini=withFilepathReadpure(goini)wherecoveringgo:s->File->IO(EitherFileErrors)gostfile=doFalse<-lift(fEOFfile)|True=>purestline<-fGetLinefilego(accumstline)filecoveringcountEmpty3:(path:String)->IO(EitherFileErrorNat)countEmpty3path=withLinespathacc0whereacc:Nat->String->Natacck"\n"=k+1acck_=kcoveringcountWords2:(path:String)->IO(EitherFileErrorNat)countWords2path=withLinespath(\n,s=>n+length(wordss))0-- 4coveringfoldLines:Monoids=>(path:String)->(f:String->s)->IO(EitherFileErrors)foldLinespathf=withLinespath(\vs=>(vs<+>).f)neutral-- 5-- Instead of returning a triple of natural numbers,-- it is better to make the semantics clear and use-- a custom record type to store the result.---- In a larger, more-complex application it might be-- even better to make things truly type safe and-- define a single field record together with an instance-- of monoid for each kind of count.recordWCwhereconstructorMkWClines:Natwords:Natchars:NatSemigroupWCwhereMkWCl1w1c1<+>MkWCl2w2c2=MkWC(l1+l2)(w1+w2)(c1+c2)MonoidWCwhereneutral=MkWC000coveringtoWC:String->WCtoWCs=MkWC1(length(wordss))(lengths)coveringwordCount:(path:String)->IO(EitherFileErrorWC)wordCountpath=foldLinespathtoWC-- this is for testing the `wordCount` example.coveringtestWC:(path:String)->IO()testWCpath= Prelude.do
Right(MkWClswscs)<-wordCountpath|Lefterr=>putStrLn"Error: \{showerr}"putStrLn"\{showls} lines, \{showws} words, \{showcs} characters"