Now with lists and polymorphism in our toolbox, we can finally start to look at functional programming.
In Haskell a function is a value, just like a number or a list is.
Functions can be passed as parameters to other functions. Here’s a toy
example. The function
applyTo1 takes a function of type
applies it to the number
1, and returns the result.
applyTo1 :: (Int -> Int) -> Int applyTo1 f = f 1
Let’s define a simple function of type
Int->Int and see
addThree :: Int -> Int addThree x = x + 3
applyTo1 addThree ==> addThree 1 ==> 1 + 3 ==> 4
Let’s go back to the type annotation for
applyTo1 :: (Int -> Int) -> Int
The parentheses are needed because the type
Int -> Int -> Int would be
the type of a function taking two
Int arguments. More on this later.
Let’s look at a slightly more interesting example. This time we’ll
implement a polymorphic function
doTwice. Note how we can use it with
various types of values and functions.
doTwice :: (a -> a) -> a -> a doTwice f x = f (f x)
doTwice addThree 1 ==> addThree (addThree 1) ==> 7 doTwice tail "abcd" ==> tail (tail "abcd") ==> "cd"
makeCool :: String -> String makeCool str = "WOW " ++ str ++ "!"
doTwice makeCool "Haskell" ==> "WOW WOW Haskell!!"
Functional Programming on Lists
That was a bit boring. Luckily there are many useful list functions that take functions as arguments. By the way, functions that take functions as arguments (or return functions) are often called higher-order functions.
The most famous of these list-processing higher-order functions is
map. It gives you a new list by applying the given function to all
elements of a list.
map :: (a -> b) -> [a] -> [b]
map addThree [1,2,3] ==> [4,5,6]
The partner in crime for
filter. Instead of transforming all
elements of a list,
filter drops some elements of a list and keeps
others. In other words,
filter selects the elements from a list that
fulfill a condition.
filter :: (a -> Bool) -> [a] -> [a]
Here’s an example: selecting the positive elements from a list
positive :: Int -> Bool positive x = x>0
filter positive [0,1,-1,3,-3] ==> [1,3]
Note how both the type signatures of
polymorphism. They work on all kinds of lists. The type of
uses two type parameters! Here are some examples of type inference using
onlyPositive xs = filter positive xs mapBooleans f = map f [False,True]
Prelude> :t onlyPositive onlyPositive :: [Int] -> [Int] Prelude> :t mapBooleans mapBooleans :: (Bool -> b) -> [b] Prelude> :t mapBooleans not mapBooleans not :: [Bool]
One more thing: remember how constructors were just functions? That means you can pass them as arguments to other functions!
wrapJust xs = map Just xs
Prelude> :t wrapJust wrapJust :: [a] -> [Maybe a] Prelude> wrapJust [1,2,3] [Just 1,Just 2,Just 3]
Examples of Functional Programming on Lists
How many “palindrome numbers” are between
-- a predicate that checks if a string is a palindrome palindrome :: String -> Bool palindrome str = str == reverse str -- palindromes n takes all numbers from 1 to n, converts -- them to strings using show, and keeps only palindromes palindromes :: Int -> [String] palindromes n = filter palindrome (map show [1..n])
palindrome "1331" ==> True palindromes 150 ==> ["1","2","3","4","5","6","7","8","9", "11","22","33","44","55","66","77","88","99", "101","111","121","131","141"] length (palindromes 9999) ==> 198
How many words in a string start with “a”? This uses the function
words from the module
Data.List that splits a string into words.
countAWords :: String -> Int countAWords str = length (filter startsWithA (words str)) where startsWithA s = head s == 'a'
countAWords "does anyone want an apple?" ==> 3
Data.List returns the list of all suffixes
(“tails”) of a list. We can use
tails for many string processing
tasks. Here’s how
tails "echo" ==> ["echo","cho","ho","o",""]
Here’s an example where we find what characters come after a given
character in a string. First of all, we use
get all substrings of a certain length:
substringsOfLength :: Int -> String -> [String] substringsOfLength n string = map shorten (tails string) where shorten s = take n s
substringsOfLength 3 "hello" ==> ["hel","ell","llo","lo","o",""]
There’s some shorter substrings left at the end (can you see why?), but
they’re fine for our purposes right now. Now that we have
substringsOfLength, we can implement the function
whatFollows c k s
that finds all the occurrences of the character
c in the string
and outputs the
k letters that come after these occurrences.
whatFollows :: Char -> Int -> String -> [String] whatFollows c k string = map tail (filter match (substringsOfLength (k+1) string)) where match sub = take 1 sub == [c]
whatFollows 'a' 2 "abracadabra" ==> ["br","ca","da","br",""]
When using higher-order functions you can find yourself defining lots of
small helper functions, like
shorten in the previous
examples. This is a bit of a chore in the long run, but luckily
Haskell’s functions behave a bit weirdly…
Let’s start in GHCi:
Prelude> let add a b = a+b Prelude> add 1 5 6 Prelude> let addThree = add 3 Prelude> addThree 2 5
So, we’ve defined
add, a function of two arguments, and only given it
one argument. The result is not a type error but a new function. The new
function just stores (or remembers) the given argument, waits for
another argument, and then gives both to
Prelude> map addThree [1,2,3] [4,5,6] Prelude> map (add 3) [1,2,3] [4,5,6]
Here we can see that we don’t even need to give a name to the function
add 3. We can just use it anywhere where a function of one
argument is expected.
This is called partial application. All functions in Haskell behave like this. Let’s have a closer look. Here’s a function that takes many arguments.
between :: Integer -> Integer -> Integer -> Bool between lo high x = x < high && x > lo
Prelude> between 3 7 5 True Prelude> between 3 6 8 False
We can give
between less arguments and get back new functions, just
like we saw with
Prelude> (between 1 5) 2 True Prelude> let f = between 1 5 in f 2 True Prelude> map (between 1 3) [1,2,3] [False,True,False]
Look at the types of partially applying
between. They behave neatly,
with arguments disappearing one by one from the type as values are added
to the expression.
Prelude> :t between between :: Integer -> Integer -> Integer -> Bool Prelude> :t between 1 between 1 :: Integer -> Integer -> Bool Prelude> :t between 1 2 between 1 2 :: Integer -> Bool Prelude> :t between 1 2 3 between 1 2 3 :: Bool
Actually, when we write a type like
Integer -> Integer -> Integer -> Bool, it means
Integer -> (Integer -> (Integer -> Bool)). That is, a multi-argument
function is just a function that returns a function. Similarly, an
between 1 2 3 is the same as
((between 1) 2) 3, so
passing multiple arguments to a function happens via multiple
single-argument calls. Representing multi-argument functions like this
is called currying (after the logician Haskell Curry). Currying is
what makes partial application possible.
Here’s another example of using partial application with
map (drop 1) ["Hello","World!"] ==> ["ello","orld!"]
In addition to normal functions, partial application also works with operators. With operators you can choose whether you apply the left or the right argument. (Partially applied operators are also called sections or operator sections). Some examples:
Prelude> map (*2) [1,2,3] [2,4,6] Prelude> map (2*) [1,2,3] [2,4,6] Prelude> map (1/) [1,2,3,4,5] [1.0,0.5,0.3333333333333333,0.25,0.2]
Prefix and Infix Notations
Normal Haskell operators are applied with prefix notation, which is just a fancy way to say that the function name comes before the arguments. In contrast, operators are applied with infix notation – the name of the function comes between the arguments.
An infix operator can be converted into a prefix function by adding parentheses around it. For instance,
(+) 1 2 ==> 1 + 2 ==> 3
This is useful especially when an operator needs to be passed as an argument to another function.
As an example, the function
zipWith takes two lists, a binary
function, and joins the lists using the function. We can use
zipWith (+) to sum two lists, element-by-element:
Prelude> :t zipWith zipWith :: (a -> b -> c) -> [a] -> [b] -> [c] Prelude> zipWith (+) [0,2,5] [1,3,3] [1,5,8]
Without the ability to turn an operator into a function, we’d have to
use a helper function – such as
Note that omitting the parentheses leads into a type error:
Prelude> zipWith + [0,2,5,3] [1,3,3] <interactive>:1:11: error: • Couldn't match expected type ‘[Integer] -> (a -> b -> c) -> [a] -> [b] -> [c]’ with actual type ‘[Integer]’ • The function ‘[0, 2, 5, 3]’ is applied to one argument, but its type ‘[Integer]’ has none In the second argument of ‘(+)’, namely ‘[0, 2, 5, 3] [1, 3, 3]’ In the expression: zipWith + [0, 2, 5, 3] [1, 3, 3] • Relevant bindings include it :: (a -> b -> c) -> [a] -> [b] -> [c] (bound at <interactive>:1:1)
The reason for this weird-looking error is that GHCi got confused and
thought that we were somehow trying to add
[0,2,5,3] [1,3,3] together. Logically, it deduced that
must be a function since it’s being applied to
[1,3,3] (remember that
functions bind tighter than operators).
Unfortunately, error messages can sometimes be obscure, since the compiler cannot always know the “real” cause of the error (which is in this case was omitting the parentheses). Weird error messages are frustrating, but only the programmer knows what was the original intent behind the code.
Another nice feature of Haskell is the syntax for applying a binary function as if it was an infix operator, by surrounding it with backticks (`). For example:
6 `div` 2 ==> div 6 2 ==> 3 (+1) `map` [1,2,3] ==> map (+1) [1,2,3] ==> [2,3,4]
The last spanner we need in our functional programming toolbox is λ (lambda). Lambda expressions are anonymous functions. Consider a situation where you need a function only once, for example in an expression like
let big x = x>7 in filter big [1,10,100]
A lambda expression allows us to write this directly, without defining a
big) for the helper function:
filter (\x -> x>7) [1,10,100]
Here are some more examples in GHCi:
Prelude> (\x -> x*x) 3 9 Prelude> (\x -> reverse x == x) "ABBA" True Prelude> filter (\x -> reverse x == x) ["ABBA","ACDC","otto","lothar","anna"] ["ABBA","otto","anna"] Prelude> (\x y -> x^2+y^2) 2 3 -- multiple arguments 13
The Haskell syntax for lambdas is a bit surprising. The backslash
\) stands for the greek letter lambda (λ). The Haskell
\x -> x+1 is trying to mimic the mathematical notation λx.
x+1. Other languages use syntax like
lambda x: x+1 (Python).
Note! You never need to use a lambda expression. You can always
instead define the function normally using
By the way, lambda expressions are quite powerful constructs which have a deep theory of their own, known as Lambda calculus. Some even consider purely functional programming languages such as Haskell to be typed extensions of Lambda calculus with extra syntax.
The two most common operators in Haskell codebases are probably
$. They are useful when writing code that uses higher-order functions.
The first of these, the
. operator, is the function composition
operator. Here’s its type
(.) :: (b -> c) -> (a -> b) -> a -> c
And here’s what it does
(f.g) x ==> f (g x)
You can use function composition to build functions out of other functions, without mentioning any arguments. For example:
double x = 2*x quadruple = double . double -- computes 2*(2*x) == 4*x f = quadruple . (+1) -- computes 4*(x+1) g = (+1) . quadruple -- computes 4*x+1 third = head . tail . tail -- fetches the third element -- of a list
We can also reimplement
(.). Note how we can use
doTwice both as applied only to a function, or as applied to a
function and a value.
doTwice :: (a -> a) -> a -> a doTwice f = f . f
let ttail = doTwice tail in ttail [1,2,3,4] ==> [3,4] (doTwice tail) [1,2,3,4] ==> [3,4] doTwice tail [1,2,3,4] ==> [3,4]
Often function composition is not used when defining a new function, but instead to avoid defining a helper function. For instance, consider the difference between these two expressions:
let notEmpty x = not (null x) in filter notEmpty [[1,2,3],,] ==> [[1,2,3],]
filter (not . null) [[1,2,3],,] ==> [[1,2,3],]
The other operator,
$ is more subtle. Let’s look at its type.
($) :: (a -> b) -> a -> b
It takes a function of type
a -> b and a value of type
returns a value of type
b. In other words, it’s a function application
operator. The expression
f $ x is the same as
f x. This seems pretty
useless, but it means that the
$ operator can be used to eliminate
parentheses! These expressions are the same:
head (reverse "abcd") head $ reverse "abcd"
This isn’t that impressive when it’s used to eliminate one pair of
parentheses, but together
$ can eliminate lots of them! For
example, we can rewrite
reverse (map head (map reverse (["Haskell","pro"] ++ ["dodo","lyric"])))
(reverse . map head . map reverse) (["Haskell","pro"] ++ ["dodo","lyric"])
reverse . map head . map reverse $ ["Haskell","pro"] ++ ["dodo","lyric"]
Sometimes the operators
$ are useful as functions in their own
right. For example, a list of functions can be applied to an argument
using map and a section of
map ($"string") [reverse, take 2, drop 2] ==> [reverse $ "string", take 2 $ "string", drop 2 $ "string"] ==> [reverse "string", take 2 "string", drop 2 "string"] ==> ["gnirts", "st", "ring"]
If this seems complicated, don’t worry. You don’t need to use
$ in your own code until you’re comfortable with them. However, you’ll
$ when reading Haskell examples and code on the
internet, so it’s good to know about them. This
article might also help.
Now, let’s rewrite the
whatFollows example from earlier using the
tools we just saw. Here’s the original version:
substringsOfLength :: Int -> String -> [String] substringsOfLength n str = map shorten (tails str) where shorten s = take n s whatFollows :: Char -> Int -> String -> [String] whatFollows c k str = map tail (filter match (substringsOfLength (k+1) str)) where match sub = take 1 sub == [c]
To get started, let’s get rid of the helper function
substringsOfLength and move all the code to
whatFollows c k str = map tail (filter match (map shorten (tails str))) where shorten s = take (k+1) s match sub = take 1 sub == [c]
Now let’s use partial application instead of defining
whatFollows c k str = map tail (filter match (map (take (k+1)) (tails str))) where match sub = take 1 sub == [c]
$ to eliminate some of those parentheses:
whatFollows c k str = map tail . filter match . map (take (k+1)) $ tails str where match sub = take 1 sub == [c]
We can also replace
match with a lambda:
whatFollows c k string = map tail . filter (\sub -> take 1 sub == [c]) . map (take (k+1)) $ tails string
Finally, we don’t need to mention the
string parameter at all, since
we can just express
whatFollows as a composition of
whatFollows c k = map tail . filter (\sub -> take 1 sub == [c]) . map (take (k+1)) . tails
We can even go a bit further by rewriting the lambda using an operator section
\sub -> take 1 sub == [c] === \sub -> (==[c]) (take 1 sub) === \sub -> (==[c]) ((take 1) sub) === \sub -> ((==[c]) . (take 1)) sub === ((==[c]) . (take 1)) === ((==[c]) . take 1)
Now what we have left is:
whatFollows c k = map tail . filter ((==[c]) . take 1) . map (take (k+1)) . tails
This is a somewhat extreme version of the function, but when used in moderation the techniques shown here can make code easier to read.
More Functional List Wrangling Examples
Here are some more examples of functional programming with lists. Let’s start by introducing a couple of new list functions:
-- take elements from a list as long as they satisfy -- a predicate takeWhile :: (a -> Bool) -> [a] -> [a] -- drop elements from a list as long as they satisfy -- a predicate dropWhile :: (a -> Bool) -> [a] -> [a]
takeWhile even [2,4,1,2,3] ==> [2,4] dropWhile even [2,4,1,2,3] ==> [1,2,3]
There’s also the function
elem, which can be used to check if a list
contains an element:
elem 3 [1,2,3] ==> True elem 4 [1,2,3] ==> False
Using these, we can implement a function
findSubstring that finds the
earliest and longest substring in a string that consist only of the
findSubstring :: String -> String -> String findSubstring chars = takeWhile (\x -> elem x chars) . dropWhile (\x -> not $ elem x chars)
findSubstring "a" "bbaabaaaab" ==> "aa" findSubstring "abcd" "xxxyyyzabaaxxabcd" ==> "abaa"
zipWith lets you combine two lists element-by-element:
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith (++) ["John","Mary"] ["Smith","Cooper"] ==> ["JohnSmith","MaryCooper"] zipWith take [4,3] ["Hello","Warden"] ==> ["Hell","War"]
Sometimes with higher-order functions it’s useful to have a function
that does nothing. The function
id :: a -> a is the identity function
and just returns its argument.
id 3 ==> 3 map id [1,2,3] ==> [1,2,3]
This seems a bit useless, but you can use it for example with
filter id [True,False,True,True] ==> [True,True,True] dropWhile id [True,True,False,True,False] ==> [False,True,False]
Another very simple but sometimes crucial function is the constant
const :: a -> b -> a. It always returns its first argument:
const 3 True ==> 3 const 3 0 ==> 3
When partially applied it can be used when you need a function that always returns the same value:
map (const 5) [1,2,3,4] ==> [5,5,5,5] filter (const True) [1,2,3,4] ==> [1,2,3,4]
Lists and Recursion
Here’s a new operator,
Prelude> 1:  Prelude> 1:[2,3] [1,2,3] Prelude> tail (1:[2,3]) [2,3] Prelude> head (1:[2,3]) 1 Prelude> :t (:) (:) :: a -> [a] -> [a]
: operator builds a list out of a head and a tail. In other words,
x : xs is the same as
[x] ++ xs. Why do we need an operator for
: is the constructor for lists: it returns a new linked
list node. The other list constructor is
, the empty list. All lists
are built using
. The familiar
[x,y,z] syntax is actually
just a nicer way to write
x:y:z:, or even more explicitly,
x:(y:(z:)). In fact
(++) is defined in terms of
: and recursion
in the standard library.
Here’s a picture of how
[1,2,3] is structured in memory:
Building a List
: we can define recursive functions that build lists. For
example here’s a function that builds lists like
descend 0 =  descend n = n : descend (n-1)
descend 4 ==> [4,3,2,1]
Here’s a function that builds a list by iterating a function
iterate f 0 x = [x] iterate f n x = x : iterate f (n-1) (f x)
iterate (*2) 4 3 ==> [3,6,12,24,48] let xs = "terve" in iterate tail (length xs) xs ==> ["terve","erve","rve","ve","e",""]
Here’s a more complicated example: splitting a string into pieces at a given character:
split :: Char -> String -> [String] split c  =  split c xs = start : split c (drop 1 rest) where start = takeWhile (/=c) xs rest = dropWhile (/=c) xs
split 'x' "fooxxbarxquux" ==> ["foo","","bar","quu"]
Pattern Matching for Lists
Last lecture, it was said that constructors are things that can be
pattern matched on. Above, it was divulged that the constructors for the
list type are
. We can put one and one together and guess
that we can pattern match on
. This is true! Here’s how you
can define your own versions of
tail using pattern
myhead :: [Int] -> Int myhead  = -1 myhead (first:rest) = first mytail :: [Int] -> [Int] mytail  =  mytail (first:rest) = rest
You can nest patterns. That is, you can pattern match more than one
element from the start of a list. In this example, we use the pattern
(a:b:_) which is the same as
sumFirstTwo :: [Integer] -> Integer -- this equation gets used for lists of length at least two sumFirstTwo (a:b:_) = a+b -- this equation gets used for all other lists -- (i.e. lists of length 0 or 1) sumFirstTwo _ = 0
sumFirstTwo  ==> 0 sumFirstTwo [1,2] ==> 3 sumFirstTwo [1,2,4] ==> 3
Here’s an example that uses many different list patterns:
describeList :: [Int] -> String describeList  = "an empty list" describeList (x:) = "a list with one element" describeList (x:y:) = "a list with two elements" describeList (x:y:z:xs) = "a list with at least three elements"
describeList [1,3] ==> "a list with two elements" describeList [1,2,3,4,5] ==> "a list with at least three elements"
List patterns that end with
: can be typed out as list literals.
That is, just like
[1,2,3] is the same value as
[x,y] is the same as the pattern
x:y:. Let’s rewrite that
describeList :: [Int] -> String describeList  = "an empty list" describeList [x] = "a list with exactly one element" describeList [x,y] = "a list with exactly two elements" describeList (x:y:z:xs) = "a list with at least three elements"
Another way we can nest patterns is pattern matching on the head while
pattern matching on a list. For example this function checks if a list
startsWithZero :: [Integer] -> Bool startsWithZero (0:xs) = True startsWithZero (x:xs) = False startsWithZero  = False
Consuming a List
Using pattern matching and recursion, we can recursively process a whole list. Here’s how you sum all the numbers in a list:
sumNumbers :: [Int] -> Int sumNumbers  = 0 sumNumbers (x:xs) = x + sumNumbers xs
Here’s how you compute the largest number in a list, this time using a helper function.
myMaximum :: [Int] -> Int myMaximum  = 0 -- actually this should be an error... myMaximum (x:xs) = go x xs where go biggest  = biggest go biggest (x:xs) = go (max biggest x) xs
go is just a cute name for the helper function here. It’s
not special syntax.
It’s often convenient to use nested patterns while consuming a list.
Here’s an example that counts how many
Nothing values occur in a list
countNothings :: [Maybe a] -> Int countNothings  = 0 countNothings (Nothing : xs) = 1 + countNothings xs countNothings (Just _ : xs) = countNothings xs
countNothings [Nothing,Just 1,Nothing] ==> 2
Building and Consuming a List
Now that we can build and consume lists, let’s do both of them at the same time. This function doubles all elements in a list.
doubleList :: [Int] -> [Int] doubleList  =  doubleList (x:xs) = 2*x : doubleList xs
It evaluates like this:
doubleList [1,2,3] === doubleList (1:(2:(3:))) ==> 2*1 : doubleList (2:(3:)) ==> 2*1 : (2*2 : doubleList (3:)) ==> 2*1 : (2*2 : (2*3 : doubleList )) ==> 2*1 : (2*2 : (2*3 : )) === [2*1, 2*2, 2*3] ==> [2,4,6]
Once you know pattern matching for lists, it’s straightforward to define
filter. Actually, let’s just look at the GHC standard
library implementations. Here’s
map :: (a -> b) -> [a] -> [b] map _  =  map f (x:xs) = f x : map f xs
and here’s filter:
filter :: (a -> Bool) -> [a] -> [a] filter _pred  =  filter pred (x:xs) | pred x = x : filter pred xs | otherwise = filter pred xs
Note! Naming the argument
_pred is a way to tell the reader of
the code that this argument is unused. It could have been just
Tail Recursion and Lists
When a recursive function evaluates to a new call to that same function
with different arguments, it is called tail-recursive. (The recursive
call is said to be in tail position.) This is the type of recursion
that corresponds to an imperative loop. We’ve already seen many examples
of tail-recursive functions, but we haven’t really contrasted the two
ways for writing the same function. This is
sumNumbers from earlier in
sumNumbers :: [Int] -> Int sumNumbers  = 0 sumNumbers (x:xs) = x + sumNumbers xs
In the second equation the function
+ is at the top level, i.e. in
tail position. The recursive call to
sumNumbers is an argument of
sumNumbers written using a tail recursive helper function:
sumNumbers :: [Int] -> Int sumNumbers xs = go 0 xs where go sum  = sum go sum (x:xs) = go (sum+x) xs
Note the second equation of
go: it has the recursive call to
the top level, i.e. in tail position. The
+ is now in an argument to
For a function like
sumNumbers that produces a single value (a
number), it doesn’t really matter which form of recursion you choose.
The non-tail-recursive function is easier to read, while the
tail-recursive one can be easier to come up with. You can try writing a
function both ways. The tail-recursive form might be more efficient, but
that depends on many details.
However, when you’re returning a list there is a big difference between
these two forms. Consider the function
doubleList from earlier. Here
it is again, implemented first directly, and then via a tail-recursive
doubleList :: [Int] -> [Int] doubleList  =  doubleList (x:xs) = 2*x : doubleList xs
doubleList :: [Int] -> [Int] doubleList xs = go  xs where go result  = result go result (x:xs) = go (result++[2*x]) xs
Here the direct version is much more efficient. The
(:) operator works
in constant time, whereas the
(++) operator needs to walk the whole
list, needing linear time. Thus the direct version uses linear time
(O(n)) with respect to the length of the list, while the
tail-recursive version is quadratic (O(n²))!
One might be tempted to fix this by using
(:) in the tail-recursive
version, but then the list would get generated in the reverse order.
This could be fixed with an application of
reverse, but that would
make the resulting function quite complicated.
There is another reason to prefer the direct version: laziness. For now it’s enough for you to know that the direct way of generating a list is simpler,
more efficient and more idiomatic. You should try to practice it in
the exercises. Check out the standard library implementations of
filter above, even they produce the list directly without tail
Something Fun: List Comprehensions
Haskell has list comprehensions, a nice syntax for defining lists that
combines the power of
filter. You might be familiar with
Python’s list comprehensions already. Haskell’s work pretty much the
same way, but their syntax is a bit different.
[2*i | i<-[1,2,3]] ==> [2,4,6]
[i | i <- [1..7], even i] ==> [2,4,6]
In general, these two forms are equivalent:
[f x | x <- lis, p x] map f (filter p lis)
List comprehensions can do even more. You can iterate over multiple lists:
[ x ++ " " ++ y | x <- ["A", "B"], y <- ["C","D"] ] ==> ["AC","AD","BC","BD"]
You can make local definitions:
[ reversed | word <- ["this","is","a","string"], let reversed = reverse word ] ==> ["siht","si","a","gnirts"]
You can even do pattern matching in list comprehensions!
firstLetters string = [ char | (char:_) <- words string ]
firstLetters "Hello World!" ==> "HW"
Something Fun: Custom Operators
In Haskell an operator is anything built from the characters
!#$%&*+./<=>?@\^|-~. Operators can be defined just like functions
(note the slightly different type annotation):
(<+>) :: [Int] -> [Int] -> [Int] xs <+> ys = zipWith (+) xs ys
(+++) :: String -> String -> String a +++ b = a ++ " " ++ b
What’s the type of this function?
both p q x = p x && q x
a -> Bool -> a -> Bool -> a -> Bool
(a -> Bool) -> (a -> Bool) -> a -> Bool
(a -> Bool) -> (b -> Bool) -> c -> Bool
What’s the type of this function?
applyInOut f g x = f (g (f x))`
(a -> b) -> (b -> a) -> a -> b
(a -> b) -> (b -> c) -> a -> c
(a -> a) -> (a -> a) -> a -> a
Which one of the following functions adds its first argument to the second?
f x x = x + x
f x = \y -> x + y
f = \x y -> x + x
Which one of these functions does not satisfy
f 1 ==> 1?
f x = (\y -> y) x
f x = \y -> y
f x = (\y -> x) x
Which one of the following functions is correctly typed?
f x y = not x; f :: (Bool -> Bool) -> Bool
f x = x ++ "a"; f :: Char -> String
f x = 'a' : x; f :: String -> String
How many arguments does
drop 2 take?
What does this function do?
f (_:x:_) = x
- Returns the first element of a list
- Returns an arbitrary element of a list
- Returns all except the first and last elements of a list
- Returns the second element of a list
What is the result of the following expression?
reverse $ take 5 . tail $ "This is a test"`
- A type error
f :: a -> b, then what is the type of
[b -> c] -> [a -> c]
[c -> a] -> [c -> b]
(b -> c) -> [a -> c]
[a] -> [b]
What is the type of the leftmost
a -> a
(a -> a) -> (a -> a)
What is the type of
(c -> a -> b) -> a
c -> (a -> b -> a)
a -> b -> c -> a
This reading was originally written by Joel Kaasinen and John Lång in their open source textbook. The book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License, which allows users to copy, modify, and distribute the book.
This reading was modified by Titus H. Klinge in 2021 and presented above under the same license in order to better serve the students of this course.