Haskell: Flow of Control

CS F331 Programming Languages CSCE A331 Programming Language Concepts Lecture Slides Wednesday, February 27, 2019

Glenn G. Chappell Department of Computer Science University of Alaska Fairbanks [email protected]

© 2017–2019 Glenn G. Chappell Review PL Category: Functional PLs [1/2]

Imperative programming: code tells a computer what to do. Declarative programming: code tells a computer what is true.

Functional programming (FP) is a declarative programming style that generally has the following characteristics. § Computation is considered primarily in terms of the evaluation of functions—as opposed to execution of tasks. § Functions are a primary concern. Rather than mere repositories for code, functions are values to be constructed and manipulated. § Side effects* are avoided. A function’s only job is to return a value.

*A function has a side effect when it makes a change, other than returning a value, that is visible outside the function.

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 2 Review PL Category: Functional PLs [2/2]

A language is a PL designed to support FP well.

A pure functional PL goes further, and does not support mutable data at all. There are no side effects in a pure functional PL.

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 3 Review Haskell: Functions — Basic Syntax [1/2]

Identifiers begin with a letter or underscore, and contain only letters, underscores, digits, and single quotes ('). There are two kinds of identifiers. § Normal identifiers begin with a lower-case letter or underscore. These name variables and functions. § Special identifiers begin with an UPPER-CASE letter. These name modules, types, and constructors. (This is not merely a convention!) myVariable -- Normal _my_Function'_33 -- Normal MyModule -- Special

For code from this topic, see func.hs.

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 4 Review Haskell: Functions — Basic Syntax [2/2]

To define a function, write what looks like a call to the function, an equals sign, and then an expression. addem a b = a+b

Parentheses may be used around individual arguments, due to precedence & associativity issues. addem 18 (5*7)

Use where to introduce a block of local definitions. lenSquared x y = sq x + sq y where sq z = z*z

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 5 Review Haskell: Functions — Defining Operators

We can define new infix binary operators. We can optionally set an operator’s precedence and associativity. a +$+ b = 2*a + b infixl 6 +$+ -- Left-associative, precedence level 6

Use a regular function as an infix binary operator by surrounding its name with backquotes (`).

2 `addem` 3

Use an infix binary operator as a regular function by surrounding its name with parentheses.

(+$+) 5 7

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 6 Review Haskell: Functions —

Currying: simulating a multiple-argument function using a single- argument function that returns a function.

Multiple-argument functions in Haskell are curried. addem x y = x+y add2 = addem 2

> add2 3 5

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 7 Review Haskell: Functions — Higher-Order Functions, Lambda Functions

Higher-order function: function that acts on functions. rev f a b = f b a sub x y = x-y rsub = rev sub > rsub 5 8 -3

Lambda function: function with no name.

> (\ x -> x*x) 5 25 > map (\ x -> x*x) [2, 3, 8] [4,9,64]

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 8 Review Haskell: Lists — Lists & Tuples

List: holds an arbitrary number of items, all of the same type.

[1, 4, 2] [] ['h','e','l','l','o'] "hello" -- Same as previous line [[1,2],[],[3],[4,5,6]] [1,3..] -- Infinite list: ALL positive odd integers

Tuple: holds a fixed number of items, possibly of different types.

(True, "abc", 34.6, [3,8])

For code from this topic, see list.hs.

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 9 Review Haskell: Lists — List Primitives

Three list primitives. 1. Construct an empty list.

[]

2. Cons: construct a list given its first item and a list of other items. Uses the infix colon (:) operator.

[5, 2, 1, 8] 5:[2, 1, 8] -- Same as above 5:2:1:8:[] -- Also same; ":" is right-associative

3. for lists. x:xs -- Pattern that matches a nonempty list

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 10 Review Haskell: Lists — Other List Syntax

List comprehension:

> [ x*y | x <- [3, 2, 1], y <- [10, 11, 12] ] [30,33,36,20,22,24,10,11,12]

> [ a | a <- [6,5,4,3,2,1], a > 3 ] [6,5,4]

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 11 Review Haskell: Lists — Lists & [1/2]

A function that takes a list will often be recursive. The base case handles the empty list. The recursive case handles a nonempty list. This might do a computation involving the head (b above) and make a recursive call on the tail (bs above).

TO DO § Write a function myMap that replicates the Prelude function map. § Look at the Prelude function filter. § Replicate filter as myFilter.

A predicate is a function that returns a Boolean value. A useful construction: if COND then EXPR1 else EXPR2. § COND is an expression of type Bool. If it is True, then EXPR1 is returned. If it is False, then EXPR2 is returned. § Expressions EXPR1 and EXPR2 must have the same type.

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 12 Review Haskell: Lists — Lists & Recursion [2/2]

Sometimes other kinds of recursion are used.

TO DO § Write a function lookInd that does item lookup by index (zero- based) in a list.

Useful § The pattern “_” means unused parameter. § Function error takes a String and returns any type. When it executes, it crashes, printing an error message, which should include the given string. § undefined is like error, but it takes no arguments.

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 13 Haskell: Flow of Control Introduction

Flow of control refers to the ways a PL determines what code is executed. For example, flow of control in Lua includes: § Selection (if … elseif … else). § Iteration (while, for). § Function calls. § Coroutines. § Threads. § Exceptions.

Haskell has very different flow-of-control facilities from most PLs that are oriented toward imperative programming.

Key Idea. Things that are done with traditional flow-of-control constructs in other programming languages are often done differently in Haskell. 27 Feb 2019 CS F331 / CSCE A331 Spring 2019 14 Haskell: Flow of Control Pattern Matching, Recursion, [1/5]

We have seen that Haskell has a useful pattern matching facility, which allows us to choose one of a number of function definitions. The rule is that the first definition with a matching pattern is the one used. isEmpty [] = True isEmpty (x:xs) = False

-- fibo the SLOW way fibo 0 = 0 fibo 1 = 1 fibo n = fibo (n-2) + fibo (n-1)

In many of the places we would use an if … else construction in other PLs, we use pattern matching in Haskell.

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 15 Haskell: Flow of Control Pattern Matching, Recursion, Lazy Evaluation [2/5]

Haskell also makes heavy use of recursion. lookInd 0 (x:xs) = x lookInd n (x:xs) = lookInd (n-1) xs lookInd _ [] = error "lookInd: index out of range"

In places where we would use a loop in an imperative PL, we use recursion in Haskell.

Recursion can be less costly in Haskell than in PLs like ++, because of Haskell’s required tail-call optimization (TCO). TCO means that a tail call does not use additional stack space.

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 16 Haskell: Flow of Control Pattern Matching, Recursion, Lazy Evaluation [3/5]

By default, Haskell does lazy evaluation. This allows for infinite lists. We have said that we can generate these even without the “..” syntax. But how? Here is an idea.

-- listFrom n -- Returns the infinite list [n, n+1, n+2, ...]. listFrom n = n:listFrom (n+1)

Is the above code acceptable? It does recursion with no base case. But this is not a problem, thanks to lazy evaluation. A recursive call is only made if further list items are needed. Using only a finite number of items guarantees that the recursion terminates. This is called : § A stream of values is generated recursively, as needed. § The recursion terminates when no more values are requested.

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 17 Haskell: Flow of Control Pattern Matching, Recursion, Lazy Evaluation [4/5]

Something else we can do: write our own if … else, as a function.

-- myIf condition tVal fVal -- Returns tVal if condition is True, fVal otherwise. myIf True tVal _ = tVal myIf False _ fVal = fVal

Thanks to lazy evaluation, no more than one of tVal, fVal is ever evaluated.

Here is the slow Fibonacci algorithm using myIf. fibo n = myIf (n <= 1) n (fibo (n-2) + fibo (n-1))

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 18 Haskell: Flow of Control Pattern Matching, Recursion, Lazy Evaluation [5/5]

And here is myFilter, reimplemented using myIf. myFilter p [] = [] myFilter p (x:xs) = myIf (p x) (x:rest) rest where rest = myFilter p xs

It turns out that the combination of pattern matching, recursion, and lazy evaluation, together with function calls, are all we need. We can build any flow-of-control construct out of these.

However, Haskell has other flow-of-control facilities, for convenience. Next we look at a few of these.

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 19 Haskell: Flow of Control Selection — Introduction

Selection allows us to choose one of multiple options to execute. For example, selection in C++ includes if … else, switch, and virtual function dispatch.

In Haskell, pattern matching works as a selection mechanism. Other selection constructions include guards, if … then … else, and case.

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 20 Haskell: Flow of Control Selection — Guards [1/3]

Guards are the Haskell equivalent of mathematical notation like the following.

�, if � ≥ 0; ����� � = −�, otherwise.

In Haskell: myAbs x | x >= 0 = x | otherwise = -x

We use guards in situations that pattern matching cannot handle. For example, there is no pattern that matches only nonnegative numbers.

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 21 Haskell: Flow of Control Selection — Guards [2/3] myAbs x | x >= 0 = x | otherwise = -x

Note that there is no equals sign after the first line above. Each vertical bar is followed by a Boolean expression. The first True expression tells which value is used. We generally want the last line to handle all remaining cases. We could use “True” as our final expression. “otherwise” is a variable with value True.

Here is the slow Fibonacci algorithm reimplemented using guards. fibo n | n <= 1 = n | otherwise = fibo (n-2) + fibo (n-1)

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 22 Haskell: Flow of Control Selection — Guards [3/3]

Here is myFilter reimplemented using guards. myFilter p [] = [] myFilter p (x:xs) | p x = x:rest | otherwise = rest where rest = myFilter p xs

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 23 Haskell: Flow of Control Selection — if … then … else

We have seen Haskell’s if … then … else construction. This is much like our myIf. myIf condition tVal fVal if condition then tVal else fVal -- Same as above

Most of the possible uses of if … then … else are probably better done with guards. And some people consider if … then … else to be un-Haskell-ish. But use it if you want.

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 24 Haskell: Flow of Control Selection — case

Haskell’s case construction is analogous to switch in C++. Here is an example, using case and using multiple function definitions.

-- case A case construction can fibo n = case n of always be replaced by 0 -> 0 multiple definitions. So I never use case. 1 -> 1 _ -> fibo (n-2) + fibo (n-1) But case does have an important behind-the- -- Multiple definitions scenes role. Multiple fibo 0 = 0 definitions are actually fibo 1 = 1 over fibo n = fibo (n-2) + fibo (n-1) a case construction.

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 25 Haskell: Flow of Control Error Handling — Fatal Errors

We have seen Haskell’s fatal-error facilities: error and undefined. lookInd 0 (x:xs) = x lookInd n (x:xs) = lookInd (n-1) xs lookInd _ [] = error "lookInd: index out of range"

These should be reserved for situations when a program needs to crash. This is generally because the program has detected a bug in its code. It crashes with an explanatory message, so that a developer can fix the bug.

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 26 Haskell: Flow of Control Error Handling — Exceptions

Haskell also has an exception mechanism, for dealing with error conditions that may be handled at runtime.

I think that Haskell’s exceptions are best avoided by new Haskell programmers (and many experienced Haskell programmers). So we will not be covering them.

If you look into Haskell’s exceptions, be aware that the terms error and exception are used inconsistently in both the standard library naming conventions and its documentation. Some documentation authors have difficulty explaining the difference between the two terms; others seem unaware that there is a difference.

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 27 Haskell: Flow of Control Error Handling — Simulating Exceptions [1/6]

As with other Haskell features, it can be interesting to see how we might implement something like exceptions ourselves.

We can do a rudimentary exception implementation using maybe types. Suppose t is a Haskell type. The type Maybe t is a type that allows for two kinds of values: Just x, where x is a value of type t, and Nothing. We can distinguish between these using pattern matching. isJust (Just x) = True isJust Nothing = False

Just and Nothing are examples of Haskell constructors.

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 28 Haskell: Flow of Control Error Handling — Simulating Exceptions [2/6]

Suppose we use Just x to represent the value x and Nothing to represent an exception. eSqrt :: Double -> Maybe Double eSqrt x | x < 0.0 = Nothing | otherwise = Just (sqrt x)

It would probably be better if our function took the same kind of value that it returns …

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 29 Haskell: Flow of Control Error Handling — Simulating Exceptions [3/6]

It would probably be better if our function took the same kind of value that it returns. eSqrt :: Maybe Double -> Maybe Double eSqrt Nothing = Nothing eSqrt (Just x) | x < 0.0 = Nothing | otherwise = Just (sqrt x)

Now the function not only raises exceptions; it also propagates exceptions it receives to its caller. This is analogous to what is called exception neutrality in C++.

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 30 Haskell: Flow of Control Error Handling — Simulating Exceptions [4/6]

Here is a division operator that uses this idea. (I use “@/” in place of “/”, since we have not covered overloading.) infixl 7 @/ -- Sets precedence, left associativity (@/) :: Maybe Double -> Maybe Double -> Maybe Double Nothing @/ _ = Nothing _ @/ Nothing = Nothing (Just _) @/ (Just 0.0) = Nothing (Just x) @/ (Just y) = Just (x / y)

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 31 Haskell: Flow of Control Error Handling — Simulating Exceptions [5/6]

We can write other operators this way. A few conveniences: e1 = Just 1.0 e2 = Just 2.0 Conversion to String e3 = Just 3.0 printIt (Just x) = show x printIt Nothing = "ERROR!"

Examples: Function application, but low precedence (and right-associative) > printIt $ e1 @/ e2 0.5 > printIt $ e3 @+ e1 @/ (e3 @- e1 @- e2) * e2 "ERROR!"

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 32 Haskell: Flow of Control Error Handling — Simulating Exceptions [6/6]

We could improve this arithmetic package just a bit. § Using type classes, we can overload the “/” operator, and thus avoid using “@”. § We can also overload the show function, so that our values can be printed in the usual way.

There might be an objection to my referring to the error signals used by this package as exceptions. They are normal return values. Why call them exceptions? My answer is that, because of the way the various functions & operators are written, error signals propagate automatically to the caller. Once an error has been found, no more arithmetic is done (because of lazy evaluation, no more arithmetic at all); the error signal is simply sent to the caller. While this mechanism may not be exceptions in some precise sense, it acts enough like exceptions as we know them, to warrant the name (IMHO).

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 33 Haskell: Flow of Control Encapsulated Loops — Introduction

Haskell allows us to encapsulate flow-of-control constructs as functions. We have already done this with myIf. We look at some functions that encapsulate loops. § map § filter § zip § Fold operations

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 34 Haskell: Flow of Control Encapsulated Loops — map & filter [1/3]

Consider the following C++ code. v is a vector. vector w; for (auto n : v) { w.push_back(n % 3); }

The same computation in Haskell is done without a loop. w = map (\ n -> n `mod` 3) v w = [ n `mod` 3 | n <- v ] -- Alternate form

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 35 Haskell: Flow of Control Encapsulated Loops — map & filter [2/3]

Another C++ snippet: vector w; for (auto n : v) { if (n > 6) w.push_back(n); }

And the equivalent Haskell: w = filter (> 6) v w = [ n | n <- v, n > 6 ] -- Alternate form

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 36 Haskell: Flow of Control Encapsulated Loops — map & filter [3/3]

So Haskell’s map and filter functions—or the equivalent list comprehensions—perform operations that we would use loops for in other PLs.

In particular, map and filter encapsulate loops that process a sequence of values, constructing another sequence of values.

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 37 Haskell: Flow of Control Encapsulated Loops — zip

Haskell also has functions that encapsulate other kinds of loops. For example, zip takes two lists and returns a single list of pairs.

> zip [8,3,7] "sun" [(8,'s'),(3,'u'),(7,'n')

Function zip stops when either of the given lists runs out.

> zip [8,3,7,4] "sunshine" [(8,'s'),(3,'u'),(7,'n'),(4,'s')]

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 38 Haskell: Flow of Control Encapsulated Loops — Fold Operations [1/3]

Yet another kind of loop involves processing a sequence of values and returning a single value. The result might be the sum of all the numbers in the sequence, the greatest value in the sequence, etc. The operation performed by a loop like this is called a fold (or sometimes reduce).

Here is an example of something that is conceptually a fold operation, implemented in a traditional manner in C++. int result = 0; // Will hold sum of items in v for (auto n : v) { result += n; }

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 39 Haskell: Flow of Control Encapsulated Loops — Fold Operations [2/3]

Haskell has a number of fold functions. These include foldl, foldr, foldl1, and foldr1 (the “l” and “r” stand for “left” and “right”). Below, I show a call to each function, with a comment showing what the call computes. foldl (+) 0 [1,2,3,4] -- (((0+1)+2)+3)+4 foldr (+) 0 [1,2,3,4] -- 1+(2+(3+(4+0))) foldl1 (+) [1,2,3,4] -- ((1+2)+3)+4 foldr1 (+) [1,2,3,4] -- 1+(2+(3+4))

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 40 Haskell: Flow of Control Encapsulated Loops — Fold Operations [3/3]

Here are practical examples of Haskell folds.

> let commafy str1 str2 = str1 ++ ", " ++ str2 > foldl1 commafy ["hello","there","everyone"] "hello, there, everyone"

> let bigger a b = if (b > a) then b else a > let maxVal list = foldl1 bigger list > maxVal [5,2,7,3,9,8,4,9,2,1] 9

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 41 Haskell: Flow of Control Other — seq

Function seq is a Haskell primitive that works as a flow-of-control construct. Function seq acts as if it is defined like this: seq x y = y

So seq takes two arguments and returns the second. However, the first argument (x above) will be evaluated first.

Function seq is the unique Haskell primitive that breaks the lazy- evaluation rule; it evaluates something whose value may not be needed. Why would we use seq? It allows use to do eager evaluation; in some cases this can improve performance.

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 42 Haskell: Flow of Control Other — Do-Construction (Preview) [1/2]

A final flow-of-control structure, which we will look at when we study Haskell I/O, is the do-construction. An example: reverseIt = do putStr "Type something: " line <- getLine putStr "What you typed, reversed: " putStrLn (reverse line)

When this executes, it looks like this:

> reverseIt Typed by user Type something: Howdy! What you typed, reversed: !ydwoH

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 43 Haskell: Flow of Control Other — Do-Construction (Preview) [2/2] reverseIt = do putStr "Type something: " line <- getLine putStr "What you typed, reversed: " putStrLn (reverse line)

A do-construction is syntactic sugar around a pipeline of functions. Roughly, each function takes the current state and returns it, possibly modified by an I/O action. Each of the lines above, except the first line, represents an I/O action.

More on this in our next topic: Haskell I/O.

27 Feb 2019 CS F331 / CSCE A331 Spring 2019 44