Exercises part 3
-
ApplicativeextendsFunctor, because everyApplicativeis also aFunctor. Proof this by implementingmapin terms ofpureand(<*>). -
MonadextendsApplicative, because everyMonadis also anApplicative. Proof this by implementing(<*>)in terms of(>>=)andpure. -
Implement
(>>=)in terms ofjoinand other functions in theMonadhierarchy. -
Implement
joinin terms of(>>=)and other functions in theMonadhierarchy. -
There is no lawful
Monadimplementation forValidated e. Why? -
In this slightly extended exercise, we are going to simulate CRUD operations on a data store. We will use a mutable reference (imported from
Data.IOReffrom the base library) holding a list ofUsers paired with a unique ID of typeNatas our user data base:DB : Type DB = IORef (List (Nat,User))Most operations on a database come with a risk of failure: When we try to update or delete a user, the entry in question might no longer be there. When we add a new user, a user with the given email address might already exist. Here is a custom error type to deal with this:
data DBError : Type where UserExists : Email -> Nat -> DBError UserNotFound : Nat -> DBError SizeLimitExceeded : DBErrorIn general, our functions will therefore have a type similar to the following:
someDBProg : arg1 -> arg2 -> DB -> IO (Either DBError a)We'd like to abstract over this, by introducing a new wrapper type:
record Prog a where constructor MkProg runProg : DB -> IO (Either DBError a)We are now ready to write us some utility functions. Make sure to follow the following business rules when implementing the functions below:
-
Email addresses in the DB must be unique. (Consider implementing
Eq Emailto verify this). -
The size limit of 1000 entries must not be exceeded.
-
Operations trying to lookup a user by their ID must fail with
UserNotFoundin case no entry was found in the DB.
You'll need the following functions from
Data.IORefwhen working with mutable references:newIORef,readIORef, andwriteIORef. In addition, functionsData.List.lookupandData.List.findmight be useful to implement some of the functions below.-
Implement interfaces
Functor,Applicative, andMonadforProg. -
Implement interface
HasIOforProg. -
Implement the following utility functions:
throw : DBError -> Prog a getUsers : Prog (List (Nat,User)) -- check the size limit! putUsers : List (Nat,User) -> Prog () -- implement this in terms of `getUsers` and `putUsers` modifyDB : (List (Nat,User) -> List (Nat,User)) -> Prog () -
Implement function
lookupUser. This should fail with an appropriate error, if a user with the given ID cannot be found.lookupUser : (id : Nat) -> Prog User -
Implement function
deleteUser. This should fail with an appropriate error, if a user with the given ID cannot be found. Make use oflookupUserin your implementation.deleteUser : (id : Nat) -> Prog () -
Implement function
addUser. This should fail, if a user with the givenEmailalready exists, or if the data banks size limit of 1000 entries is exceeded. In addition, this should create and return a unique ID for the new user entry.addUser : (new : User) -> Prog Nat -
Implement function
updateUser. This should fail, if the user in question cannot be found or a user with the updated user'sEmailalready exists. The returned value should be the updated user.updateUser : (id : Nat) -> (mod : User -> User) -> Prog User -
Data type
Progis actually too specific. We could just as well abstract over the error type and theDBenvironment:record Prog' env err a where constructor MkProg' runProg' : env -> IO (Either err a)Verify, that all interface implementations you wrote for
Progcan be used verbatim to implement the same interfaces forProg' env err. The same goes forthrowwith only a slight adjustment in the function's type.
-