Examples of Functions¶
Example: nand with pattern matching¶
Haskell functions are written in an equational style, and use simple pattern matching to decide which equation to apply. As an example of this, lets implement the nand function. nand stands for “not and” and is defined by the following truth table:
A | B | A nand B |
---|---|---|
False | False | True |
True | False | True |
False | True | True |
True | True | False |
Haskell lets us implement it very directly:
> nand :: Bool -> Bool -> Bool
> nand False False = True
> nand False True = True
> nand True False = True
> nand True True = False
This is pretty much a direct transcription of the nand truth table!
You can call nand
using either prefix or infix style, whatever you
prefer. Here’s prefix style:
Main> nand False True -- prefix style
True
Main> nand True True
False
For infix style, wrap it in back-quotes:
Main> True `nand` True -- infix style
False
Main> True `nand` False
True
Main> False `nand` False
Example: Calculating 1 + 2 + 3 + … + n¶
Here are some different ways of calculating \(1 + 2 + 3 + \ldots + n\):
> mysum1 :: Int -> Int
> mysum1 n = sum [1..n]
[1..n]
expands to the list [1,2,3,...,n]
, and the built-in function
sum
adds up all the numbers.
This version uses basic recursion:
> mysum2 :: Int -> Int
> mysum2 0 = 0 -- base case
> mysum2 n = n + (mysum2 (n - 1)) -- recursive case
A variation on this is to use guarded commands:
> mysum3 :: Int -> Int
> mysum3 n
> | n < 0 = error "n must be >= 0"
> | n == 0 = 0
> | otherwise = n + (mysum3 (n - 1))
Or you could use if-then-else:
> mysum4 :: Int -> Int
> mysum4 n = if n == 0 then 0 else n + (mysum4 (n - 1))
In general, try to pick a style that is easiest to read, and does not result in unnecessarily slow code.
Example: Length of a List¶
The built-in Haskell function length
returns the length of a list.
Let’s write our own versions:
> len1 :: [a] -> Int
> len1 [] = 0
> len1 x = 1 + len1 (tail x)
The tail
functions returns a list with its first element removed, e.g.
tail [2, 6, 8]
returns [6, 8]
.
Haskell has some special syntax for getting the head and tail elements of a list:
> len2 :: [a] -> Int
> len2 [] = 0
> len2 (first:rest) = 1 + (len2 rest)
Instead of the names first
and rest
, Haskell functions often uses
more generic names like x
and xs
:
> len3 :: [a] -> Int
> len3 [] = 0
> len3 (x:xs) = 1 + (len3 xs)
We don’t actually use the variable x
, so you can replace it with _
:
> len4 :: [a] -> Int
> len4 [] = 0
> len4 (_:xs) = 1 + (len4 xs)
You could also write it using guarded commands:
> len5 :: [a] -> Int
> len5 seq
> | null seq = 0 -- null tests if a list is empty
> | otherwise = 1 + (len5 (tail seq))
Or using if-then-else:
> len6 :: [a] -> Int
> len6 seq = if null seq then 0 else 1 + (len6 (tail seq))
Example: RGB Color Functions¶
The following functions are provided as more examples for learning and understanding basic Haskell.
-- RGB Color Functions
--
-- An RGB triple is a 3-tuple (R, G, B), where R, G, and B are integers in the
-- range [0, 256).
in_range :: Int -> Bool
in_range x = 0 <= x && x < 256
is_rgb :: (Int, Int, Int) -> Bool
is_rgb (r, g, b) = (in_range r) && (in_range g) && (in_range b)
is_gray :: (Int, Int, Int) -> Bool
is_gray (r, g, b) = (r == g && g == b)
invert_rgb :: (Int, Int, Int) -> (Int, Int, Int)
invert_rgb (r, g, b) = (255 - r, 255 - g, 255 - b)
-- returns the brightness of an RGB triple
brightness :: (Int, Int, Int) -> Float
brightness (r, g, b) = ((fromIntegral r) + (fromIntegral g) + (fromIntegral b)) / 3.0
-- returns the distance between two RGB colors
dist :: (Int, Int, Int) -> (Int, Int, Int) -> Float
dist (r1,g1,b1) (r2,b2,g2) = let dr = fromIntegral (r1 - r2)
dg = fromIntegral (g1 - g2)
db = fromIntegral (b1 - b2)
in sqrt (dr^2 + db^2 + dg^2)
-- same as dist, but uses "where" instead of "let/in"
dist2 :: (Int, Int, Int) -> (Int, Int, Int) -> Float
dist2 (r1,g1,b1) (r2,b2,g2) = sqrt (dr^2 + db^2 + dg^2)
where dr = fromIntegral (r1 - r2)
dg = fromIntegral (g1 - g2)
db = fromIntegral (b1 - b2)
-- constraint lo hi x; uses guards instead of patterns
constrain :: Int -> Int -> Int -> Int
constrain lo hi x
| x < lo = lo
| hi < x = hi
| otherwise = x
constrain_rgb :: (Int, Int, Int) -> (Int, Int, Int)
constrain_rgb (r, g, b) = (legal r, legal g, legal b)
where legal = constrain 0 255 -- local function defined
-- thanks to currying
-- add two RGB colors, constraining any values greater than 255
simple_add :: (Int, Int, Int) -> (Int, Int, Int) -> (Int, Int, Int)
simple_add (r1,g1,b1) (r2,g2,b2) = constrain_rgb (r1+r2, g1+g2, b1+b2)
-- convert a string color name into an RGB triple
to_RGB :: String -> (Int, Int, Int)
to_RGB "red" = (255,0,0)
to_RGB "green" = (0,255,0)
to_RGB "blue" = (0,0,255)
to_RGB "white" = (255,255,255)
to_RGB "black" = (0,0,0)
to_RGB _ = error "unknown color"
-- convert an RGB triple into a string name
to_Str :: (Int, Int, Int) -> String
to_Str (255,0,0) = "red"
to_Str (0,255,0) = "green"
to_Str (0,0,255) = "blue"
to_Str (255,255,255) = "white"
to_Str (0,0,0) = "black"
to_Str _ = error "unknown RGB triple"
Example: Finding Primes¶
Lets write a couple of functions for finding prime numbers. They won’t be extremely efficient, but they are interesting examples how to write Haskell functions.
Recall that an integer \(n\) is prime if it’s greater than 1, and is evenly divisible only by 1 and itself. For example, 2 is the smallest prime (and the only even prime), and the next few primes are 3, 5, 7, 11, and 13. There are an infinite number of primes, and in general testing for primality is an important computational problem.
It’s not hard to see that if the smallest divisor (greater than 1) of \(n\) is \(n\) itself, then \(n\) must be prime. For example, the smallest divisor (greater than 1) of 101 is 101, so 101 is prime.
So here is a function that returns the smallest divisor of a number:
> smallest_divisor :: Integer -> Integer
> smallest_divisor n
> | n < 0 = error "n must be >= 0"
> | n == 0 = 0
> | n == 1 = 1
> | otherwise = head (dropWhile (\x -> n `mod` x /= 0) [2..n])
The dropWhile
function takes two parameters: a function, and a list. Here
we’ve passed it the lambda function (\x -> n `mod` x /= 0)
, which returns
True
just when x
does not evenly divide n
. The expression
[2..n]
returns the list [2, 3, ..., n-1, n]
, i.e. all possible
divisors of n
. The call to dropWhile
“drops”, i.e. removes, all the
elements from the start of the list that fail to satisfy the function. The
first element of the list that is returned is thus the smallest divisor of
n
.
Now it’s easy to write is_prime
:
> is_prime :: Integer -> Bool
> is_prime n | n < 2 = False
> | otherwise = (smallest_divisor n) == n
And is_composite
:
> is_composite :: Integer -> Bool
> is_composite n | n <= 1 = False
> | otherwise = not (is_prime n)
A composite number is an integer greater than 1 that is not prime.
Suppose you want a list of all the primes up to 100:
filter is_prime [2..100]
Or a list of all the composites up to 100:
filter is_composite [2..100]
filter pred lst
returns a new list containing just the elements of lst
that satisfy pred
.
You could also write functions like these:
> primes_less_than :: Integer -> [Integer]
> primes_less_than n = filter is_prime [2..(n-1)]
>
> composites_less_than :: Integer -> [Integer]
> composites_less_than n = filter is_composite [2..(n-1)]
Another way to calculate the first 100 primes, or composites, is this:
take 100 (filter is_prime [2..])
take 100 (filter is_composite [2..])
The function take n lst
returns a list that is the first n
elements of
list.
The expression [2..]
is the infinite list [2,3,4,5,...]
. Since
Haskell uses lazy evaluation, it doesn’t generate elements on [2..]
until they are needed, and so in the expression take 100 (filter is_prime
[2..])
only a finite portion of [2..]
ends up getting evaluated.
Example: List Equality¶
The built-in function ==
tests if two lists are equal, but it’s
instructive to implement our own version:
> equal :: Eq a => [a] -> [a] -> Bool
> equal [] [] = True
> equal [] _ = False
> equal _ [] = False
> equal (a:as) (b:bs) = if a == b then (equal as bs) else False
The Eq a =>
at the start of the type signature is important. Eq
is a
standard type class provided by Haskell which is defined like this (taken
from here):
class Eq a where
(==), (/=) :: a -> a -> Bool
x /= y = not (x == y) -- default implementation of "not equal"
x == y = not (x /= y) -- default implementation of "equal"
This defines a type class named Eq
. It says that any type a
that
is an instance of Eq
must implement both ==
(equals) and /=
(not
equals), both with the type signatures a -> a -> Bool
.
In addition, Eq
provides default implementations of ==
and /=
. For
example, if a
only implements ==
, then \=
will automatically be
defined as not (x == y)
.
A type class does not say how ==
and /=
work for any particular type
a
: it just says that those functions must exist for a
. You use
instance
to define specific functions for a type class. For example:
instance Eq Integer where
x == y = x `integerEq` y
This says that, for the type Integer
, x == y
is defined to be x
`integerEq` y
, where we assume integerEq
is a low-level function that
tests if two Integer
values are the same.
We don’t need to define /=
explicitly, because the /=
from the Eq
class will automatically be used whenever we call x \= y
, i.e. x \= y
evaluates to not (x == y)
, which evaluates to not (x `integerEq`
y)
.
Eq
is a basic and important type class, and so Haskell provides dozens
of Eq instances for its basic types
.
Example: Sorting¶
Here’s a short implementation of quicksort:
> quicksort :: Ord a => [a] -> [a]
> quicksort [] = []
> quicksort (x:xs) = smalls ++ [x] ++ bigs
> where smalls = quicksort [n | n <- xs, n <= x]
> bigs = quicksort [n | n <- xs, n > x]
Notice that the type signature starts with Ord a =>
. Ord
is a type
class that guarantees that <=
and >
(and the other comparison
functions) work for whatever type a
is. Types like numbers, strings, and
lists of number/strings all implement Ord
, and so you can call
quicksort
on any of those. For example:
> quicksort [8, 9, -1, 5, 2]
[-1,2,5,8,9]
> quicksort "banana"
"aaabnn"
> quicksort ["up", "on", "yes", "no", "cow"]
["cow","no","on","up","yes"]
> quicksort [[5, 2], [2, 8, 7], [1,1,1], [4]]
[[1,1,1],[2,8,7],[4],[5,2]]
Notice the expression [n | n <- xs, n <= x]
. This is an example of a
list comprehension, which is a notation inspired by set theory. The list
comprehension [n | n <- xs, n <= x]
evaluates to a list containing all the
elements of xs
that are less than, or equal to, x
.
While the source code for quicksort
is remarkably short and
readable, it is very inefficient. For instance, the list comprehensions that
partition xs
return a copy of the data, whereas quicksort traditionally
does this partitioning in-place without making a copy. Also, appending lists
with ++
is relatively slow compared to the usual quicksort partitioning.
So this function doesn’t scale to longer lists the way that a more
traditional, in-place, quicksort implementation would in a language like Go
or C++.
You can implement an efficient in-place quicksort (search for “efficient haskell quicksort” on the web), but it is much more complicated, and requires techniques beyond what we can cover in an introduction to Haskell.
Example: Testing with QuickCheck¶
QuickCheck is a useful way to automatically test some kinds of functions. QuickCheck does property testing, i.e. it tests if some property of a function is true.
To see what this means, here are a few examples of properties for
quicksort
:
quicksort (quicksort lst) == quicksort lst
, for any listlst
; this saysquicksort
is idempotentquicksort (reverse lst) == quicksort lst
, for any listlst
quicksort [1..n] == [1..n]
, for any integern
greater than 0length lst == length (quicksort lst)
, for any listlst
By implementing each of these properties as Haskell functions that start with
prop_
, QuickCheck can automatically generate and run lots of random
tests:
-- quicksort_testing.hs
--
-- To use QuickCheck, you need to install it. The easiest way to do this is
-- to use the cabal package manager:
--
-- $ cabal update
-- $ cabal install QuickCheck
--
--
-- To run all tests, type runTests in the interpreter:
--
-- > runTests
--
-- Some useful references for quickCheck:
-- - http://www.cse.chalmers.se/~rjmh/QuickCheck/manual.html
-- - https://begriffs.com/posts/2017-01-14-design-use-quickcheck.html
--
import Test.QuickCheck
quicksort :: Ord a => [a] -> [a]
quicksort [] = []
quicksort (x:xs) = smalls ++ [x] ++ bigs
where smalls = quicksort [n | n <- xs, n <= x]
bigs = quicksort [n | n <- xs, n > x]
-- sorting is idempotent
--
-- in general, a function f is idempotent if f (f x) = f x for all x, i.e.
-- applying f twice gives the same result as applying it once
prop_qs1 :: [Int] -> Bool
prop_qs1 lst = quicksort (quicksort lst) == quicksort lst
-- sorting a list, or its reverse, returns the same result
prop_qs2 :: [Int] -> Bool
prop_qs2 lst = quicksort lst == quicksort (reverse lst)
-- sorting [1,2,..n] returns the same list
prop_qs3 :: Int -> Bool
prop_qs3 n = quicksort [1..n] == [1..n]
-- sorting doesn't change the length of a list
prop_qs4 :: [Int] -> Bool
prop_qs4 lst = length lst == length (quicksort lst)
-- this is a do environment --- the way that Haskell lets you perform actions
-- in sequence
runTests = do
quickCheck prop_qs1
quickCheck prop_qs2
quickCheck prop_qs3
quickCheck prop_qs4
Type runTests
do the testing, e.g.:
> runTests
+++ OK, passed 100 tests.
+++ OK, passed 100 tests.
+++ OK, passed 100 tests.
+++ OK, passed 100 tests.
quickCheck
generates 100 random inputs for each prop_
function and
verifies that the property is true for each input. Of course, this does not
guarantee quicksort
is bug-free, but in practice random testing often does
a good job of catching bugs in small functions. Plus, many programmers find
property tests more interesting to create than low-level (input, output)
pairs.
Lets see what happens when QuickCheck finds a bug. Consider this function:
quicksortBuggy :: Ord a => [a] -> [a]
quicksortBuggy [] = []
quicksortBuggy (x:xs) = smalls ++ [x] ++ bigs
where smalls = quicksortBuggy [n | n <- xs, n < x] -- oops
bigs = quicksortBuggy [n | n <- xs, n > x]
The bug is in the equation for smalls
: n < x
is written, when it is
meant to be n <= x
. Here’s what testing does:
> runTests
+++ OK, passed 100 tests.
+++ OK, passed 100 tests.
+++ OK, passed 100 tests.
*** Failed! Falsifiable (after 10 tests and 4 shrinks):
[4,4]
Notice that the first 4 properties don’t catch the bug — QuickCheck can’t
guarantee to catch all problems! It’s prop_qs4
that catches the error,
i.e. prop_qs4
notices that the list output by quicksortBuggy
is not
always the same length as the input list.
QuickTest reports that the list [4,4]
violates prop_qs4
. Trying this
with quicksortBuggy
shows that it indeed returns the wrong result:
> quicksortBuggy [4,4]
[4]
Notice that QuickCheck said it did “4 shrinks” to get [4,4]
. It turns out
that humans often find randomly generated data quite messy and hard to read,
and so QuickCheck uses various heuristics to “shrink” a failing test case into
one that is smaller and simpler. While there is no guarantee how well these
heuristics will work, in practice they often do a good job of finding small
and simple tests cases that can then be used to help debug the function.
The idea of testing using properties has proven to be quite popular with some programmers, and so you can find many QuickCheck-like tools in other languages. For example, in Python the Hypothesis package offers quickcheck-like testing for Python code.
QuickCheck Example: last item of a list¶
Let’s use QuickCheck to test a function called mylast
that is supposed to
work the same as the standard last
function (which returns the last
element of a list):
mylast :: [a] -> a
mylast [] = error "empty list"
mylast [x] = x
mylast (_:xs) = mylast xs
-- mylast :: [a] -> a
-- mylast [] = error "empty list"
-- mylast lst = head (drop (n-1) lst)
-- where n = length lst
-- mylast :: [a] -> a
-- mylast [] = error "empty list"
-- mylast lst = head (reverse lst)
We’ve provided a couple of alternate implementations in case you want to test those as well.
First, you need to think of some properties that mylast
has. This takes
some thinking, and you get better at it with practice. Here are three
properties that hold for all non-empty lists a
and b
:
- ``last (a ++ b) == last b``
last (reverse a) == head a
last [a] == a
Looking at our implementation of mylast
, the third property (last [a] ==
a
) doesn’t seem like it will be very useful to create test cases for because
the second equation in mylast
explicitly handles that case, so we can be
pretty sure it always holds.
You might try writing the first property like this:
prop_mylast1_bad :: [Int] -> [Int] -> Bool
prop_mylast1_bad a b = mylast (a ++ b) == mylast b
But when you run it you get a failure very quickly:
> quickCheck prop_mylast1_bad
*** Failed! (after 1 test):
Exception:
empty list
[]
[]
The problem is that prop_mylast1_bad
generates any list of Int
values, including the empty list []
. But our properties only hold for
non-empty lists.
There are two ways that you could fix this. The first is to re-write the test
using the conditional operator ==>
:
prop_mylast1 :: [Int] -> [Int] -> Property
prop_mylast1 a b = a /= [] && b /= [] ==> mylast (a ++ b) == mylast b
This test only checks if the property holds if both a
and b
are
non-empty. Also notice the return type of the test is now Property
instead
of Bool
. Here’s a sample run:
> quickCheck prop_mylast1
+++ OK, passed 100 tests; 16 discarded.
16 of the randomly generated tests were discarded because they include either
a
or b
equal to the empty lists. So be careful when using ==>
: if
your condition is very restrictive you may not end up doing many tests.
A second approach to writing this test is to use the special type
NonEmptyList a
provided by QuickCheck:
prop_mylast1b :: NonEmptyList Int -> NonEmptyList Int -> Bool
prop_mylast1b (NonEmpty a) (NonEmpty b) = mylast (a ++ b) == mylast b
This causes the random lists generated by QuickCheck to always have at least
one element. Note that the return type is back to Bool
again.
Here are all the tests, plus a helper function to run them all:
prop_mylast1 :: [Int] -> [Int] -> Property
prop_mylast1 a b = a /= [] && b /= [] ==> mylast (a ++ b) == mylast b
prop_mylast1b :: NonEmptyList Int -> NonEmptyList Int -> Bool
prop_mylast1b (NonEmpty a) (NonEmpty b) = mylast (a ++ b) == mylast b
prop_mylast2 :: [Int] -> Property
prop_mylast2 a = a /= [] ==> mylast (reverse a) == head a
prop_mylast2b :: NonEmptyList Int -> Bool
prop_mylast2b (NonEmpty a) = mylast (reverse a) == head a
run_mylastTests = do
quickCheck prop_mylast1
quickCheck prop_mylast1b
quickCheck prop_mylast2
quickCheck prop_mylast2b
Here’s a sample run:
> run_mylastTests
+++ OK, passed 100 tests; 22 discarded.
+++ OK, passed 100 tests.
+++ OK, passed 100 tests; 14 discarded.
+++ OK, passed 100 tests.