In the following exercises you will need to use the following terms to make the expressions typecheck:
pure
(<$>) -- or fmap
(<*>)
- Make the following expression typecheck.
added :: Maybe Integer
added = (+3) (lookup 3 $ zip [1,2,3] [4,5,6])
Answer:
added :: Maybe Integer
added = (+3) <$> (lookup 3 $ zip [1,2,3] [4,5,6])
- Make the following expression typecheck.
y :: Maybe Integer
y = lookup 3 $ zip [1,2,3] [4,5,6]
z :: Maybe Integer
z = lookup 2 $ zip [1,2,3] [4,5,6]
tupled :: Maybe (Integer, Integer)
tupled = (,) y z
Answer:
y :: Maybe Integer
y = lookup 3 $ zip [1,2,3] [4,5,6]
z :: Maybe Integer
z = lookup 2 $ zip [1,2,3] [4,5,6]
tupled :: Maybe (Integer, Integer)
tupled = (,) <$> y <*> z
- Make the following expressions typecheck.
import Data.List (elemIndex)
x :: Maybe Int
x = elemIndex 3 [1,2,3,4,5]
y :: Maybe Int
y = elemIndex 4 [1,2,3,4,5]
max' :: Int -> Int -> Int
max' = max
maxed :: Maybe Int
maxed = max' x y
Answer:
import Data.List (elemIndex)
x :: Maybe Int
x = elemIndex 3 [1,2,3,4,5]
y :: Maybe Int
y = elemIndex 4 [1,2,3,4,5]
max' :: Int -> Int -> Int
max' = max
maxed :: Maybe Int
maxed = max' <$> x <*> y
- Make the following expressions typecheck.
xs = [1,2,3]
ys = [4,5,6]
x :: Maybe Integer
x = lookup 3 $ zip xs ys
y :: Maybe Integer
y = lookup 2 $ zip xs ys
summed :: Maybe Integer
summed = sum $ (,) x y
Answer:
xs = [1,2,3]
ys = [4,5,6]
x :: Maybe Integer
x = lookup 3 $ zip xs ys
y :: Maybe Integer
y = lookup 2 $ zip xs ys
summed :: Maybe Integer
summed = fmap sum $ (,) <$> x <*> y
Write an `Applicative instance for Identity.
newtype Identity a = Identity a deriving (Eq, Ord, Show)
instance Functor Identity where
fmap = undefined
instance Applicative Identity where
pure = undefined
(<*>) = undefined
Write an Applicative
instance for Constant
.
newtype Constant a b =
Constant {getConstant :: a}
deriving (Eq, Ord, Show)
instance Functor (Constant a) where
fmap = undefined
instance Monoid a
=> Applicative (Constant a) where
pure = undefined
(<*>) = undefined
Given the function and values provided, use (<$>)
from Functor
, (<*>) and
purefrom the
Applicative` typeclass to fill in missing bits of the broken code to make it work.
const <$> Just "Hello" <*> "World"
Answer:
const <$> Just "Hello" <*> pure "World"
(,,,) Just 90 <*> Just 10 Just "Tierness" [1,2,3]
Answer:
pure (,,,) <*> Just 90 <*> Just 10 <*> Just "Tierness" <*> pure [1,2,3]
Implement the list Applicative
. Writing a minimally complete Applicative
instance calls for writing the definitions of both pure
and <*>
. We're going to provide a hint as well. Use the checkers library to validate your Applicative
instance.
data List a =
Nil
| Cons a (List a)
deriving (Eq, Show)
Remember what you wrote for the list Functor
:
instance Functor List where
fmap = undefined
Writing the list Applicative
is similar.
instance Applicative List where
pure = undefined
(<*>) = undefined
Expected result:
λ> f = Cons (+1) (Cons (*2) Nil)
λ> v = Cons 1 (Cons 2 Nil)
λ> f <*> v
Cons 2 (Cons 3 (Cons 2 (Const 4 Nil)))
In case you get stuck, use the following functions and hints.
append :: List a -> List a -> List a
append Nil ys = ys
append (Cons x xs) ys = Cons x $ xs `append` ys
fold :: (a -> b -> c) -> b -> List a -> b
fold _ b Nil = b
fold f b (Cons h t) = f h (fold f b t)
concat' :: List (List a) -> List a
concat' = fold append Nil
-- write this one in terms
-- of concat' and fmap
flatmap :: (a - List b)
-> List a
-> List b
flatMap f as = undefined
Use the above and try using flatmap
and fmap
without explicitly pattern matching on cons cells. You'll still need to handle the Nil
cases.
flatMap
is less strange than it would initially seem. It's basically fmap
then smush.
λ> fmap (\x -> [x, 9]) [1,2,3]
[[1,9], [2,9], [3,9]]
λ> toMyList = foldr Cons Nil
λ> xs = toMyList [1,2,3]
λ> c = Cons
λ> f x = x `c` (9 `c` Nil)
λ> flatMap f x
Cons 1 (Cons 9 (Cons 2
Cons 9 (Cons 3 (Cons 9 Nil))))
Applicative
instances, unlike Functors
, are not guaranteed to have a unique implementation for a given datatype.
Solution file (can be run as a standalone script)
Implement the ZipList Applicative
. Use the checkers library to validate your Applicative
instance. We're going to provide the EqPropr
instance and explain the weirdness in a moment.
data List a =
Nil
| Cons a (List a)
deriving (Eq, Show)
take' :: Int -> List a -> List a
take' = undefined
instance Functor List where
fmap = undefined
instance Applicative List where
pure = undefined
(<*>) = undefined
newtype ZipList' a =
ZipList' (List a)
deriving (Eq, Show)
instance Eq a => EqProp (ZipList' a) where
xs =-= ys = xs' `eq` ys'
where xs' = let (ZipList' l) = xs
in take' 3000 l
ys' = let (ZipList' l) = ys
in take' 3000 l
instance Functor ZipList' where
fmap f (ZipList' xs) = ZipList' $ fmap f xs
instance Applicative ZipList' where
pure = undefined
(<*>) = undefined
The idea is to align a list of functions with a list of values and apply the first function to the first value and so on. The instance should work with infinite lists. Some examples:
λ> zl' = ZipList'
λ> z = zl' [(+9), (*2), (+8)]
λ> z' = zl' [1..3]
λ> z <*> z'
ZipList' [10,4,11]
λ> z' = zl' (repeat 1)
λ> z <*> z'
ZipList' [10,2,9]
Note that the second z'
was an infinite list. Check Prelude
for functions that can give you what you need. One starts with the letter z
, the other with the letter r
. You’re looking for inspiration from these functions, not to be able to directly
reuse them as you’re using a custom List type, not the provided Prelude
list type.
Explaining and justifying the weird EqProp
:
The good news is it’s EqProp
that has the weird "check only the first 3,000 values"
semantics instead of making the Eq
instance weird. The bad news is this is a byproduct of testing for equality between infinite lists... that is, you can’t. If you use a typical EqProp
instance, the test for homomorphism in your Applicative
instance will
chase the infinite lists forever. Since QuickCheck
is already an exercise in "good enough" validity checking, we could choose to feel justified in this. If you don’t believe us try running the following in your REPL
:
repeat 1 == repeat 1
Solution file (can be run as a script)
Validation
has the same representation as Either
, but it can be different. The Functor
will behave the same, but the Applicativbe
will be different. Use the checkers library for testing.
data Validation e a =
Failure e
| Success a
deriving (Eq, Show)
-- same as Either
instance Functor (Validation e) where
fmap = undefined
-- This is different
instance Monoid e => Applicative (Validation e)
pure = undefined
(<*>) = undefined
Solution file (can be run as a script)
Given a type that has an instance of Applicative
, specialize the types of the methods. Test your specialization in the REPL
. One way to do this is to bind aliases
of the typeclass methods to more concrete types that have the type we told you to fill in.
- For the followoing type:
-- Type
[]
-- Methods
pure :: a -> ? a
(<*>) :: ? (a -> b) -> ? a -> ? b
Answer:
pure @[] :: a -> [a]
(<*>) @[] :: [(a -> b)] -> [a] -> [b]
- For the following type:
-- Type
IO
-- Methods
pure :: a -> ? a
(<*>) :: ? (a -> b) -> ? a -> ? b
Answer:
pure @IO :: a -> IO a
(<*>) @IO :: IO (a -> b) -> IO a -> IO b
- For the following type:
-- Type
(,) a
-- Methods
pure :: a -> ? a
(<*>) :: ? (a -> b) -> ? a -> ? b
Answer:
pure @((,) l):: a -> (l, a)
(<*>) @((,) l):: (l, a -> b) -> (l, a) -> (l, b)
- For the following type:
-- Type
(->) e
-- Methods
pure :: a -> ? a
(<*>) :: ? (a -> b) -> ? a -> ? b
**Answer:**
```hs
pure @((->) e) :: a -> (e -> a)
(<*>) @((->) e) :: (e -> a -> b) -> (e -> a) -> (e -> b)
Write instances for the following datatypes. Use the checkers library to validate the instances.
- For the following data type
data Pair a = Pair a a deriving Show
Solution file (can be run as a script)
- For the following data type
data Two a b = Two a b
Solution file (can be run as a script)
- For the following data type
data Three a b c = Three a b c
- For the following data type
data Three' a b = Three' a b b
Solution file (can be run as a script)
- For the following data type
data Four a b c d = Four a b c d
Solution file (can be run as a script)
- For the following data type
data Four' a b = Four' a a a b
Solution file (can be run as script)
Remember the vowels and stops exercise in the folds chapter? Write the function to generate the possible combinations of three input lists using listA3
from Control.Applicative
.
import Control.Applicative (liftA3)
stops :: String
stops = "pbtdkg"
vowels :: String
vowels = "aeiou"
combos :: String -> String -> String -> [(Char, Char, Char)]
combos = undefined