moduleSolutions.FunctorimportData.IORefimportData.ListimportData.List1importData.StringimportData.Vect%defaulttotal---------------------------------------------------------------------------------- Code Required from the Turoial--------------------------------------------------------------------------------interfaceFunctor'(0f:Type->Type)wheremap':(a->b)->fa->fbinterfaceFunctor'f=>Applicative'fwhereapp:f(a->b)->fa->fbpure':a->farecordComp(f,g:Type->Type)(a:Type)whereconstructorMkCompunComp:f(ga)implementationFunctorf=>Functorg=>Functor(Compfg)wheremapf(MkCompv)=MkComp $ mapf<$>vrecordProduct(f,g:Type->Type)(a:Type)whereconstructorMkProductfst:fasnd:gaimplementationFunctorf=>Functorg=>Functor(Productfg)wheremapf(MkProductlr)=MkProduct(mapfl)(mapfr)dataGender=Male|Female|OtherrecordNamewhereconstructorMkNamevalue:StringrecordEmailwhereconstructorMkEmailvalue:StringrecordPasswordwhereconstructorMkPasswordvalue:StringrecordUserwhereconstructorMkUserfirstName:NamelastName:Nameage:MaybeNatemail:Emailgender:Genderpassword:PasswordinterfaceCSVFieldawhereread:String->MaybeaCSVFieldGenderwhereread"m"=JustMaleread"f"=JustFemaleread"o"=JustOtherread_=NothingCSVFieldBoolwhereread"t"=JustTrueread"f"=JustFalseread_=NothingCSVFieldNatwhereread=parsePositiveCSVFieldIntegerwhereread=parseIntegerCSVFieldDoublewhereread=parseDoubleCSVFielda=>CSVField(Maybea)whereread""=JustNothingreads=Just<$>readsreadIf:(String->Bool)->(String->a)->String->MaybeareadIfpmks=ifpsthenJust(mks)elseNothingisValidName:String->BoolisValidNames=letlen=lengthsin0<len&&len<=100&&allisAlpha(unpacks)CSVFieldNamewhereread=readIfisValidNameMkNameisEmailChar:Char->BoolisEmailChar'.'=TrueisEmailChar'@'=TrueisEmailCharc=isAlphaNumcisValidEmail:String->BoolisValidEmails=letlen=lengthsin0<len&&len<=100&&allisEmailChar(unpacks)CSVFieldEmailwhereread=readIfisValidEmailMkEmailisPasswordChar:Char->BoolisPasswordChar' '=TrueisPasswordCharc=not(isControlc)&¬(isSpacec)isValidPassword:String->BoolisValidPasswords=letlen=lengthsin8<len&&len<=100&&allisPasswordChar(unpacks)CSVFieldPasswordwhereread=readIfisValidPasswordMkPassworddataHList:(ts:ListType)->TypewhereNil:HListNil(::):(v:t)->(vs:HListts)->HList(t::ts)---------------------------------------------------------------------------------- Functor---------------------------------------------------------------------------------- 1Functor'Maybewheremap'_Nothing=Nothingmap'f(Justv)=Just $ fvFunctor'Listwheremap'_[]=[]map'f(x::xs)=fx::map'fxsFunctor'List1wheremap'f(h:::t)=fh:::map'ftFunctor'(Vectn)wheremap'_[]=[]map'f(x::xs)=fx::map'fxsFunctor'(Eithere)wheremap'_(Leftve)=Leftvemap'f(Rightva)=Right $ fvaFunctor'(Paire)wheremap'f(ve,va)=(ve,fva)-- 2[Prod]Functorf=>Functorg=>Functor(\a=>(fa,ga))wheremapfun(fa,ga)=(mapfunfa,mapfunga)-- 3recordIdentityawhereconstructorIdvalue:aFunctorIdentitywheremapf(Idva)=Id $ fva-- 4recordConst(e,a:Type)whereconstructorMkConstvalue:eFunctor(Conste)wheremap_(MkConstv)=MkConstv-- 5dataCrud:(i:Type)->(a:Type)->TypewhereCreate:(value:a)->CrudiaUpdate:(id:i)->(value:a)->CrudiaRead:(id:i)->CrudiaDelete:(id:i)->CrudiaFunctor(Crudi)wheremapf(Createvalue)=Create $ fvaluemapf(Updateidvalue)=Updateid $ fvaluemap_(Readid)=Readidmap_(Deleteid)=Deleteid-- 6dataResponse:(e,i,a:Type)->TypewhereCreated:(id:i)->(value:a)->ResponseeiaUpdated:(id:i)->(value:a)->ResponseeiaFound:(values:Lista)->ResponseeiaDeleted:(id:i)->ResponseeiaError:(err:e)->ResponseeiaFunctor(Responseei)wheremapf(Createdidvalue)=Createdid $ fvaluemapf(Updatedidvalue)=Updatedid $ fvaluemapf(Foundvalues)=Found $ mapfvaluesmap_(Deletedid)=Deletedidmap_(Errorerr)=Errorerr-- 7dataValidated:(e,a:Type)->TypewhereInvalid:(err:e)->ValidatedeaValid:(val:a)->ValidatedeaFunctor(Validatede)wheremap_(Invaliderr)=Invaliderrmapf(Validval)=Valid $ fval---------------------------------------------------------------------------------- Applicative---------------------------------------------------------------------------------- 1Applicative'(Eithere)wherepure'=Rightapp(Rightf)(Rightv)=Right $ fvapp(Leftve)_=Leftveapp_(Leftve)=LeftveApplicativeIdentitywherepure=IdIdf<*>Idv=Id $ fv-- 2{n:_}->Applicative'(Vectn)wherepure'=replicatenapp[][]=[]app(f::fs)(v::vs)=fv::appfsvs-- 3Monoide=>Applicative'(Paire)wherepure'v=(neutral,v)app(e1,f)(e2,v)=(e1<+>e2,fv)-- 4Monoide=>Applicative(Conste)wherepure_=MkConstneutralMkConste1<*>MkConste2=MkConst $ e1<+>e2-- 5Semigroupe=>Applicative(Validatede)wherepure=ValidValidf<*>Validv=Valid $ fvValid_<*>Invalidve=InvalidveInvalide1<*>Invalide2=Invalid $ e1<+>e2Invalidve<*>Valid_=Invalidve-- 6dataCSVError:TypewhereFieldError:(line,column:Nat)->(str:String)->CSVErrorUnexpectedEndOfInput:(line,column:Nat)->CSVErrorExpectedEndOfInput:(line,column:Nat)->CSVErrorApp:(fst,snd:CSVError)->CSVErrorSemigroupCSVErrorwhere(<+>)=App-- 7readField:CSVFielda=>(line,column:Nat)->String->ValidatedCSVErrorareadFieldlinecolstr=maybe(Invalid $ FieldErrorlinecolstr)Valid(readstr)toVect:(n:Nat)->(line,col:Nat)->Lista->ValidatedCSVError(Vectna)toVect0line_[]=Valid[]toVect0linecol_=Invalid(ExpectedEndOfInputlinecol)toVect(Sk)linecol[]=Invalid(UnexpectedEndOfInputlinecol)toVect(Sk)linecol(x::xs)=(x::)<$>toVectkline(Scol)xs-- We can't use do notation here as we don't have an implementation-- of Monad for `Validated`readUser':(line:Nat)->ListString->ValidatedCSVErrorUserreadUser'liness=casetoVect6line0ssofValid[fn,ln,a,em,g,pw]=>[|MkUser(readFieldline1fn)(readFieldline2ln)(readFieldline3a)(readFieldline4em)(readFieldline5g)(readFieldline6pw)|]Invaliderr=>InvaliderrreadUser:(line:Nat)->String->ValidatedCSVErrorUserreadUserline=readUser'line.forget.split(','==)interfaceCSVLineawheredecodeAt:(line,col:Nat)->ListString->ValidatedCSVErroraCSVLine(HList[])wheredecodeAt__[]=ValidNildecodeAtlc_=Invalid(ExpectedEndOfInputlc)CSVFieldt=>CSVLine(HListts)=>CSVLine(HList(t::ts))wheredecodeAtlc[]=Invalid(UnexpectedEndOfInputlc)decodeAtlc(s::ss)=[|readFieldlcs::decodeAtl(Sc)ss|]decode:CSVLinea=>(line:Nat)->String->ValidatedCSVErroradecodeline=decodeAtline1.forget.split(','==)hdecode:(0ts:ListType)->CSVLine(HListts)=>(line:Nat)->String->ValidatedCSVError(HListts)hdecode_=decode-- 8-- 8.1head:HList(t::ts)->thead(v::_)=v-- 8.2tail:HList(t::ts)->HListtstail(_::t)=t-- 8.3(++):HListxs->HListys->HList(xs++ys)[]++ws=ws(v::vs)++ws=v::(vs++ws)-- 8.4indexList:(as:Lista)->Fin(lengthas)->aindexList(x::_)FZ=xindexList(_::xs)(FSy)=indexListxsyindexList[]ximpossibleindex:(ix:Fin(lengthts))->HListts->indexListtsixindexFZ(v::_)=vindex(FSx)(_::vs)=indexxvsindexix[]impossible-- 8.5namespaceHVectpublicexportdataHVect:(ts:VectnType)->TypewhereNil:HVectNil(::):(v:t)->(vs:HVectts)->HVect(t::ts)publicexporthead:HVect(t::ts)->thead(v::_)=vpublicexporttail:HVect(t::ts)->HVecttstail(_::t)=tpublicexport(++):HVectxs->HVectys->HVect(xs++ys)[]++ws=ws(v::vs)++ws=v::(vs++ws)publicexportindex:{0n:Nat}->{0ts:VectnType}->(ix:Finn)->HVectts->indexixtsindexFZ(v::_)=vindex(FSx)(_::vs)=indexxvsindexix[]impossible-- 8.6-- Note: We are usually not allowed to pattern match-- on an erased argument. However, in this case, the-- shape of `ts` follows from `n`, so we can pattern-- match on `ts` to help Idris inferring the types.---- Note also, that we create a `HVect` holding only empty-- `Vect`s. We therefore only need to know about the length-- of the type-level vector to implement this.empties:{n:Nat}->{0ts:VectnType}->HVect(Vect0<$>ts)empties{n=0}{ts=[]}=[]empties{n=S_}{ts=_::_}=[]::emptieshcons:{0ts:VectnType}->HVectts->HVect(Vectm<$>ts)->HVect(Vect(Sm)<$>ts)hcons[][]=[]hcons(v::vs)(w::ws)=(v::w)::hconsvswshtranspose:{n:Nat}->{0ts:VectnType}->Vectm(HVectts)->HVect(Vectm<$>ts)htranspose[]=emptieshtranspose(x::xs)=hconsx(htransposexs)vects:Vect3(HVect[Bool,Nat,String])vects=[[True,100,"Hello"],[False,0,"Idris"],[False,2,"!"]]vects':HVect[Vect3Bool,Vect3Nat,Vect3String]vects'=htransposevects-- 9Applicativef=>Applicativeg=>Applicative(Compfg)wherepure=MkComp.pure.pureMkCompff<*>MkCompfa=MkComp[|ff<*>fa|]-- 10Applicativef=>Applicativeg=>Applicative(Productfg)wherepurev=MkProduct(purev)(purev)MkProductfflffr<*>MkProductfalfar=MkProduct(ffl<*>fal)(ffr<*>far)---------------------------------------------------------------------------------- Monad---------------------------------------------------------------------------------- 1mapWithApp:Applicativef=>(a->b)->fa->fbmapWithAppfunfa=purefun<*>fa-- 2appWithBind:Monadf=>f(a->b)->fa->fbappWithBindfffa=ff>>=(\fun=>fa>>=(\va=>pure $ funva))-- or, more readable, the same thing with do notationappWithBindDo:Monadf=>f(a->b)->fa->fbappWithBindDofffa=dofun<-ffva<-fapure $ funva-- 3bindFromJoin:Monadm=>ma->(a->mb)->mbbindFromJoinmaf=join $ mapfma-- 4joinFromBind:Monadm=>m(ma)->majoinFromBind=(>>=id)-- 5-- The third law-- `mf <*> ma = mf >>= (\fun => map (fun $) ma)`-- does not hold, as implementation of *apply* on the-- right hand side does not perform error accumulation.---- `Validated e` therefore comes without implementation of-- `Monad`. In order to use it in do blocks, it's best to-- convert it to Either and back.-- 6DB:TypeDB=IORef(List(Nat,User))dataDBError:TypewhereUserExists:Email->Nat->DBErrorUserNotFound:Nat->DBErrorSizeLimitExceeded:DBErrorrecordProgawhereconstructorMkProgrunProg:DB->IO(EitherDBErrora)-- 6.1-- make sure you are able to read and understand the-- point-free style in the implementation of `map`!FunctorProgwheremapf(MkProgrun)=MkProg $ map(mapf).runApplicativeProgwherepurev=MkProg $ _=>pure(Rightv)MkProgrf<*>MkProgra=MkProg $ \db=>doRightfun<-rfdb|Lefterr=>pure(Lefterr)Rightva<-radb|Lefterr=>pure(Lefterr)pure(Right $ funva)MonadProgwhereMkProgra>>=f=MkProg $ \db=>doRightva<-radb|Lefterr=>pure(Lefterr)runProg(fva)db-- 6.2HasIOProgwhereliftIOact=MkProg $ _=>mapRightact-- 6.3throw:DBError->Progathrowerr=MkProg $ _=>pure(Lefterr)getUsers:Prog(List(Nat,User))getUsers=MkProg(mapRight.readIORef)putUsers:List(Nat,User)->Prog()putUsersus=iflengthus>1000thenthrowSizeLimitExceededelseMkProg $ \db=>Right<$>writeIORefdbusmodifyDB:(List(Nat,User)->List(Nat,User))->Prog()modifyDBf=getUsers>>=putUsers.f-- 6.4lookupUser:(id:Nat)->ProgUserlookupUserid=dodb<-getUserscaselookupiddbofJustu=>pureuNothing=>throw(UserNotFoundid)-- 6.5deleteUser:(id:Nat)->Prog()deleteUserid=-- In the first step, we are only interested in the potential-- of failure, not the actual user value.-- We can therefore use `(>>)` to chain the operations.-- In order to do so, we must wrap `lookupUser` in a call-- to `ignore`.ignore(lookupUserid)>>modifyDB(filter $ (id/=).fst)-- 6.6EqEmailwhere(==)=(==)`on`valuenewId:List(Nat,User)->NatnewId=S.foldl(\n1,(n2,_)=>maxn1n2)0addUser:(u:User)->ProgNataddUseru=dous<-getUserscasefind((u.email==).email.snd)usofJust(id,_)=>throw $ UserExistsu.emailidNothing=>letid=newIdusinputUsers((id,u)::us)$>id-- 6.7update:Eqa=>a->b->List(a,b)->List(a,b)updatevavb=map(\p@(va',vb')=>ifva==va'then(va,vb)elsep)updateUser:(id:Nat)->(mod:User->User)->ProgUserupdateUseridmod=dou<-mod<$>lookupUseridus<-getUserscasefind((u.email==).email.snd)usofJust(id',_)=>ifid/=id'thenthrow $ UserExistsu.emailid'elseputUsers(updateiduus)$>uNothing=>putUsers(updateiduus)$>u-- 6.8recordProg'enverrawhereconstructorMkProg'runProg':env->IO(Eithererra)Functor(Prog'enverr)wheremapf(MkProg'run)=MkProg' $ map(mapf).runApplicative(Prog'enverr)wherepurev=MkProg' $ _=>pure(Rightv)MkProg'rf<*>MkProg'ra=MkProg' $ \db=>doRightfun<-rfdb|Lefterr=>pure(Lefterr)Rightva<-radb|Lefterr=>pure(Lefterr)pure(Right $ funva)Monad(Prog'enverr)whereMkProg'ra>>=f=MkProg' $ \db=>doRightva<-radb|Lefterr=>pure(Lefterr)runProg'(fva)dbHasIO(Prog'enverr)whereliftIOact=MkProg' $ _=>mapRightactthrow':err->Prog'enverrathrow've=MkProg' $ _=>pure(Leftve)