Haskell by Example – Bob Ippolito – January 2014



Haskell by Example – Bob Ippolito – January 2014

0 0


eh-02-haskell-by-example


On Github etrepum / eh-02-haskell-by-example

Haskell by Example

Bob Ippolito

January 2014

Want to learn more? Take my Intro to Haskell course atenginehere.com/courses/intro-to-haskell-etrepum

Getting Haskell

(In order of preference)

Interactive Haskell

  • λ> is the interpreter prompt.
  • The code to the right of it is what you type.
  • Lines that start with -- are comments.
  • The line below is the result after pressing return.

Haskell as a calculator

λ> 486
486
λ> 137 + 349
486
λ> 1000 - 334
666
λ> -2 - (-3)
1
λ> 5 * 99
495
λ> 2 ^ 16
65536
λ> -2
-2
λ> 1.1
1.1
λ> 10 / 5
2.0
λ> 2.7 + 10
12.7
λ> 0.1 * 1.0e4
1000.0
λ> 36 ** 0.5
6.0

Exercise 1

  • Try using Haskell's operators to find out what 2 to the 100th power is.
  • If you're using the code window here, or FP Haskell Center, the program should look like this, with … replaced by the expression you want to evaluate:
main = print (…)

In (prefix) form

λ> -2
-2
λ> 1.1
1.1
λ> (/) 10 5
2.0
λ> (+) 2.7 10
12.7
λ> (*) 0.1 1.0e4
1000.0
λ> (**) 36 0.5
6.0
λ> -2
-2
λ> 1.1
1.1
λ> 10 / 5
2.0
λ> 2.7 + 10
12.7
λ> 0.1 * 1.0e4
1000.0
λ> 36 ** 0.5
6.0

Exercise 2

  • Convert your program from Exercise 1 to use the operators in prefix form.

Calling functions

  • Function application looks like function arg.
  • No parentheses required.
  • Function application has the highest precedence (higher than any operator).

Using functions

λ> even 10
True
λ> negate 4
-4
λ> negate 1 + 10
9
λ> max 4 5
5
λ> True && not (False || True)
False

`infix` syntax sugar

λ> max 4 5
5
λ> div 10 2
5
λ> lcm 360 13
4680
λ> divMod 360 13
(27,9)
λ> elem 'a' "apple"
True
λ> elem 4 [1, 2, 3]
False
λ> 4 `max` 5
5
λ> 10 `div` 2
5
λ> 360 `lcm` 13
4680
λ> 360 `divMod` 13
(27,9)
λ> 'a' `elem` "apple"
True
λ> 4 `elem` [1, 2, 3]
False

What can I do?

  • Haskell's built-in functionality is all in the Prelude module
  • Go to http://www.haskell.org/hoogle/
  • Search for Prelude
  • Click on the module Prelude result
  • Keep this tab open :)

Exercise 3

  • Write an expression to calculate the last four decimal digits of 3 ^ 1337.
  • You can use the mod function for this, try evaluating these expressions:
λ> mod 1234 100
…
λ> 1234 `mod` 10
…

Let there be variables

  • Variables must start with a lower-case letter or underscore.
  • By convention, theyAreWrittenLikeThis.
  • A leading underscore implies that it is an unused variable, but this is only used to silence compiler warnings.

Using let

λ> let x = 10 in x * x
100
λ> let { x = 10; y = 2 } in x * y
20
λ> let { x = 2 * y; y = 40 } in x * y
3200
λ> let { x = undefined; y = 2 } in y
2
λ> let { r = 2; area = pi * r * r } in area
12.566370614359172

Exercise 4

  • Use a let expression with variables r and circumference that evaluates to the circumference of a circle with that radius

Define your own functions

  • All functions in Haskell take exactly one argument
  • … but there's syntax to make it seem like there are multi-argument functions!

Functions and Lambdas (λ)

-- this is far more common than lambda syntax
λ> let inc x = x + 1 in inc 10
11
λ> let add x y = x + y in add 1 2
3
-- parentheses only used for clarity here
λ> let inc = (\x -> x + 1) in inc 10
11
λ> let add = (\x -> (\y -> x + y)) in add 1 2
5

Exercise 5

  • Refactor your implementation of Exercise 4 to use a function named circumference that takes an argument r and evaluates to the circumference of a circle with the given radius.
  • Bonus: Try doing it with and without the \lambda -> syntax

Lists

  • Lists in Haskell are linked lists
  • They are homogeneous, can only store one type in a list
  • The constructors are : and []
  • There are several kinds of syntax sugar for creating lists

Making lists

λ> [1, 2, 3, 4]
[1,2,3,4]
λ> 1 : 2 : 3 : 4 : []
[1,2,3,4]
λ> (:) 1 ((:) 2 ((:) 3 ((:) 4 [])))
[1,2,3,4]
λ> ['a', 'b', 'c', 'd']
"abcd"
λ> 'a' : 'b' : "cd"
"abcd"
λ> [True, 1 == 0, 1 > 0]
[True,False,True]
λ> [1, 2] ++ [3, 4]
[1,2,3,4]

Strings!

  • Strings in Haskell are lists of Char, or [Char]
  • They support the full range of unicode
  • There are other data types for bytes and text in the Haskell Platform that can be more efficient, but we'll start with String

