Examples of Functions¶
Example: nand with pattern matching¶
Haskell functions are written in an equational style, and often use pattern matching to decide which equation to apply. For example, 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 like this:
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:
> nand False True -- prefix style
True
> nand True True
False
For infix style, back-quotes are required:
> True `nand` True -- infix style
False
> True `nand` False
True
> 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, pick the 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
-- using 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 of 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 users guarded commands and 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 predicate 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
.
We can use smallest_divisor
to write is_prime
like this:
is_prime :: Integer -> Bool
is_prime n | n < 2 = False
| otherwise = (smallest_divisor n) == n
A composite number is an integer greater than 1 that is not prime. So
is_composite
can be written like this:
is_composite :: Integer -> Bool
is_composite n | n <= 1 = False
| otherwise = not (is_prime n)
Combining is_prime
with other standard functions lets us do some
interesting calculations. For example, here’s how you can calculate 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 the 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. Lets 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), each with the type signatures a -> a -> Bool
.
In addition, Eq
provides default implementations of ==
and /=
. So,
if a
only implements ==
, then \=
will automatically be defined as
not (x == y)
.
Eq
does not say how ==
and /=
are implemented 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.
Pig Latin¶
Pig Latin is a silly language that kids sometimes use. To convert an English word to Pig Latin, you follow these two rules:
- Pig Latin Rule 1: If the word starts with a consonant, then move all the consonants from the start of the word to the end of the word. For example, the word “cat” becomes “atcay” in Pig Latin, and “strength” becomes “engthstray”.
- Pig Latin Rule 2: If the word starts with a vowel, add “ay” to the end of the word. For instance, “orange” becomes “orangeay” (pronounced “orange-ay”), “is” becomes “isay”, and “a” becomes “aay”.
To convert an entire sentence to Pig Latin, convert each word to Pig Latin. For example, “bring the candy at noon” becomes “ingbray ethay andycay atay oonnay”.
Let’s write a Haskell function called piggify
that converts a string of
English words into Pig Latin, e.g.:
> piggify "mary found the treasure"
"arymay oundfay ethay easuretray"
The type signature for piggify
is:
piggify :: String -> String
Now lets think of the basic algorithm for piggify
. This seems like a
reasonable approach:
Split the English string into words. The standard Haskell function
words
can do this:> words "in the box" ["in","the","box"]
Convert each of the words on the list from the previous step to Pig Latin using the
map
function.map f lst
applies the functionf
to every element of the listlst
, returning the results in a new list.So, for example, the expression
map piglatin ["in","the","box"]
evaluates to the same thing as[piglatin "in", piglatin "the", piglatin "box"]
, which evaluates to["inay","ethay","oxbay"]
.We haven’t written the
piglatin
function yet. So we’ll make a note that this function needs to be written, and implement it later.Finally, since
piggify
returns a string, we have to convert the list of strings from the previous step into a string of words. The standard Haskell functionunwords
can do this, e.g.:> unwords ["inay","ethay","oxbay"] "inay ethay oxbay"
These steps are enough to let us write the piggify
function:
piggify :: String -> String
piggify s = unwords (map piglatin (words s))
The next task is to write the piglatin
function, which converts a single
English word to Pig Latin. Here is one implementation:
-- returns True if c is a vowel, and False otherwise
isVowel :: Char -> Bool
isVowel c = c `elem` "aeiouAEIOU"
piglatin :: String -> String
piglatin s | s == "" = s
| isVowel (head s) = s ++ "ay"
| otherwise = (dropWhile isCons s) ++ (takeWhile isCons s)
++ "ay"
where isCons c = not (isVowel c)
The first two steps handle the cases of the empty string and words that start with vowels. For words that start with consonants:
dropWhile isCons s
returns the string that is the same ass
but all leading consonants have been removed. Note that the functionisCons c
is a locally defined helper function that returnsTrue
ifc
is a consonant, andFalse
otherwise.For example,
dropWhile isCons "treasure"
returns"easure"
.takeWhile isCons s
returns all the leading consonants ofs
. For example,takeWhile isCons "treasure"
returns the string"tr"
.
So, if s
is the string "treasure"
, we get this:
(dropWhile isCons "treasure") ++ (takeWhile isCons "treasure") ++ "ay"
= "easure" ++ "tr" ++ "ay"
= "easuretray"
This way of writing code is som,etimes called top-down development. We
first wrote piggify
, and then after that we wrote piglatin
. It is
possible to go the other way as well, i.e. to use bottom-up development
where we would write (and test) piglatin
first, and then implement
piggify
.
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]
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]]
The expression [n | n <- xs, n <= x]
is an example of a list
comprehension, which is a notation inspired by set theory that combines
mapping and filtering. 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
partitioning in-place without making a copy. Also, appending lists with ++
is slow compared to in-place quicksort, and also makes copies. So this
function doesn’t scale to longer lists the way that a traditional, in-place,
quicksort implementation would in a language like Go or C++.
You can implement an efficient in-place quicksort, but it is much more complicated (e.g. see this version), and requires techniques beyond what we can cover in an introduction to Haskell.