

Study with the several resources on Docsity
Earn points by helping other students or get them with a premium plan
Prepare for your exams
Study with the several resources on Docsity
Earn points to download
Earn points by helping other students or get them with a premium plan
A lab exercise from a haskell programming course. It introduces the higher-order functions map and filter, which allow for functional programming on lists. The lab covers the concepts of mapping (applying a function to all elements in a list) and filtering (selecting specific elements based on a condition). Examples and exercises to help students understand these concepts.
Typology: Lab Reports
1 / 3
This page cannot be seen from the preview
Don't miss anything!


Overview This lab will give you some practice with Haskell’s higher-order func- tions map and filter. These generalize some idioms for list processing that we’ve already seen. Caveat: While parts of this lab may look familiar (see Lab 5), don’t be fooled. This lab covers new material, which is discussed in Chapter 9 of the textbook.
Lab 5 focused on list comprehensions, which provide a mechanism for defining new lists out of old lists. For example, the following function takes a list of integers and doubles all of the elements in it:
doubleAll :: [Int] -> [Int] doubleAll lst = [ 2*x | x <- lst ]
This comprehension includes a generator x <- lst and a transformation 2*x. Because the comprehension doesn’t include any tests to filter out elements, the resulting list will always contain the same number of elements as the initial list. Thus, for example, doubleAll [1,3,5,7,10] will return a five-element list.
We could write doubleAll using explicit recursion, as follows:
doubleAll2 [] = [] doubleAll2 (x:xs) = 2*x : doubleAll2 xs
The equation for the empty list is straightforward: doubling every element of an empty list should result in another empty list. The second equation handles nonempty lists and is explicit about how the new list is generated: we transform the head of the list using the same transformation as in the list comprehension (2*x), and we tack it on to the front of the list obtained by transforming the rest of the list (via the recursive call). Each recursive call has the effect of generat- ing the next element to be operated on; thus the recursive calls—along with the binding of the function arguments to the pattern (x:xs)—play the role of the generator x <- lst.
Yet another way to write the function doubleAll is to use the built-in (and higher order!) function map, as follows:
doubleAll3 lst = map double lst where double x = 2*x
Here, we define a function double that multiplies its argument by 2. To apply double across the entire list, we use the function map. Finally, note that we didn’t have to give a name to the “multiply by two” function. Instead, we could have used an anonymous function, as follows:
doubleAll4 lst = map (\x -> 2*x) lst
As another example, consider the following function (make sure you understand what it does by trying it out on some test data):
addPairs :: [(Int,Int)] -> [[Int]] addPairs pairList = [[m+n] | (m,n) <- pairList]
We can rewrite addPairs using explicit recursion, as follows:
addPairs2 [] = [] addPairs2 ((m,n):rest) = [m+n] : addPairs2 rest
Note that, once again, the recursive definition uses exactly the same transfor- mation as the list-comprehension version, and tacks (using the : operator) the first-element result onto the result of the recursive call. We can use map to simplify our work: here, we want a function that takes a single pair and listifies the sum of its elements. Thus, we can use either of the following function definitions:
addPairs3 lst = map listify lst where listify (x,y) = [x+y]
addPairs4 lst = map (\ (x,y) -> [x+y]) lst
Finally, notice that sometimes you’ll need to pass around an extra argument, if the function itself takes more than one argument. For example, consider the following generalization of doubleAll:
multAll :: Int -> [Int] -> [Int] multAll m lst = [ m*x | x <- lst]
The explicit-recursion version of this function must pass around the argument m on each call:
multAll2 m [] = [] multAll2 m (x:xs) = m*x : multAll m xs
Once again, we can also write an explicit-map version of the code:
multAll3 m lst = map (\x -> m*x) lst
We’ve already seen that we can add additional tests to a list comprehension, with the result of filtering out some of the elements of the original list. For example, the following function takes a list of integers and returns a list containing only those elements that are less than 10 (it drops—or filters out—the rest):
lessThanTen :: [Int] -> [Int] lessThanTen = [ x | x <- lst, x < 10]
We can write lessThanTen using explicit recursion, as follows:
lessThanTen2 [] = [] lessThanTen2 (x:xs) | x < 10 = x : lessThanTen2 xs | otherwise = lessThanTen2 xs
Notice that recursive calls still provide the mechanism for “marching down” the list; at each point, we look at the first element of the list to determine whether or not to include it in the final result.
However, this function can also be written using Haskell’s built-in filter func- tion, which takes a predicate (i.e., a function that returns a Bool type) and a list, and returns a list containing those elements that satisfy the predicate. Specifi- cally, we can write:
lessThanTen3 lst = filter (\ x -> x< 10) lst
Similarly, consider writing a function remove, which could be written using a list comprehension as follows:
remove :: Char -> String -> String remove ch str = [ x | x <- str, x /= ch]
This function can also be written using explicit recursion, as follows:
remove ch [] = [] remove ch (c:cs) | ch /= c = c : remove ch cs | otherwise = remove ch cs
Using filter, we can write remove as follows:
remove ch str = filter (\c -> ch /= c) str
prependAll :: String -> [String] -> [String] prependAll s [] = [] prependAll s (c:cs) = (c++s): prependAll s cs
Note that (prependAll s strs) has the effect of prepending each string in strs to the string s. For example, (prependAll "st" ["ang", "be", "", "lo"]) returns ["angst","best","st","lost"]. Fill in the blank below to write a version of prependAll using map:
prependAll s strs = map ___________________________ strs
You may fill the blank in with either a helper function or with an anonymous function.
getConsonants :: String -> String getConsonants [] = [] getConsonants (c:cs) | not (isVowel c) = c: getConsonants cs | otherwise = getConsonants cs where isVowel c = (elem (toLower c) "aeiou")