Edit this page Backlinks This page is read only. You can view the source, but not change it. Ask your administrator if you think this is wrong. ====== Lab 11. Classes, Applicatives and More Monads ====== ===== 11.0 Introduction ===== A powerful tool for understanding classes in Haskell is the '':info <class>'' in the ghci command line: <code haskell> >:info Eq type Eq :: * -> Constraint -- We'll return to this a bit later :-) class Eq a where -- Shows the blue-print of the class (==) :: a -> a -> Bool (/=) :: a -> a -> Bool {-# MINIMAL (==) | (/=) #-} -- The minimal requirements to be implemented by a data type -- to be included in the class -- Defined in ‘GHC.Classes’ instance (Eq a, Eq b) => Eq (Either a b) -- Instances of the class -- Defined in ‘Data.Either’ instance Eq a => Eq (Maybe a) -- Defined in ‘GHC.Maybe’ instance Eq Integer -- Defined in ‘GHC.Num.Integer’ </code> If we look at Monad: <code haskell> >:info Monad type Monad :: (* -> *) -> Constraint class Applicative m => Monad m where (>>=) :: m a -> (a -> m b) -> m b (>>) :: m a -> m b -> m b return :: a -> m a {-# MINIMAL (>>=) #-} ... </code> We see that the only requirement for Monad to work is overriding ''( >>= )''. However, the catch comes from the restriction: <code haskell> class Applicative m => Monad m ... </code> By looking at the Applicative: <code haskell> >:info Applicative type Applicative :: (* -> *) -> Constraint class Functor f => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b GHC.Base.liftA2 :: (a -> b -> c) -> f a -> f b -> f c (*>) :: f a -> f b -> f b (<*) :: f a -> f b -> f a {-# MINIMAL pure, ((<*>) | liftA2) #-} </code> We see that besides $ ( >>= ) $, we need pure and $ ( <*> ) $. If we recall the definitions from the previous laboratory: <code haskell> instance Applicative Foo where pure = return ff <*> fx = ff >>= (\f -> fx >>= (\x -> return (f x))) </code> We see that pure and (<*>) are implemented using return and ( >>= ), the actual needs to implement a Monad. However, it might initially be curious how we implement the functions of a class deemed mandatory for another with calls from the latter. This is due to the ad-hoc polymorphism in Haskell and the language's recursive nature (recall the linked definitions between $(==)$ and $(/=)$). And historically, Monad was added before Applicative in Haskell, so this assures backwards compatibility. However, as you will see in the next exercises, you can first implement the instance for Applicative and use its functions to derive Monad. ===== 11.1. Tasks ===== For the following exercises: Define the data structure and instances for Eq, Show, Functor, Applicative (in 2 ways from scratch and based on Monad) and Monad (in 2 ways from scratch and based on Applicative) for the following: 1. data MyList a = ... 2. data MyTree a = ... 3. data MyMaybe a = ... 4. data MyPair a b = ... <note> As you can see from :info, the type for our classes is unary (type Class :: * -> Constraint), meaning that they expect to add just another type as parameter, as in MyList that expects and a. \\ However, MyPair needs 2 types to form an object. We can think of MyPair as a hash set (key, value) and curry the types: <code haskell> instance Class (MyPair a) where ... </code> Meaning "I already got type a, I expect you to give type b and apply your operations only on the object of type b.". </note> Create objects for each class and test the validity of your class implementations with examples for show, fmap, $<*>$, $>>=$. ===== 11.2. More Applicatives ===== Applicates, as monads, are used preponderantly for type construction and validation. 5. For the first task we will want to validate a database of users. Being given the following data types: <code haskell> data User = User { name :: String , age :: Int , balance :: Int , password :: String } deriving Show data Validation e a = Valid a | Invalid e deriving Show </code> 5.1. Create the instance of Applicative for Validation. 5.2. Create functions to validate each attribute of a user. <code haskell> validateAttribute :: a -> Validation [String] a </code> For example, validateAge will only allow Users with positive ages under < 150 years, otherwise it will return an Error. For validateName, we want to make sure name starts with a capital letter and only contains letters. Password has to have at least 6 characters, a majuscule and a special character. Balance can also be negative. 5.3. Make use of the above functions, and the functions from Applicative, as well as infixed fmap ''(<$>)'' to create a validation function for the user. <code haskell> validateUser :: String -> Int -> Int -> String -> Validation [String] User </code> 5.4 Create unpackUser that takes a tuple and returns a User: <code haskell> unpackUser :: (String, Int, Int, String) -> User </code> 5.5 Being given the following list of user data: <code haskell> user_data = [("Catalin", 25, -1000, "aBc123!"), ("Nicoleta", 19, 1000, "da"), ("Nicoleta", 19, 1000, "Daba12_"), ("Andrei", 30, 2000, "pAr.la2"), ("Iust1n", 45, 3000, "kajD413!"), ("Iustin", 45, 3000, "kajD413!"), ("Maria", 28, -1500, "parOla3!")] type BankAccount = [Validation [String] User] </code> Create a function that filters the invalid users and adds them to a database: <code haskell> addUsers :: BankAccount -> [User] -> BankAccount </code> 5.6 Filter the users with a negative balance. <code haskell> filter_debtors :: BankAccount -> BankAccount </code> 6. Recall what a parser is from the course lecture: <code haskell> import Data.Char import Control.Applicative data Expr = Var String | Val Int | Plus Expr Expr deriving Show data Parser a = Parser (String -> [(a,String)]) parse (Parser p) s = p s instance Monad Parser where -- (>>=) :: Parser a -> (a -> Parser b) -> Parser b mp >>= f = Parser $ \s -> case parse mp s of [] -> [] [(v,r)] -> parse (f v) r return x = Parser $ \s -> [(x,s)] instance Applicative Parser where af <*> mp = do f <- af v <- mp return $ f v pure = return instance Functor Parser where fmap f mp = do x <- mp return $ f x charParser :: Char -> Parser Char charParser c = Parser $ \s -> case s of [] -> [] (x:xs) -> if (x == c) then [(x,xs)] else [] predicateParser :: (Char -> Bool) -> Parser Char predicateParser p = Parser $ \s -> case s of [] -> [] (x:xs) -> if p x then [(x,xs)] else [] </code> 6.1 Try to understand and reimplement the instances for Functor, Applicative and Monad for Parser yourself. 6.2 Using the definitions above and functions from Data.Char [5], implement letter - that parses one letter of the input, and digit - that parses one digit (given as a char) of the input. <code haskell> letter :: Parser Char letter = undefined digit :: Parser Char digit = undefined </code> 6.3 Use letter, digit and functions from the classes studied in this laboratory to define letterDigit that parses one letter and one digit one after the other: <code haskell> letterDigit :: Parser (Char,Char) letterDigit = undefined positive_parse_ex = parse letterDigit "a1b2c3" -- by calling this in the command line we should get [(('a','1'),"b2c3")] negative_parse_ex = parse letterDigit "aa2a" -- by calling this in the command line we should get [] </code> ===== 11.3. Nice to read ===== 1. https://mmhaskell.com/monads/applicatives 2. https://learnyouahaskell.com/functors-applicative-functors-and-monoids 3. https://learnyouahaskell.com/a-fistful-of-monads 4. https://www.youtube.com/watch?v=FLAPIgvlVnE&t=1945s&ab_channel=JamesHobson 5. [Data.Char Library](https://hackage.haskell.org/package/base-4.21.0.0/docs/Data-Char.html)