Haskell: Flow of Control
Total Page:16
File Type:pdf, Size:1020Kb
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 functional programming 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 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. Pattern matching 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 & Recursion [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, Lazy Evaluation [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 C++, 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 corecursion: § 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.