Write a function fact
that takes an integer and returns the factorial of that integer.
Example:
fact 5 -- Output: 120
Solution:
fact :: Integer -> Integer
fact 0 = 1
fact n = n * fact (n - 1)
Int
vs Integer
Write a function len
that takes a list and returns the length of the list.
Example:
len [1,2,3,4,5] -- Output: 5
Solution:
len :: [a] -> Integer
len [] = 0
len (x:xs) = 1 + len xs
Alternative solution using foldr
:
len :: [a] -> Int
len = foldr (\_ acc -> acc + 1) 0
Write a function rev
that takes a list and returns the list in reverse order.
Example:
rev [1,2,3,4,5] -- Output: [5,4,3,2,1]
Solution:
rev :: [a] -> [a]
rev [] = []
rev (x:xs) = (rev xs) ++ [x]
Alternative solution using foldl
:
rev :: [a] -> [a]
rev = foldl (\acc x -> x : acc) []
Write a function foldleft
that takes a binary function, an initial value, and a list, and applies the function to the list in a left-associative manner.
Example:
foldleft (+) 0 [1,2,3,4,5] -- Output: 15
Solution:
foldleft :: (a -> b -> b) -> b -> [a] -> b
foldleft f z [] = z
foldleft f z (x:xs) = foldleft f (f x z) xs
Write a function rev'
that takes a list and returns the list in reverse order, using foldleft
.
Example:
rev' [1,2,3,4,5] -- Output: [5,4,3,2,1]
The solution presented is wrong, explain why.
rev' :: [a] -> [a]
rev' = foldl (:) []
The cons operator :
has as first argument an element of type a
and as second a list of type a
:
(:) :: a -> [a] -> [a]
foldl (:) [] [1,2,3,4,5]
foldl (:) ([] : 1) [2,3,4,5] -- error
Correct approach:
rev :: [a] -> [a]
rev = foldl (\acc x -> x : acc) []
Define a data type TrafficLight
with three constructors: Red
, Yellow
, and Green
. Implement Show
and Eq
instances for TrafficLight
.
Example:
show Red -- Output: "red"
Red == Red -- Output: True
Solution:
data TrafficLight = Red | Yellow | Green
instance Show TrafficLight where
show Red = "red"
show Yellow = "yellow"
show Green = "green"
instance Eq TrafficLight where
Red == Red = True
Yellow == Yellow = True
Green == Green = True
_ == _ = False
putStrLn
vs print
module Main where
data TrafficLight = Red | Yellow | Green deriving (Show,Eq)
main = do
putStrLn (show Red)
print Red
The putStrLn
function is used to write a string to the terminal, followed by a newline character. The show
function is used to convert the Red
value to a string before it's passed to putStrLn
. So, this line is converting Red
to a string, and then printing that string to the terminal, followed by a newline 2, 6.
The print
function in Haskell is used to print a value to the terminal. However, unlike putStrLn
, print
can work with any value, not just strings. This is because print
uses the Show
typeclass to convert its argument to a string before printing it. The Show
typeclass provides a way to convert values to strings in Haskell. So, this line is converting Red
to a string using the Show
typeclass, and then printing that string to the terminal 6.
First define the Point
data type, then write a function that calculates the distance
between two points.
Example:
p1 = Point 3.0 4.0
p2 = Point 6.0 8.0
distance p1 p2
-- The output should be 5.0
Solution:
pointx (Point x _) = x
pointy (Point _ y) = y
distance :: Point -> Point -> Float
distance (Point x1 y1) (Point x2 y2) =
let dx = x1 - x2
dy = y1 - y2
in sqrt $ dx^2 + dy^2
Implement a filtering function myFilter :: (a -> Bool) -> [a] -> [a]
that takes a predicate function and a list, and returns a new list with elements that satisfy the predicate.
Example:
myFilter even [1,2,3,4,5,6] -- The output should be [2,4,6]
Solution:
myFilter :: (a -> Bool) -> [a] -> [a]
myFilter _ [] = []
myFilter f (h:t)
| f h = h : myFilter f t
| otherwise = myFilter f t
Write a custom function called myZip
that mimics the behavior of the standard zip
function. The function should take two lists as input and return a list of tuples. If the lists are of unequal length, the resulting list should be as long as the shorter list.
Example:
myZip [1, 2, 3] [4, 5, 6]
-- Output: [(1,4),(2,5),(3,6)]
Solution:
myZip :: [a] -> [b] -> [(a,b)]
myZip [] _ = []
myZip _ [] = []
myZip (x:xs) (y:ys) = (x,y) : myZip xs ys
The function uses pattern matching to check if either of the input lists is empty. If either list is empty, it returns an empty list. If neither list is empty, it constructs a tuple from the heads of the two lists, then recurses on the tails of the two lists. The constructed tuple is prepended to the result of the recursive call, building up the list of tuples.
Write a function in Haskell called myZipWith
. The myZipWith
function should take three arguments: a binary function f
, and two lists. The function should combine the two lists into a single list by applying the function f
to the corresponding elements of the two lists. If the lists are of different lengths, the function should stop processing once it has reached the end of the shortest list.
Example:
myZipWith (+) [1, 2, 3] [4, 5, 6]
-- This will output: [5, 7, 9]
Solution:
myZipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
myZipWith _ [] _ = []
myZipWith _ _ [] = []
myZipWith f (x:xs) (y:ys) = (f x y) : myZipWith f xs ys
Write a function sumf
that takes a list of numbers and returns the sum of all elements in the list using foldr
.
Example:
sumf [1,2,3,4,5] -- Output: 15
Solution:
sumf :: Num a => [a] -> a
sumf = foldr (+) 0
Num a => [a] -> a
is the function's type signature. It means that the function takes a list of numeric values ([a]
) and returns a numeric value (a
). The Num a =>
part is a type constraint, specifying that a
must belong to the Num
type class, which includes types that can be used in numeric operations.
foldr (+) 0
is the function definition. It uses the foldr
function from Haskell's Prelude library. foldr
is a higher-order function that takes a binary function, a starting value (also known as an accumulator), and a list, then applies the function to the list elements and the accumulator from right to left.
foldr
Write a function elemf
that takes an element and a list, and returns True
if the element is in the list and False
otherwise, using foldr
.
Example:
elemf 3 [1,2,3,4,5] -- Output: True
elemf 7 [1,2,3,4,5] -- Output: False
Solution:
elemf :: Eq a => a -> [a] -> Bool
elemf x = foldr (\a b -> a == x || b) False
A lambda expression in Haskell starts with a backslash (\
), followed by one or more arguments, an arrow (->
), and then the body of the function.
Here is a simple example of a lambda function:
\x -> x + 1
This function takes one argument x
and adds 1 to it. You can apply this function to a value like this:
(\x -> x + 1) 4 -- returns 5
Lambda functions can also take multiple arguments:
\x y -> x + y
This function takes two arguments x
and y
, and returns their sum. You can apply this function to two values like this:
(\x y -> x + y) 3 5 -- return 8
Write a function mapf
that takes a function and a list, and returns a new list made by applying the function to each element of the original list, using foldr
.
Example:
mapf (*2) [1,2,3,4,5] -- Output: [2,4,6,8,10]
Solution:
mapf :: (a -> b) -> [a] -> [b]
mapf f = foldr (\a b -> f a : b) []
Solutions to these exercises.
Define a binary tree data structure BTree
for the binary tree data structure and write and instance of the Show
class for Btree
.
Example:
t1 = BNode 1 (bleaf 2) (BNode 3 (bleaf 4) (bleaf 5))
show t1 -- [ 1 [ 2 ] [ 3 [ 4 ] [ 5 ] ] ]
Solution:
data BTree a = BEmpty | BNode a (BTree a) (BTree a)
bleaf a = BNode a BEmpty BEmpty
instance Show a => Show (BTree a) where
show BEmpty = ""
show (BNode v left right) = " [ " ++ show v ++ show left ++ show right ++ " ] "
bleaf a
, a
is not a generic typebleaf
is a function that takes a value a
(not type a) and constructs a new tree node with that value and two empty child trees. It is not a method that is called on an object, but rather a standalone function. You can use this function to create a new tree node with a specific value.
Implement a function bmap :: (a -> b) -> BTree a -> BTree b
that applies a function to every element in the binary tree.
Example:
t1 = BNode 1 (bleaf 2) (BNode 3 (bleaf 4) (bleaf 5))
bmap (+1) tree -- [ 2 [ 3 ] [ 4 [ 5 ] [ 6 ] ] ]
Solution:
bmap :: (a -> b) -> BTree a -> BTree b
bmap _ BEmpty = BEmpty
bmap f (BNode v left right) = BNode (f v) (bmap f left) (bmap f right)
Write a function bToList
that takes a binary tree and returns a list of its elements.
Example:
t1 = BNode 1 (bleaf 2) (BNode 3 (bleaf 4) (bleaf 5))
bToList t1 -- Output: [1,2,3,4,5]
Solution:
bToList :: (BTree a) -> [a]
bToList BEmpty = []
bToList (BNode v x y) = [v] ++ (bToList x) ++ (bToList y)
Create an infinite binary tree with binf
, then implement a function btake
to take the first n
levels of the tree.
Example:
btake 2 (binf 1)
-- Expected output: "[ 4 [ 5 [ 6 ] [ 6 ] ] [ 5 [ 6 ] [ 6 ] ] ]"
Solution:
binf :: Integer -> BTree Integer
binf n = BNode n (binf (n+1)) (binf (n+1))
btake :: Integer -> BTree a -> BTree a
btake _ BEmpty = BEmpty
btake n (BNode v left right)
| n > 0 = BNode v (btake (n-1) left) (btake (n-1) right)
| otherwise = BEmpty
Alternative solution:
binf :: Integer -> BTree Integer
binf x = let t = binf (x+1)
in BNode x t t
btake _ BEmpty = BEmpty
btake 0 _ = BEmpty
btake n (BNode v l r) = BNode v (btake (n-1) l) (btake (n-1) r)
Define an instance of the Eq
type class for the BTree
data type. Two binary trees are considered equal if they contain the same elements.
Example:
t1 == t2 -- Output: True or False
Solution:
instance Eq a => Eq (BTree a) where
x == y = (bToList x) == (bToList y)
Alternative Solution:
instance Eq a => Eq (BTree a) where
BEmpty == BEmpty = True
BNode v1 l1 r1 == BNode v2 l2 r2 = v1 == v2 && l1 == l2 && r1 == r2
_ == _ = False
Write a function btfold
that takes a binary function, a binary tree, and an initial value, and applies the binary function to the elements of the binary tree and the initial value.
Example:
t4 = BNode 7 (BNode 3 (bleaf 4) (bleaf 9)) (bleaf 11)
btfold (+) t 0 -- Output: 34
Solution:
btfold :: (a -> b -> b) -> BTree a -> b -> b
btfold _ BEmpty acc = acc
btfold f (BNode v left right) i = f v (btfold f left (btfold f right i))
Write a function btfilter
that takes a predicate and a binary tree, and returns a new binary tree containing only the elements of the original tree that satisfy the predicate.
Example:
t1 = BNode 1 (bleaf 2) (BNode 3 (bleaf 4) (bleaf 5))
btfilter (>3) t1 -- Output: <4 5>
Solution:
btfilter :: (a -> Bool) -> BTree a -> BTree a
btfilter _ BEmpty = BEmpty
btfilter pred (BNode x left right)
| pred x = BNode x (btfilter pred left) (btfilter pred right)
| otherwise = merge (btfilter pred left) (btfilter pred right)
where
-- Merge two binary trees
merge :: BTree a -> BTree a -> BTree a
merge BEmpty t = t
merge t BEmpty = t
merge t1 (BNode x left right) = BNode x (merge t1 left) right
Write a function btconcat
that takes two binary trees and returns a binary tree containing the elements of the binary trees.
Example:
t1 = BNode 1 (bleaf 2) (BNode 3 (bleaf 4) (bleaf 5))
t2 = BNode 6 (bleaf 7) (bleaf 8)
btconcat t1 t2
-- Output: [ 6 [ 7 [ 1 [ 2 ] [ 3 [ 4 ] [ 5 ] ] ] ] [ 8 ] ]
Solution:
btconcat :: BTree a -> BTree a -> BTree a
btconcat BEmpty t = t
btconcat t BEmpty = t
btconcat t1 (BNode x left right) = BNode x (btconcat t1 left) right
Write a function btconcatmap
that takes a function and a list of binary trees, and returns a tree concatenation of the results of applying the function to the elements of each binary trees.
Example:
t1 = BNode 1 (bleaf 2) (BNode 3 (bleaf 4) (bleaf 5))
t2 = BNode 6 (bleaf 7) (bleaf 8)
btconcatmap (*2) [t1,t2] -- Output: [2[4][6[8][10[12[14][16]]]]]
Solution:
btconcatmap :: (a -> b) -> [BTree a] -> BTree b
btconcatmap f [] = BEmpty
btconcatmap f treelist = bmap f (foldr bconcat BEmpty treelist)
where
bconcat BEmpty tree = tree
bconcat tree BEmpty = tree
bconcat (BNode el left right) tree = BNode el left (bconcat right tree)
Write an instance of the Functor class for the binary tree data type.
Example:
t1 = BNode 1 (bleaf 2) (BNode 3 (bleaf 4) (bleaf 5))
fmap (*2) t1 -- Output: [ 4 2 8 4 10 ]
Solution:
instance Functor BTree where
fmap = btmap
Write an instance of the Foldable class for the binary tree data type.
Example:
t1 = BNode 1 (bleaf 2) (BNode 3 (bleaf 4) (bleaf 5))
foldr (+) 0 t1 -- Output: 15
Solution:
btfold :: (a -> b -> b) -> BTree a -> b -> b
btfold _ BEmpty acc = acc
btfold f (BNode v left right) i = f v (btfold f left (btfold f right i))
instance Foldable BTree where
foldr f acc tree = btfold f tree acc
Write an instance of the Applicative class for the binary tree data type.
Example:
btFuncs = BNode (+1) (bleaf (*2)) (bleaf (`div` 2))
btValues = BNode 1 (bleaf 2) (bleaf 3)
result = btFuncs <*> btValues
-- [ 0 [ 1 [ 2 [ 4 [ 2 [ 3 ] [ 4 ] ] ] [ 6 ] ] ] [ 1 ] ]
A possible solution:
instance Applicative BTree where
pure = bleaf
ftree <*> tree = foldr btconcat BEmpty (fmap (\ f -> fmap f tree) ftree)
Since the ftree
contains the functions to apply and has more than one node (function), we can apply each function to tree
and then concatenate the results.
Visual representation of the computation:
First each function is applied to the tree:
2 2 0
/ \ / \ / \ => then the results are concatenated:
3 3 4 6 1 1
2
/ \
3 4
\
0
/ \
1 1
\
2
/ \
4 6
Solutions to these exercises.
Given the data type ZL
for a ZipList, implement the Show
instance for ZL
.
Example:
l1 = ZCons 1 (ZCons 2 (ZCons 3 ZEmpty))
show l1 -- Output: "{1,2,3}"
Solution:
data ZL a = ZEmpty | ZCons a (ZL a) deriving Eq
instance Show a => Show (ZL a) where
show ZEmpty = "{}"
show (ZCons x ZEmpty) = "{" ++ show x ++ "}"
show (ZCons x xs) = "{" ++ show x ++ "," ++ (drop 1 (show xs))
The drop 1
function in Haskell is used to remove the first element from a list or a string.
Alternative solution without drop
:
instance Show a => Show (ZL a) where
show ZEmpty = "{}"
show ziplist = "{" ++ show' ziplist ++ "}"
where
show' ZEmpty = ""
show' (ZCons x ZEmpty) = show x
show' (ZCons x xs) = show x ++ "," ++ show' xs
Implement a function toZipList
that converts a list to a ZipList.
Example:
l2 = toZipList [1,3..5]
show l2 -- Output: "{1,3,5}"
Solution:
toZipList :: [a] -> ZL a
toZipList [] = ZEmpty
toZipList (x:xs) = ZCons x (toZipList xs)
Alternative solution:
toZipList :: [a] -> ZL a
toZipList = foldr ZCons ZEmpty
Implement the Functor
instance for ZL
.
Example:
l3 = fmap (*2) l1
show l3 -- Output: "{2,4,6}"
Solution:
instance Functor ZL where
fmap f ZEmpty = ZEmpty
fmap f (ZCons x xs) = ZCons (f x) (fmap f xs)
Implement the Foldable
instance for ZL
.
Example:
sum l1 -- Output: 6
Solution:
instance Foldable ZL where
foldr f z ZEmpty = z
foldr f z (ZCons x xs) = f x (foldr f z xs)
Implement the Applicative
instance for ZL
.
Example:
l4 = ZCons (* 2) (pure (+ 1)) <*> l1
show l4 -- Output: "{2,4,6,2,3,4}"
Solution:
zlconcat ZEmpty z = z
zlconcat z ZEmpty = z
zlconcat (ZCons x xs) l = ZCons x (zlconcat xs l)
instance Applicative ZL where
pure x = ZCons x ZEmpty
ZEmpty <*> _ = ZEmpty
zlfun <*> zlval = foldr zlconcat ZEmpty $ fmap (`fmap` zlval) zlfun
-- same as:
-- zlfun <*> zlval = foldr zlconcat ZEmpty (fmap (\f -> fmap f zlval) zlfun)
Solutions to these exercises.
The Logger
object enables logging (strings) alongside computations.
It can be defined as follows:
type Log = [String]
data Logger a = Logger a Log
instance (Eq a) => Eq (Logger a) where
(Logger x _) == (Logger y _) = x == y
instance (Show a) => Show (Logger a) where
show (Logger d l) = show d
++ "\nLog:" ++
foldr (\line acc-> "\n\t" ++ line ++ acc) "" l
instance Functor Logger where
fmap f (Logger x l) = Logger (f x) l
Implement the Applicative
instance of Logger
.
Example:
logger1 = Logger (+1) ["Log1: +1"]
logger2 = Logger 7 ["Log2: 7"]
logger3 = Logger (+2) ["Log3: +2"]
logger3 <*> (logger1 <*> logger2) -- output:
-- 10
-- Log:
-- Log3: +2
-- Log1: +1
-- Log2: 7
Solution:
instance Applicative Logger where
pure x = Logger x []
(Logger fun xs) <*> (Logger val ys) = Logger (fun val) (xs ++ ys)
Implement the Monad
instance of Logger
.
Example:
main :: IO ()
main = do
let logger1 = Logger 3 ["Initialized logger1 with 3"]
logger2 = Logger 4 ["Initialized logger2 with 4"]
addLog x y = Logger (x + y) ["Added " ++ show x ++ " and " ++ show y]
print $ logger1 >>= (\x -> addLog x 5) >>= (\x -> addLog x 10)
-- 18
-- Log:
-- Initialized logger1 with 3
-- Added 3 and 5
-- Added 8 and 10
Solution:
instance Monad Logger where
(Logger x l) >>= f =
let Logger x' l' = f x
in Logger x' (l ++ l')
The >>=
operator takes a Logger
value and a function that returns a Logger
value. It applies the function to the data part of the Logger
, and concatenates the logs of the original Logger
and the new Logger
obtained from the function:
(Logger x l) >>= f =
: This line defines the bind operation (>>=)
for the Logger
monad. The operation takes a Logger
value and a function f
that returns a Logger
value. The Logger
value is represented as (Logger x l)
, where x
is the data part and l
is the log messages.let Logger x' l' = f x
: This line applies the function f
to the data part x
of the Logger
value, and binds the result to (Logger x' l')
. Here, x'
is the data part and l'
is the log messages of the new Logger
value obtained from the function f
.in Logger x' (l ++ l')
: This line constructs a new Logger
value with the data part x'
and the log messages l ++ l'
. The log messages l
of the original Logger
and the log messages l'
of the new Logger
obtained from the function f
are concatenated using the list concatenation operator (++)
.(>>=)
for the Logger
monad allows chaining computations while accumulating log messages. When a Logger
value is bound to a function that returns a Logger
value, the function is applied to the data part of the Logger
, and the logs of the original Logger
and the new Logger
obtained from the function are concatenated. This is how the Logger
monad keeps track of log messages along with computations.Given the BTree
data object, create the bleafM
function that creates a leaf and logs the operation.
data BTree a = BEmpty | BNode a (BTree a) (BTree a) deriving Eq
bleaf x = BNode x BEmpty BEmpty
instance Show a => Show (BTree a) where
show BEmpty = ""
show (BNode v x y) = "<" ++ show x ++ " " ++ show v ++ " " ++ show y ++ ">"
t1 = BNode 1 (bleaf 2) (BNode 3 (bleaf 4) (bleaf 5))
t2 = BNode 2 (BNode 2 (bleaf 3) (bleaf 2)) (bleaf 5)
Example:
bleafM 5 -- output:
-- < 5 >
-- Log:
-- Created leaf 5
A solution that does not leverage the bind operation:
bleafM :: Show a => a -> Logger (BTree a)
bleafM el = Logger (bleaf el) ["Created leaf " ++ show el]
Alternative solution using the bind >>=
:
putLog :: String -> Logger ()
putLog s = Logger () [s]
bleafM x =
putLog ("Created leaf " ++ show x) >>= \_ ->
return (bleaf x)
The previous solution using the do
notation:
putLog :: String -> Logger ()
putLog s = Logger () [s]
bleafM x = do
putLog $ "Created leaf " ++ show x
return $ bleaf x
Changing from do
notation to >>=
, this is equivalent to:
bleafM :: Show a => a -> Logger (Tree a)
bleafM x =
putLog ("Created leaf " ++ show x) >>= \_ ->
return (bleaf x)
putLog ("Created leaf " ++ show x)
is the first monadic action that logs the message "Created leaf " followed by the string representation of x
. This returns Logger () [String]
.>>=
is used to chain the result of the first monadic action to the next. Since putLog
returns Logger () [String]
, the bind operator receives Logger
and ignores the unit value ()
with \_ ->
.return (bleaf x)
is the second monadic action that returns the Logger (Tree a)
containing the newly created leaf. The default implementation of return
in a Monad
can be derived from the Applicative
's pure
.Actually, the default definition of >>
in the Monad
class is given by:
(>>) :: Monad m => m a -> m b -> m b
a >> b = a >>= \_ -> b
In this case we can use the >>
("then") operator that discards the unit value ()
:
bleafM x =
putLog ("Created leaf " ++ show x) >>
return (bleaf x)
When we use the >>
operator in the context of the Logger
monad, we're discarding the value of the first monadic action but not its side effects. In the case of the Logger
monad, the side effect is appending the log message.
()
Implement a function treeReplaceM
that replaces a value in a binary tree with another value and logs the replacement.
Example:
let tree = BNode 1 (bleaf 1) (BNode 1 (bleaf 1) (bleaf 1))
print $ treeReplaceM tree 1 2
-- << 2 > 2 << 2 > 2 < 2 >>>
-- Log:
-- replaced 1 with 2
-- replaced 1 with 2
-- replaced 1 with 2
-- replaced 1 with 2
-- replaced 1 with 2
Solution:
-- Define the tree replacement function
treeReplaceM :: (Eq a, Show a) => BTree a -> a -> a -> Logger (BTree a)
treeReplaceM BEmpty _ _ = return BEmpty
treeReplaceM (BNode v l r) x y = do
newl <- treeReplaceM l x y
newr <- treeReplaceM r x y
if v == x then do
putLog $ "replaced " ++ show x ++ " with " ++ show y
return $ BNode y newl newr
else
return $ BNode v newl newr
The <-
operator in Haskell's do
notation is used to extract the result from a monadic action and bind it to a variable. This allows you to work with the value inside the monad within the do
block.
The snippet above can be translate in terms of >>=
:
treeReplaceM :: (Eq a, Show a) => BTree a -> a -> a -> Logger (BTree a)
treeReplaceM BEmpty _ _ = return BEmpty
treeReplaceM (BNode v l r) x y =
treeReplaceM l x y >>= \newl ->
treeReplaceM r x y >>= \newr ->
if v == x then
putLog ("replaced " ++ show x ++ " with " ++ show y) >>= \_ ->
return (BNode y newl newr)
else
return (BNode v newl newr)
Implement the buildTreeM
function that constructs a binary tree where each node's value is half of its parent node's value. The creation of each node should be logged.
Example:
print $ buildTreeM 3
-- <<< 0 > 1 < 0 >> 3 << 0 > 1 < 0 >>>
-- Log:
-- Added node 3
-- Added node 1
-- Created leaf 0
-- Created leaf 0
-- Added node 1
-- Created leaf 0
-- Created leaf 0
Solution:
buildTreeM :: Int -> Logger (BTree Int)
buildTreeM 0 = bleafM 0
buildTreeM x = do
putLog $ "Added node " ++ show x
l <- buildTreeM (x `div` 2)
r <- buildTreeM (x `div` 2)
return $ BNode x l r
This function can be translated to use the >>=
operator and pure
instead of return
as follows:
buildTreeM x =
putLog ("Added node " ++ show x) >>=
\_ -> buildTreeM (x `div` 2) >>=
\l -> buildTreeM (x `div` 2) >>=
\r -> pure (BNode x l r)
Now, let's break down this code:
putLog $ "Added node " ++ show x
is an I/O action that logs a message. The $
operator is used for function application, it's essentially replacing parentheses. So, putLog $ "Added node " ++ show x
is equivalent to putLog ("Added node " ++ show x)
. The putLog
function takes a String as an argument and returns an I/O action.
>>=
is the bind operator. It takes a monadic value (in this case, the I/O action from putLog
) and a function that takes a normal value and returns a monadic value. It feeds the result of the I/O action into the function. In this case, the function is a lambda function \_ -> ...
that ignores its input (since putLog
doesn't produce a meaningful result value) and creates further monadic computations.
buildTreeM (x
div 2)
is a recursive call to the function itself, with x
divided by 2. This is also a monadic action (it builds a part of the tree and possibly logs more messages). It's used twice, once for l
and once for r
, creating the left and right subtrees.
The final part, \r -> pure (BNode x l r)
, uses the pure
function to lift a normal value into the monad. It constructs a BNode
with the original x
and the subtrees l
and r
built by the recursive calls. The pure
function is essentially the same as return
in this context, but it's more general because it works in any Applicative functor, not just in Monads.
Solutions to these exercises.
Create a data type LolStream a
that consists of an integer and a list of elements of type a
. Then define a function isPeriodic
that takes a LolStream a
as an argument and checks if the stream is periodic. A stream is periodic if the integer parameter of the LolStream
is greater than 0. The parameter n
is the length of the periodic part of the stream.
Example:
let stream = LolStream 5 [1, 2, 3, 4, 5]
print $ isPeriodic stream
-- True
Solution:
data LolStream a = LolStream Int [a]
isPeriodic :: LolStream a -> Bool
isPeriodic (LolStream n _) = n > 0
Implement the destream
function. This function should take a LolStream
and return a list of elements. If the LolStream
is not periodic, it should return the entire list. Otherwise, it should return the first n
elements of the list.
Example:
let stream = LolStream 5 $ cycle [1, 2, 3, 4, 5]
print $ destream $ stream
Solution:
destream :: LolStream a -> [a]
destream (LolStream n l) = if n <= 0 then l else take n l
Implement the Show
instance for the LolStream
data type. The Show
instance should represent the LolStream
in a readable format.
let stream = LolStream 5 [1, 2, 3, 4, 5]
print stream
-- LolStream[1,2,3,4,5]
Solution:
instance (Show a) => Show (LolStream a) where
show l | not (isPeriodic l) = "LolStream[...]"
show lol@(LolStream n l) = "LolStream" ++ show (destream lol)
@
syntax for pattern matching