Lab 8: Higher-Order Patterns for Processing Lists - Haskell, Lab Reports of Computer Science

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

Pre 2010

Uploaded on 08/09/2009

koofers-user-zhc
koofers-user-zhc 🇺🇸

10 documents

1 / 3

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
Lab 8: Higher-Order Patterns for Processing Lists
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.
1 Mapping (Applying to All)
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:
CIS 252 (Spring 2009) Introduction to Computer Science Page 1
pf3

Partial preview of the text

Download Lab 8: Higher-Order Patterns for Processing Lists - Haskell and more Lab Reports Computer Science in PDF only on Docsity!

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.

1 Mapping (Applying to All)

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

2 Filtering (Element Selection)

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

3 Your Problems

  1. Consider the following function prependAll:

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.

  1. Consider the following function getConsonants:

getConsonants :: String -> String getConsonants [] = [] getConsonants (c:cs) | not (isVowel c) = c: getConsonants cs | otherwise = getConsonants cs where isVowel c = (elem (toLower c) "aeiou")