Exercise 6

  • Implement a function named showNumber that given a number, evaluates to the string "Your number is: " with the given number as a string appended to it.
  • For example: showNumber 10 would evaluate to"Your number is: 10"
  • HINT: Use the show function to do this, show 10 evaluates to "10"

If/then/else

  • if predicate then consequent else alternative
fib x =
  if x > 1 then fib (x - 1) + fib (x - 2) else x

fib x =
  if x > 1
    then fib (x - 1) + fib (x - 2)
    else x

Exercise 7

  • Implement a function named fac that given a positive integer, evalues to the factorial of that number.
  • In pseudocode: fac n = n * (n - 1) * (n - 2) * … * 2 * 1

Pattern matching

fib x =
  case x of {0 -> 0; 1 -> 1; _ -> fib (x-1) + fib (x-2)}

fib x = case x of
  0 -> 0
  1 -> 1
  _ -> fib (x - 1) + fib (x - 2)
  
fib 0 = 0
fib 1 = 1
fib x = fib (x - 1) + fib (x - 2)

Matching lists

lastElem [] = error "empty list"
lastElem (x : []) = x
lastElem (_ : xs) = lastElem xs

lastElem [] = error "empty list"
lastElem [x] = x
lastElem (_ : xs) = lastElem xs

Exercise 8

  • Implement a function named firstElem that evaluates to the first element of the given list, or error "empty list" if given an empty list.
  • Use only pattern matching and recursion to implement this function.
  • Example: firstElem [1,2,3] == 1

Exercise 9

  • Implement a function named nth that evaluates to the nth element of a list (with zero based indexing), or error "index too large" if the list is too short.
  • Use only pattern matching and recursion to implement this function.
  • Example: nth 1 [1,2,3] == 2

Guards

  • Guards are great syntax sugar for checking conditions
  • Can be used with case or when defining functions
  • otherwise is equivalent to True
evens xs = case xs of
  [] -> []
  (x : xs') | x `rem` 2 == 0 -> x : evens xs'
            | otherwise      -> evens xs'

evens [] = []
evens (x:xs)
  | x `rem` 2 == 0 = x : evens xs
  | otherwise      = evens xs

Exercise 10

  • Using guards, implement select which returns the list elements that match the given predicate.
  • Example: select (>5) [2,4,6,8,10] == [6,8,10]

foldr

  • foldr is a right fold, which can be used to implement many other functions on lists
  • You can think of it as replacing : with k and [] with z
foldr k z []     = z
foldr k z (x:xs) = x `k` foldr k z xs

map

  • map is a commonly used list function that can be expressed in terms of foldr
  • map (+1) [1,2,3,4] == [2,3,4,5]
map f xs = foldr (\x acc -> f x : acc) [] xs

Simplifying map

map f xs = foldr (\x acc -> f x : acc) [] xs

-- eta-reduction, remove xs from both sides
map f = foldr (\x acc -> (f x : acc)) []

-- rewrite in prefix form
map f = foldr (\x acc -> (:) (f x) acc)) []

-- eta-reduction of inner function
map f = foldr (\x -> (:) (f x))) []

-- reduce inner function further with .
map f = foldr ((:) . f) []

Going point-free

  • Some people call this "pointless". I agree in many cases.
map f = foldr ((:) . f) []

-- move arguments around for easier reduction
map f = flip foldr [] ((:) . f)

-- reduce further with .
map = flip foldr [] . ((:) .)

-- rewrite flip using an infix section
map = (`foldr` []) . ((:) .)

-- What does this accomplish?
-- Not much. Don't bother.
-- But you may see code like this.

Exercise 11

  • Implement select from Exercise 10 using foldr without any explicit recursion
  • select (>5) [2,4,6,8,10] == [6,8,10]

Basic IO

  • Programs don't do anything without IO
  • Haskell will call your main function and interpret IO actions
  • IO actions can be sequenced, or combined

Running Haskell programs

  • The EngineHere code runner doesn't support input
  • Please use a locally installed Haskell to run these
  • Or use FP Haskell Center, which does support input
  • Create the .hs file with a text editor and use runhaskell to interpret them

putStr

Hello.hs

main = putStr "hello\n"
$ runhaskell Hello.hs
hello

putStrLn

Hello.hs

main = putStrLn "hello"
$ runhaskell Hello.hs
hello

print

Hello.hs

main = do
  print "hello"
  putStrLn (show "hello")
  putStr (show "hello" ++ "\n")
$ runhaskell Hello.hs
"hello"
"hello"
"hello"

getLine

  • <- binds the result of an IO action to a variable
main = do
  putStrLn "Enter your name:"
  name <- getLine
  putStrLn ("You entered: " ++ name)

return

  • return creates an IO action from a value
  • It has nothing to do with exiting a function!
main = do
  putStrLn "Enter your name:"
  name <- return "Bob"
  putStrLn ("You entered: " ++ name)

Recursion in IO

  • You can press ^C (Control and C at the same time) to abort this!
main = do
  putStrLn "Enter your name:"
  name <- return "Bob"
  putStrLn ("You entered: " ++ name)
  main

State

nameLoop names = do
  putStrLn "Who are you?"
  name <- getLine
  if name `elem` names
    then do
      putStrLn ("I already know " ++ name)
      nameLoop names
    else do
      putStrLn ("Nice to meet you " ++ name)
      nameLoop (name : names)

main = nameLoop []

Modules

Types

Defining types

0