Chapter 9: Lists¶
9.1 Lists¶
In this chapter, we will:
Explain the list datatype and how to pattern match on lists.
Practice many standard library functions for operating on lists.
Learn about the underlying representations of lists.
See what that representation means for their evaluation.
And do a whole bunch of exercises!
9.2 The list datatype¶
--# The binary infix data constructor (:), used to
--# construct nested cells of a list, pronounced "cons".
--# Cons is right associative, and has a precedence of 5,
--# which is lower than the default of precedence of 9
--# for regular left associative prefix functions.
--# vvvvvvv
data [] a = [] | a : [a]
--# ^^
--# The empty list data constructor, [], pronounced "nill".
--#
--# Since the [] type constructor only takes one type argument,
--# a, lists must be homogenous in Haskell.
--#
--# Because of non-strict evaluation, lists can be infinite, and
--# are often used similarly to iterators generated by range()
--# in python.
instance Eq a => Eq [a]
instance Ord a => Ord [a]
instance Show a => Show [a]
instance Read a => Read [a]
instance Semigroup [a]
instance Monoid [a] --# Depends on Semigroup.
instance Foldable [] --# (foldMap) depends on Monoid.
instance Functor []
instance Traversable [] --# Depends on Functor and Foldable.
instance Applicative [] --# Depends on Functor.
instance Monad [] --# Depends on Applicative.
instance MonadFail [] --# Depends on Monad.
9.3 Pattern matching on lists¶
9.3.1 Using Maybe¶
9.4 List’s syntactic sugar¶
In both Lisp and Haskell, lists are implemented as linked records with two fields - one field contains the elements value, and the other field contains a link to the next record or Nil (end-of-list). These records are called cons cells in lisp.
More generally, for other data structures, linked records are instead called nodes. Nodes may have many fields, including the possibility of multiple links to other nodes. Data may be arranged into arbitrary structures this way.
The spine refers to the entire succession of nested cons cells that comprise a list. (Or linked nodes that comprise some other data structure.) The field of a cons cell that contains the value is not considered part of the spine.
9.5 Using ranges to construct lists¶
9.5.1 Exercise: EnumFromTo¶
Write your own enumFromTo
for the types provided. Do not use range syntax to do so. It should
return the same results as if you did [start..stop]
. Replace the undefined
with your own
definition.
eftBool :: Bool -> Bool -> [Bool]
eftBool = undefined
eftOrd :: Ordering -> Ordering -> [Ordering]
eftOrd = undefined
eftInt :: Int -> Int -> [Int]
eftInt = undefined
eftChar :: Char -> Char -> [Char]
eftChar = undefined
Here is my attempt. To check it, navigate to exercises/9.5.1_-_enumfromto.rst.d/eft
and run
stack test
.
module Lib where
eftBool :: Bool -> Bool -> [Bool]
eftBool True False = []
eftBool True True = [True]
eftBool False True = [False,True]
eftBool False False = [False]
eft :: (Ord t, Enum t) => t -> t -> [t]
eft from to | from > to = []
| from == to = [from]
| from < to = from : eft (succ from) to
eftOrd :: Ordering -> Ordering -> [Ordering]
eftOrd from to = eft from to
eftInt :: Int -> Int -> [Int]
eftInt from to = eft from to
eftChar :: Char -> Char -> [Char]
eftChar from to = eft from to
Here are the tests, for your perusal.
import Test.Hspec
import Test.QuickCheck
import Lib
main :: IO ()
main = hspec $ do
describe "eftBool" $ do
it "True False ==> []" $ do
eftBool True False `shouldBe` []
eftBool True False `shouldBe` enumFromTo True False
it "True True ==> [True]" $ do
eftBool True True `shouldBe` [True]
eftBool True True `shouldBe` enumFromTo True True
it "False True ==> [False,True]" $ do
eftBool False True `shouldBe` [False,True]
eftBool False True `shouldBe` enumFromTo False True
it "False False ==> [False]" $ do
eftBool False False `shouldBe` [False]
eftBool False False `shouldBe` enumFromTo False False
describe "eftOrd" $ do
it "LT LT ==> [LT]" $ do
eftOrd LT LT `shouldBe` [LT]
eftOrd LT LT `shouldBe` enumFromTo LT LT
it "LT EQ ==> [LT,EQ]" $ do
eftOrd LT EQ `shouldBe` [LT,EQ]
eftOrd LT EQ `shouldBe` enumFromTo LT EQ
it "LT GT ==> [LT,EQ,GT]" $ do
eftOrd LT GT `shouldBe` [LT,EQ,GT]
eftOrd LT GT `shouldBe` enumFromTo LT GT
describe "eft" $ do
it "eft will return the same result as enuFromTo" $
property (\x y -> enumFromTo x y == eft x (y :: Int))
9.6 Extracting portions of lists¶
9.6.1 Exercises: Thy Fearful Symmetry¶
Using
takeWhile
anddropWhile
, write a function that splits a string on spaces to return a list of words.Next, write a function that takes a string and splits it into lines, breaking on the newline character
'\n'
.Try writing a new function that parameterizes the character you’re breaking the string on. Rewrite
myWords
andmyLines
to use it.Here is the answer to all three questions:
module Lib where myWords :: String -> [String] myWords = mySplit ' ' myLines :: String -> [String] myLines = mySplit '\n' mySplit :: Char -> String -> [String] mySplit _ "" = [] mySplit ch str = takeWhile (/= ch) str : mySplit ch (drop 1 $ dropWhile (/= ch) str)
Some tests:
Navigate to
exercises/9.6.1_-_thy_fearful_symmetry.rst.d/thy-fearful-symmetry/
and runstack test
for proof it works. (I did not use themain
function in the textbook.)
9.7 List comprehensions¶
9.7.1 Adding predicates¶
9.7.2 Exercises: Comprehend thy lists¶
Take a look at the following functions, determine what
you think the output lists will be, and then run them
in your REPL to verify. (Note that you will need the
mySqr
list from above in scope to do this.)
Here is mySqr
, again, for convenience:
mySqr = [x^2 | x <- [1..10]]
[x | x <- mySqr, rem x 2 == 0]
[(x,y) | x <- mySqr, y <- mySqr, x < 50, y > 50]
take 5 [(x,y) | x <- mySqr, y <- mySqr, x < 50, y > 50]
I’ve tested these out in a terminal session. My guesses are left as comments in it.
There is also a stack project under
exercises/9.7.2_-_compehend_thy_lists.rst.d/
that includes tests you can run by navigating to
that directory and then running stack test
.
9.7.3 List comprehensions with strings¶
9.7.4 Exercises: Square Cube¶
Given the following:
mySqr = [x^2 | x <- [1..5]]
myCube = [x^3 | x <- [1..5]]
First write an expression that will make tuples of the outputs of
mySqr
andmyCube
.one = [ (x,y) | x <- mySqr, y <- myCube]
Now alter that expression so that it only uses the x and y values that are less than 50.
two = [ (x,y) | (x,y) <- one, x < 50 && y < 50 ]
Apply another function to that list comprehension to determine how many tuples inhabit your output list.
Navigate to
exercises/9.7.4_-_square_cube.rst.d/square-cube/
and runstack test
to verify.
9.8 Spines and nonstrict evaluation¶
The spine is the structure of a collection type that arranges values in some order.
The spine is the connective structure that links a
composite data-structure together. In the case of a
list, it’s the succession of nested cons data
constructors (and the final empty list data
constructor), like so: _ : _ : _ : []
.
Lists aren’t constructed until they’re consumed. Until a value is consumed, there is a series of placeholders as a blueprint of the lists that can be constructed when it’s needed.
Also, functions can traverse the spine of a list without forcing evaluation of the values within that list.
Another way of phrasing this is: You can evaluate the cons data constructors in a spine without forcing evaluation of the arguments to those constructors.
Evaluation of the spine proceeds down the spine. However, constructing the list (when that is necessary) proceeds up the spine.
9.8.1 Using GHCi’s :sprint command¶
9.8.2 Spines are evaluated independently of values¶
Values in Haskell get reduced to weak head normal form by default.
Weak head normal form means the expression is only evaluated as far as is necessary to reach a data constructor or a lambda awaiting an argument.
Also, since Haskell uses an outermost reduction strategy, the subexpressions within the lambda or data constructor don’t need to be evaluated.
To determine if something is in WHNF we only have to look at the outermost part of the expression. If it’s a data constructor or lambda, it’s in WHNF. If it’s a function application, it’s not.
Evaluation of the list goes “down the spine” (left to right). Construction of the lists goes “up the spine” (right to left).
Functions that are spine strict can force complete evaluation of the spine of a list even if they don’t force evaluation of each value.
9.8.3 Exercise: Bottom Madness¶
9.8.3.1 Will it blow up?¶
Will the following expressions return a value or be ⊥ ?
(NB: I had a hard time writing test for this, so I’ve
also included an expect script and asciinema recordings
under exercises/9.8.3*.rst.d/
as proof to myself
that I’ve done the work.)
[x^y | x <- [1..5], y <- [2, undefined]]
Prediction: The code should expand to a list like this. Until forced, though, the elements
(x^y)
remain unevaluated.[ (1 ^ 2), (2 ^ undefined) , (2 ^ 2), (2 ^ undefined) , (3 ^ 2), (3 ^ undefined) , (4 ^ 2), (4 ^ undefined) , (4 ^ 2), (4 ^ undefined) ]
Since
undefined
is forced by the(^)
operator, it should crash after getting as far as1 : _
in the evaluation process.Result:
·∾ [ x^y | x <- [1..5], y <- [2,undefined]] [1,*** Exception: Prelude.undefined CallStack (from HasCallStack): error, called at libraries/base/GHC/Err.hs:79:14 in base:GHC.Err undefined, called at <interactive>:2:30 in interactive:Ghci 1
take 1 $ [x^y | x <- [1..5], y <- [2, undefined]]
Prediction:
[1]
We’re only taking one element of that expanded expression, so the second element won’t be forced, meaning no exception is thrown.
Result:
·∾ take 1 $ [ x ^ y | x <- [1..5], y <- [2,undefined]] [1]
sum [1, undefined, 3]
Prediction: Sum forces evaluation of all elements in a list, in order to sum them, so this should throw an exception.
Result:
·∾ sum [1, undefined, 3] *** Exception: Prelude.undefined CallStack (from HasCallStack): error, called at libraries/base/GHC/Err.hs:79:14 in base:GHC.Err undefined, called at <interactive>:6:9 in interactive:Ghci2
length [1, 2, undefined]
Prediction: Because length counts the cons constructors, but does not force evaluation of the elements, it will return
3
.Result:
·∾ length [1,2,undefined] 3
length $ [1, 2, 3] ++ undefined
Prediction: Length will force the spine, and undefined is part of the spine due to
(++ undefined)
, so it will throw an exception.Result:
·∾ length $ [1,2,3] ++ undefined *** Exception: Prelude.undefined CallStack (from HasCallStack): error, called at libraries/base/GHC/Err.hs:79:14 in base:GHC.Err undefined, called at <interactive>:1:21 in interactive:Ghci1
take 1 $ filter even [1,2,3,undefined]
Prediction:
[2]
.Result:
·∾ take 1 $ filter even [1,2,3,undefined] [2]
take 1 $ filter even [1,3,undefined]
Prediction: Since we don’t encounter any even numbers before the
undefined
element the list will continue to be evaluated byfilter even
, throwing an exception when we get toundefined
.Result:
·∾ take 1 $ filter even [1,3,undefined] *** Exception: Prelude.undefined CallStack (from HasCallStack): error, called at libraries/base/GHC/Err.hs:79:14 in base:GHC.Err undefined, called at <interactive>:1:27 in interactive:Ghci1
take 1 $ filter odd [1,3,undefined]
Prediction: This should evaluate to
[1]
.Result:
·∾ take 1 $ filter odd [1,3,undefined] [1]
take 2 $ filter odd [1,3,undefined]
Prediction:
[1,3]
.Result:
·∾ take 2 $ filter odd [1,3,undefined] [1,3]
take 3 $ filter odd [1,3,undefined]
Prediction: Take requires three elements, so
undefined
will be reached and an exception will be thrown.Result:
·∾ take 3 $ filter odd [1,3,undefined] [1,3*** Exception: Prelude.undefined CallStack (from HasCallStack): error, called at libraries/base/GHC/Err.hs:79:14 in base:GHC.Err undefined, called at <interactive>:1:26 in interactive:Ghci1
9.8.3.2 Intermission: Is it in normal form?¶
For each expression below, determine whether it’s in:
Normal form, which implies weak head normal form.
Weak head normal form only.
Neither.
Remember that an expression cannot be in normal form or weak head normal form if the outermost part of the expression isn’t a data constructor. It can’t be in normal form if any part of the expression is unevaluated:
[1, 2, 3, 4, 5]
NF and WHNF.
1 : 2 : 3 : 4 : _
WHNF, but not NF. We reach the outermost data constructor, the rightmost cons.
enumFromTo 1 10
Neither. This is a fully applied function that has not been reduced. There is no data constructor here, yet, until further reduction happens.
length [1, 2, 3, 4, 5]
Neither. Fully applied, but not reduced.
sum (enumFromTo 1 10)
Neither. Same as above.
['a'..'m'] ++ ['n'..'z']
Neither? It’s fully applied, but not reduced, and it’s not a constructor or lambda awaiting an argument.
(_, 'b')
WHNF, but not NF. We only need the data constructor
(_,_)
to be evaluated for WHNF.
9.9 Transforming lists of values¶
Map doesn’t traverse the whole list and apply the function immediately. The function is applied to the values you force out of the list one by one.
Exercises: More Bottoms¶
Will the following expression return a value or be bottom?
one = take 1 $ map (+1) [undefined,2,3]
Because we’re forcing
map (+1)
on the first element, this will evaluate to bottom and throw an exception.Let’s find out:
·∾ take 1 $ map (+1) [undefined,2,3] [*** Exception: Prelude.undefined CallStack (from HasCallStack): error, called at libraries/base/GHC/Err.hs:80:14 in base:GHC.Err undefined, called at <interactive>:231:20 in interactive:Ghci40
Will the following expression return a value?
take 1 $ map (+1) [1,undefined,3]
The result should be
[2]
.Let’s find out:
·∾ take 1 $ map (+1) [1,undefined,3] [2]
Will the following expression return a value
take 2 $ map (+1) [1,undefined,3]
Since we’re taking two elements, this should attempt to apply (+1), and throw an exception.
Let’s test it:
·∾ take 2 $ map (+1) [1,undefined,3] [2,*** Exception: Prelude.undefined CallStack (from HasCallStack): error, called at libraries/base/GHC/Err.hs:80:14 in base:GHC.Err undefined, called at <interactive>:237:22 in interactive:Ghci41
Describe what this function does in English, then run it.
·∾ itIsMystery xs = map (\x -> x `elem` "aeiou") xs
This function checks for vowels. If an element of the list is a vowel, it’s replaced with True. Otherwise, it’s replaced with False. The result is a list of Boolean values.
Let’s test it!
·∾ itIsMystery "This sentence" {- This -} [ False, False, True , False {- ' ' -} , False {- sentence -} , False, True , False, False, True, False, False, True ]
(The results are formatted, but it did come from a real GHCi session.)
What will be the result of the following functions:
map (^2) [1..10]
This should expand to the expression
[1^2,2^2,3^2,4^2,5^2,6^2,7^2,8^2,9^2,10^2]
. When you press enter in GHCi to evaluatemap (^2) [1..10]
GHCi will runprint it
to display the output in the REPL session, forcing the expressions evaluation to[1,4,9,16,25,36,49,64,81,100]
. This is the same value as themySqr
list comprehension from previous exercises in this chapter.Time to test.
·∾ map (^2) [1..10] [1,4,9,16,25,36,49,64,81,100] ·∾ [x^2 | x <- [1..10]] [1,4,9,16,25,36,49,64,81,100]
map minimum [[1..10],[10..20],[20..30]]
The GHCi command
:doc minimum
says it returns the least element of a non-emptyFoldable
structure. So this should return[1,10,20]
, I think.Let’s find out:
·∾ map minimum [[1..10],[10..20],[20..30]] [1,10,20]
map sum [[1..5],[1..5],[1..5]]
This should evaluate to
[15,15,15]
.·∾ map sum [[1..5],[1..5],[1..5]] [15,15,15]
Back in … Wait, what? What are you asking me to do?
The function to modify, from figure 22 in section 9.9.
ite x = map (\x -> if x == 3 then (-x) else (x))
Write this function in terms of
Data.Bool (bool)
, instead of the if-then-else expression.Ok, here is the function rewrittten to use
bool
.·∾ eti = map (\x -> bool x (negate x) (x == 3)) ·∾ eti [1..10] [1,2,-3,4,5,6,7,8,9,10] ·∾ ite = map (\x -> if x == 3 then negate x else x) ·∾ ite [1..10] [1,2,-3,4,5,6,7,8,9,10] ·∾ ite [1..10] == eti [1..10] True
9.10 Filtering lists of values¶
filter :: (a -> Bool) -> [a] -> [a]
filter _ [] = []
filter pred (x:xs)
| pred x = x : filter pred xs
| otherwise = filter pred xs
9.10.1 Exercises: Filtering¶
Given the following example, from figure 5:
·∾ filter (\x -> x `elem` "aeiou") "abracadabra" "aaaaa" ·∾ [ x | x <- "abracadabra", x `elem` "aeiou" ] "aaaaa"
How might we write a filter function that will give us all the multiples of 3 out of a list from 1..30?
A quick attempt in GHCi:
·∾ filter (\x -> x `mod` 3 == 0) [1..30] [3,6,9,12,15,18,21,24,27,30]
Recalling what we learned about function composition, how could we compose the above function with the length function to tell us how many multiples of 3 there are between 1 and 30?
A quick attempt in GHCi:
·∾ length $ filter (\x -> x `mod` 3 == 0) [1..30] 10
Next, we’re going to work on removing all articles (“the”, “a”, and “an”) from sentences. You want to get to something that works like this:
·∾ myFilter "the brown dog was a goof" [ "brown" , "dog" , "was" , "goof" ]
You may recall that earlier in this chapter, we asked you to write a function that separates a string into a list of strings by separating them at spaces. That is a standard library function called words. You may consider starting this exercise by using words (or your own version, of course).
A super quick implementation in GHCi:
·∾ :{ ⋮ myFilter l = ⋮ filter (not . flip elem articles) w ⋮ where ⋮ w = words l ⋮ articles = ["the","a","an"] ⋮ :} ·∾ myFilter "the brown dog was a goof" ["brown","dog","was","goof"]
I’m in a rush today. I’ll have to make a stack project with a more complete solution for this later. Actually, nevermind, this is enough.
9.11 Zipping lists¶
9.11.1 Zipping exercises¶
Write your own version
zip
, and ensure it behaves the same as the original:zip :: [a] -> [b] -> [(a,b)] zip = undefined
Here is my attempt:
zip' :: [a] -> [b] -> [(a,b)] zip' [] _ = [] zip' _ [] = [] zip' (x:xs) (y:ys) = (x,y) : zip' xs ys
Do what you did for zip but now for
zipWith
:zipWith :: (a -> b -> c) -> [a] -> [b] -> [c] zipWith = undefined
Here is my attempt:
zipWith' :: (a -> b -> c) -> [a] -> [b] -> [c] zipWith' f [] _ = [] zipWith' f _ [] = [] zipWith' f (x:xs) (y:ys) = (f x y) : zipWith' f xs ys -- zipWith' f xs ys = [ f x y | x <- xs, y <- ys]
Rewrite your
zip
in terms of thezipWith
you wrote.Here is my attempt:
zip'Refactored :: [a] -> [b] -> [(a,b)] zip'Refactored = zipWith' (,)
9.12 Chapter Exercises¶
9.12.1 Data.Char¶
Query the types of
isUpper
andtoUpper
:·∾ :type isUpper isUpper :: Char -> Bool ·∾ :type toUpper toUpper :: Char -> Char
Write a function that filters for uppercase letters.
onlyUpper = filter isUpper
Write a function that capitalizes the first letter of a string.
capFirstLetter [] = [] capFirstLetter (x:xs) = toUpper x : xs
Make a recursive version that capitalizes all the letters.
{-# ANN capAll ("HLint: Ignore all" :: String) #-} capAll [] = [] capAll (x:xs) = toUpper x : capAll xs
Write a function that will capitalize the first letter of a string and return only that letter.
firstCapCh [] = error "cannot convert empty string to char" firstCapCh x = toUpper $ head x -- Another version, using Maybe to represent the -- possiblity of failure instead of execptions. -- firstCapChMaybe :: String -> Maybe Char -- firstCapChMaybe [] = Nothing -- firstCapChMaybe (x:_) = Just (toUpper x)
Now rewrite it as a composed function.
firstCapChPointfree = toUpper . head
9.12.2 Ciphers¶
Your goal in this exercise is to write a basic Caesar cipher that shifts rightward.
You want your shift to wrap back around to the beginning of the alphabet, so that if you have a rightward shift of three from ‘z’, you end up back at ‘c’ and not somewhere in the vast Unicode hinterlands.
You should include an unCaesar
function that will decipher your
text, as well. In a later chapter, we will test it.
Here is my attempt:
module Lib where
import Data.Char (ord, chr, toLower, toUpper, isUpper)
import Data.Maybe (fromJust)
import Data.List (elemIndex)
alpha = "abcdefghijklmnopqrstuvwxyz"
shift :: Int -> Char -> Char
shift n c
| toLower c `elem` alpha && isUpper c = toUpper (alpha !! newIndex)
| toLower c `elem` alpha = (alpha !! newIndex)
| otherwise = c
where
newIndex = (oldIndex + n) `mod` 26
oldIndex = fromJust (elemIndex (toLower c) alpha)
unshift :: Int -> Char -> Char
unshift n c = shift (-n) c
Here are some tests:
import Test.Hspec
import Lib
main = hspec $ do
describe "shift" $ do
it "zero" $ do
shift 0 'a' `shouldBe` 'a'
it "positive" $ do
shift 1 'a' `shouldBe` 'b'
it "positive from 'z'" $ do
shift 3 'z' `shouldBe` 'c'
it "negative" $ do
shift (-5) 'a' `shouldBe` 'v'
it "non alpha character" $ do
shift 8 '.' `shouldBe` '.'
it "uppercase character" $ do
shift 3 'A' `shouldBe` 'D'
it "w ==> x" $ do
shift 1 'w' `shouldBe` 'x'
it "sentence" $ do
map (shift 1) "Attack at dawn." `shouldBe` "Buubdl bu ebxo."
map (shift 2) "Attack at dawn." `shouldBe` "Cvvcem cv fcyp."
describe "unshift" $ do
it "1 a ==> z" $ do
unshift 1 'a' `shouldBe` 'z'
it "sentence" $ do
map (unshift 9) "Attack at dawn." `shouldBe` "Rkkrtb rk urne."
map (unshift 1) "Buubdl bu ebxo." `shouldBe` "Attack at dawn."
map (unshift 2) "Cvvcem cv fcyp." `shouldBe` "Attack at dawn."
9.12.3 Writing your own standard functions¶
Below are the outlines of some standard functions. The goal here is to write your own versions of these to gain a deeper understanding of recursion over lists and how to make functions flexible enough to accept a variety of inputs. You could figure out how to look up the answers, but you won’t do that, because you know you’d only be cheating yourself out of the knowledge. Right?
Let’s look at an example of what we’re after
here. The and
function takes a list of
Bool
values and returns True
if and
only if no values in the list are False.
Here’s how you might write your own version
of it:
-- direct recursion, not using (&&)
myAnd :: [Bool] -> Bool
myAnd [] = True
myAnd (x:xs) =
if x == False
then False
else myAnd xs
-- direct recursion, using (&&)
myAnd :: [Bool] -> Bool
myAnd [] = True
myAnd (x:xs) = x && myAnd xs
And now the fun begins:
myOr
returnsTrue
if anyBool
in the list isTrue
:myOr :: [Bool] -> Bool myOr = undefined
Here is my attempt:
myOr :: [Bool] -> Bool myOr [] = False myOr (x:xs) = x || myOr xs
myAny
returnsTrue
ifa -> Bool
applied to any of the values in the list returnsTrue
:myAny :: (a -> Bool) -> [a] -> Bool myAny = undefined
Example for validating
myAny
:Prelude> myAny even [1, 3, 5] False Prelude> myAny odd [1, 3, 5] True
Here is my attempt:
myAny :: (a -> Bool) -> [a] -> Bool myAny p [] = False myAny p (x:xs) = p x || myAny p xs
After you write the recursive
myElem
, write another version that usesany
. The built-in version ofelem
in GHC 7.10 and newer has a type that usesFoldable
instead of the list type, specifically.You can ignore that and write the concrete version that works only for lists:
myElem :: Eq a => a -> [a] -> Bool
Here’s an example of
myElem
’s use in GHCi:Prelude> myElem 1 [1..10] True Prelude> myElem 1 [2..10] False
Here is my attempt:
infix 4 `myElem` myElem :: Eq a => a -> [a] -> Bool myElem e [] = False myElem e (x:xs) = e == x || myElem e xs
Implement
myReverse
:Function stub:
myReverse :: [a] -> [a] myReverse = undefined
Example use:
Prelude> myReverse "blah" "halb" Prelude> myReverse [1..5] [5,4,3,2,1]
Here is my attempt:
myReverse :: [a] -> [a] myReverse [] = [] myReverse (x:xs) = myReverse xs ++ [x] -- Question 4, take two. myRev :: [a] -> [a] myRev l = go l [] where go [] new = new go (x:xs) new = go xs (x:new)
squish
flattens a list of lists into a list:squish :: [[a]] -> [a] squish = undefined
Here is my attempt:
squish :: [[a]] -> [a] squish [] = [] squish (x:xs) = x ++ squish xs
squishMap
maps a function over a list and concatenates the results.Function stub:
squishMap :: (a -> [b]) -> [a] -> [b] squishMap = undefined
Demonstration of use in GHCi:
Prelude> squishMap (\x -> [1, x, 3]) [2] [1,2,3] Prelude> squishMap (\x -> "WO "++[x]++" HOO ") "123" "WO 1 HOO WO 2 HOO WO 3 HOO "
Here is my attempt:
squishMap :: (a -> [b]) -> [a] -> [b] squishMap f [] = [] squishMap f l = (squish . map' f) l where map' g [] = [] map' g (x:xs) = g x : map' g xs
squishAgain
flattens a list of lists into a list. This time, re-use thesquishMap
function:squishAgain :: [[a]] -> [a] squishAgain = undefined
Here is my attempt:
squishAgain :: [[a]] -> [a] squishAgain = squishMap id
myMaximumBy
takes a comparison function and a list and returns the greatest element of the list based on the last value that the comparison returnsGT
for. If you importmaximumBy
fromData.List
, you’ll see that the type is:Foldable t => (a -> a -> Ordering) -> t a -> a
Rather than:
(a -> a -> Ordering) -> [a] -> a
Here is your starting point:
myMaximumBy :: (a -> a -> Ordering) -> [a] -> a myMaximumBy = undefined
Demonstration:
Prelude> xs = [1, 53, 9001, 10] Prelude> myMaximumBy compare xs 9001
Here is my attempt:
myMaximumBy :: (a -> a -> Ordering) -> [a] -> a myMaximumBy _ [] = error "myMaximumBy does not work on empty lists" myMaximumBy p (e:l) | all (\x -> x == GT || x == EQ) (map (e `p`) l) = e | otherwise = myMaximumBy p l
myMinimumBy
takes a comparison function and a list and returns the least element of the list based on the last value that the comparison returnsLT
for.Function stub:
myMinimumBy :: (a -> a -> Ordering) -> [a] -> a myMinimumBy = undefined
Demonstration:
Prelude> xs = [1, 53, 9001, 10] Prelude> myMinimumBy compare xs 1
Here is my attempt:
myMinimumBy :: (a -> a -> Ordering) -> [a] -> a myMinimumBy p [] = error "myMinimumBy only works on nonempty finite lists" myMinimumBy p (e:l) | all (\x -> x == LT || x == EQ) (map (e `p`) l) = e | otherwise = myMinimumBy p l
Using the
myMinimumBy
andmyMaximumBy
functions, write your own versions of maximum and minimum. If you have GHC 7.10 or newer, you’ll see a type constructor that wants aFoldable
instance instead of a list, as has been the case for many functions so far:myMaximum :: (Ord a) => [a] -> a myMaximum = undefined myMinimum :: (Ord a) => [a] -> a myMinimum = undefined
Here is my attempt:
myMaximum :: (Ord a) => [a] -> a myMaximum = myMaximumBy compare myMinimum :: (Ord a) => [a] -> a myMinimum = myMinimumBy compare