Lecture: Haskell
Dear students:
A new language is upon us. New languages require us to regress to infancy. The world around is filled with expressions that we can't understand, and we are uncomfortable. Today we embrace that discomfort and start babbling in Haskell by solving some programming challenges together.
Inventory
To solve these problems, we'll to make use of some of Haskell's builtin functions. These are the ones from the reading plus a few more that you might find useful:
-
head items
: get first element of a list -
tail items
: get all elements but the head of a list -
take n items
: get firstn
elements of a list -
drop n items
: get list without firstn
elements -
fst pair
: get first component of a 2-tuple -
snd pair
: get second component of a 2-tuple -
item : items
: prepend a new head onto a list -
these ++ those
: concatenate two lists -
div a b
: integer division -
mod a b
: integer modulus -
a ^ b
: positive integer exponentiation -
a ** b
: floating point exponentiation -
ord c
: convert character to ASCII -
chr c
: convert ASCII to character
PairSum
Write a function pairSum
that, given a pair of numbers, returns their sum. For example:
-
pairSum (5, 6)
→11
-
pairSum (-3, -6)
→-9
-
pairSum (0, 0)
→0
Here's one way to write it:
pairSum :: Num a => (a, a) -> a
pairSum pair = fst pair + snd pair
Maybe you prefer local variables?
pairSum :: Num a => (a, a) -> a
pairSum pair = x + y
where x = fst pair
y = snd pair
Majority
Write a function majority
that, given a population as an Int
, returns the number of people that are needed to count as a majority in that people. For example:
-
majority 100
→51
-
majority 99
→50
-
majority 98
→50
-
majority 1
→1
-
majority 2
→2
Here's one solution:
majority :: Int -> Int
majority n = div n 2 + 1
Recall that Haskell operators and functions are the same thing. Functions are prefix operators, but they can be moved to infix:
majority :: Int -> Int
majority n = n `div` 2 + 1
Neck
Write a function neck
that, given a list, returns its second element. For example:
-
neck [5, 3, 1]
→3
-
neck "guarantee"
→'u'
-
neck [10..20]
→11
-
neck [0,5..100]
→5
Here's one solution:
neck :: [a] -> a
neck xs = head (tail xs)
Parentheses are annoying to balance and can make code hard to read. Haskell provides an alternative in $
. It yields the value of everything to its right, allowing expressions of the form f (g (x))
to be rewritten as f $ g $ x
.
neck :: [a] -> a
neck xs = head $ tail xs
Next
Write a function next
that accepts two numbers in an arithmetic sequence and returns the next number. For example:
-
next 5 8
→11
-
next 2 2
→2
-
next 10 0
→-10
-
next 5.3 5.4
→5.5
Here's one solution:
next :: Num a => a -> a -> a
next a b = b + delta
where delta = b - a
Bigger First
Write a function biggerFirst
that, given a pair of lists, returns the one that has a bigger first element. For example:
-
biggerFirst ("bless", "other")
→"other"
-
biggerFirst ([1..5], [3, 7])
→[3, 7]
-
biggerFirst ([-10, -20, -30], [-20, -10, -30])
→[-10, -20, -30]
Here's one solution:
biggerFirst :: Ord a => ([a], [a]) -> [a]
biggerFirst pair = if a0 >= b0 then a else b
where
a = fst pair
b = snd pair
a0 = head a
b0 = head b
Slice
Write a function slice
that, given a list, a starting index, and an ending index, returns the sublist between the given indices. For example:
-
slice 2 6 "romania"
→"mania"
-
slice 0 2 [9, 3, 2, 11, 8]
→[9, 3, 2]
Here's one solution:
slice :: Int -> Int -> [a] -> [a]
slice from to xs = take n afterFroms
where
afterFroms = drop from xs
n = to - from + 1
Abbreviate
Write a function abbreviate
that, given a string and an integer length, it returns the string intact if its short or abbreviated if its long. Shortness is determine by the length parameter. For example:
-
abbreviate "muenster" 4
→"m...r"
-
abbreviate "listen" 6
→"listen"
Here's one solution:
abbreviate :: String -> Int -> String
abbreviate text n =
if length text <= n
text
else
[head text] ++ "..." ++ [last text]
Quadrant
Write a function quadrant
that accepts a Cartesian coordinate pair of numbers. It returns "I"
, "II"
, "III"
, or "IV"
to indicate the pairs quadrant. If the pair doesn't lie in a quadrant, it returns "?"
. For example:
-
quadrant (1, 2)
→"I"
-
quadrant (0, 0)
→"?"
-
quadrant (3, -10)
→"IV"
Here's one solution:
quadrant :: (Double, Double) -> String
quadrant xy
| x > 0 && y > 0 = "I"
| x < 0 && y > 0 = "II"
| x < 0 && y < 0 = "III"
| x > 0 && y < 0 = "IV"
| otherwise = "?"
where
x = fst xy
y = snd xy
sumHeadNeck
Write a function sumHeadNeck
that accepts a list of numbers that has at least two elements. It returns a new list in which the head and the neck have been replaced by their sum.
-
sumHeadNeck [100, 3]
→[103]
-
sumHeadNeck [6, 10, 3, 5]
→[16, 3, 5]
Here's one solution:
sumHeadNeck :: Num a => [a] -> [a]
sumHeadNeck xs = (x0 + x1) : rest
where
x0 = head xs
x1 = neck xs
rest = drop 2 xs
Main
In a sense, Haskell is two languages: one for its pure functions and one for the messier stuff like printing and running sequences of statements. Writing functions is the clean and beautiful part of Haskell, but sometimes we do need a main routine to set the whole process in motion. Let's take a peak at some very simple mains. The reading will go into more detail about how they work.
Suppose we want a program that prompts the user for the names of two people and then prints a message confessing their love. We'd write it like this:
main = do
first <- getLine
second <- getLine
let confession = first ++ "❤️" ++ second
putStrLn confession
There are two “assignment” operators here. The <-
is for assigning messy data gained from impure I/O calls. The let
and =
are for assigning pure values.
Suppose we want a program that gives us a list of integers between two values. We'd write it like this:
main = do
startString <- getLine
endString <- getLine
let start = read startString :: Int
let end = read endString :: Int
let xs = [start..end]
print xs
The read
function is the opposite of show
. It's like parseInt
or parseDouble
, but it doesn't have a type baked in. It's polymorphic. So we have to steer the type with the ::
operator. We can read it as “as”.
TODO
Here's your list of things to do before we meet next:
See you next time.
Sincerely,
P.S. It's time for a haiku!