Computer Science 312 Making choices Tail Recursion Local contexts with where clauses Dealing with Errors Modules, Imports, Exports 1
Making Choices in Haskell Use a set of function clauses, where pattern matching selects the option (factorial, fibonacci, etc.) factorial :: Integer -> Integer factorial 1 = 1 factorial n = n * factorial (n – 1) fibonacci:: Integer -> Integer fibonacci 1 = 1 fibonnaci 2 = 1 fibonacci n = fibonacci (n – 1) + fibonacci (n – 2)
Making Choices in Haskell Simple function clauses will not work in the cases of functions like max, min, and sum, where the option depends on an explicit condition Prelude> max 3 4 4 Prelude> sum 1 4 10
Use a Function Guard Indentation is significant! <function name> <parameters> | <Boolean expression-1> = <expression-1> … | <Boolean expression-n> = <expression-n> | otherwise = <default expression> myMax :: Ord a => a -> a -> Bool myMax x y | x > y = x | otherwise = y sum :: Integer -> Integer -> Integer sum lower upper | lower == upper = lower | otherwise = lower + sum (lower + 1) upper Indentation is significant!
Or Use an if-else Expression <function name> <parameters> = if <Boolean expression-1> then <expression-1> … else if <Boolean expression-n> then <expression-n> else <default expression> myMax :: Ord a => a -> a -> Bool myMax x y = if x > y then x else y Syntactic sugar for function guards
Which Form to Prefer? factorial :: Integer -> Integer factorial n = n * factorial (n – 1) factorial :: Integer -> Integer factorial n | n == 1 = 1 | otherwise = n * factorial (n – 1) factorial :: Integer -> Integer factorial n = if n == 1 then 1 else n * factorial (n – 1)
The case Expression interpretCommand :: Char -> String interpretCommand letter = case toUpper letter of 'N' -> newFile 'O' -> openFile 'S' -> saveFile 'Q' -> quitProgram _ -> handleError Like a switch statement in C or Java, but returns a value The _ symbol is a wildcard that matches any pattern
How Costly Is Recursion? factorial :: Integer -> Integer factorial 1 = 1 factorial n = n * factorial (n – 1) fib:: Integer -> Integer fib 1 = 1 fib 2 = 1 fib n = fib (n – 1) + fib (n – 2) Factorial is O(n) in running time and memory Fibonacci is O(kn) in running time and O(logn) in memory The growth of memory is due to cells for each function call pushed onto the runtime stack
Tail Recursion factorial :: Integer -> Integer factorial 1 = 1 factorial n = n * factorial (n – 1) tailFactorial :: Integer -> Integer -> Integer tailFactorial 1 result = result tailFactorial n result = tailFactorial (n – 1) (n * result) In tail recursion, there is no work remaining to be done after a recursive call The compiler can translate this to a loop with a single record for state variables on the stack
Tracing the Two Versions factorial 3 -> 3 * factorial 2 -> 2 * factorial 1 -> <- 1 <- 2 <- 6 tailFactorial 3 1 -> tailFactorial 2 3 -> tailFactorial 1 6 -> <- 6
Maintain the Interface with a Helper factorial :: Integer -> Integer factorial n = tailFactorial n 1 tailFactorial :: Integer -> Integer -> Integer tailFactorial 1 result = result tailFactorial n result = tailFactorial (n – 1) (result * n)
Nest the Helper in a where Clause factorial :: Integer -> Integer factorial n = tailFactorial n 1 where tailFactorial :: Integer -> Integer -> Integer tailFactorial 1 result = result tailFactorial n result = tailFactorial (n – 1) (result * n) If a function is just a helper for another function, you can define it locally within a where clause The scope of the where is determined by indentation
Nest Function in a where Clause factorial :: Integer -> Integer factorial n = tailFactorial n 1 where tailFactorial :: Integer -> Integer -> Integer tailFactorial 1 result = result tailFactorial n result = tailFactorial (n – 1) (result * n) fib :: Integer -> Integer fib n = tailFib 1 0 n where tailFib:: Integer -> Integer -> Integer -> Integer tailFib a b 1 = a tailFib a b n = tailFib (a + b) a (n – 1) Both functions are now linear in running time and constant in memory usage
Dealing with Errors factorial :: Integer -> Integer factorial 0 = 1 factorial n | n < 0 = error "Argument n must be >= 0" | otherwise = n * factorial (n – 1) The error function halts the program with an error message
Modules Prelude – the standard module, always available Data – organizes many data types under submodules Data.Char – character and text processing functions Data.Array – array processing functions Module names are capitalized
Full Imports Like from math import * Prelude> import Data.Char Prelude Data.Char> isLetter('K') True Prelude Data.Char> isLower('K') False Prelude Data.Char> digitToInt('9') 9 Prelude Data.Char> ord('9') 57 Prelude Data.Char> chr(57) '9' Like from math import *
Partial Imports Like from math import sqrt, etc. Prelude> import Data.Char (isLetter, isLower, digitToInt, ord) Prelude Data.Char> isLetter('K') True Prelude Data.Char> isLower('K') False Prelude Data.Char> digitToInt('9') 9 Prelude Data.Char> ord('9') 57 Prelude Data.Char> chr(57) '9' Like from math import sqrt, etc. Avoids name conflicts with other functions
Qualified Imports – The Full Slate Prelude> import qualified Data.Char Prelude Data.Char> Data.Char.isLetter('K') True Prelude Data.Char> Data.Char.isLower('K') False Prelude Data.Char> Data.Char.digitToInt('9') 9 Prelude Data.Char> Data.Char.ord('9') 57 Prelude Data.Char> Data.Char.chr(57) '9' Like import math Avoids name conflicts with these and other functions
Qualified Imports – A Subset Prelude> import qualified Data.Char (isLetter, isLower, digitToInt, ord) Prelude Data.Char> Data.Char.isLetter('K') True Prelude Data.Char> Data.Char.isLower('K') False Prelude Data.Char> Data.Char.digitToInt('9') 9 Prelude Data.Char> Data.Char.ord('9') 57 Prelude Data.Char> Data.Char.chr(57) '9' Avoids name conflicts with just these functions, And the others are not imported at all
Unrestricted Exports {- File: NewMath.hs Author: Ken Lambert Purpose: provides some simple math functions -} module NewMath where factorial :: Integer -> Integer factorial n = tailFactorial n 1 tailFactorial :: Integer -> Integer -> Integer tailFactorial 1 result = result tailFactorial n result = tailFactorial (n – 1) (result * n) Both factorial and tailFactorial are visible to importers
Restricted Exports {- File: NewMath.hs Author: Ken Lambert Purpose: provides some simple math functions -} module NewMath (factorial) where factorial :: Integer -> Integer factorial n = tailFactorial n 1 tailFactorial :: Integer -> Integer -> Integer tailFactorial 1 result = result tailFactorial n result = tailFactorial (n – 1) (result * n) Now the helper function tailFactorial is hidden from importers
For next time Introduction to lists, strings and tuples Homework assignment #1 is available on Sakai!