Haskell: Flow of Control continued Haskell: I/Ochappell/class/2017_spr/... · Haskell: Flow of Control —Pattern Matching, Recursion, Lazy Eval. Haskell includes the versatile combination
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Haskell: Flow of ControlHaskell: I/O
CS F331 Programming LanguagesCSCE A331 Programming Language ConceptsLecture SlidesWednesday, March 1, 2017
Glenn G. ChappellDepartment of Computer ScienceUniversity of Alaska [email protected]
Function definition: what looks like a function call, an equals sign, and an expression for the value of the function. Pattern matching is used. Introduce local definitions with where.
factorial 0 = 1factorial n = n * factorial prev where
prev = n-1
We can also define new infix binary operators.
a +$+ b = 2*a + b
1 Mar 2017 CS F331 / CSCE A331 Spring 2017
Patterns
Local definition
2
ReviewHaskell: Functions [2/2]
Currying: simulating a multiparameter function using a single parameter function that returns a function.
sub a b = a-bsub 5 2 -- Returns 3sub_from_5 = sub 5sub_from_5 2 -- Returns 3
1 Mar 2017 CS F331 / CSCE A331 Spring 2017 3
ReviewHaskell: Lists [1/3]
A Haskell list holds an arbitrary number of data items, all of the same type. A list literal uses brackets and commas.
["hello", "there"] -- List of two String values[[1], [], [1,2,3,4]] -- List of lists of Integer[1, [2, 3]] -- ERROR; types differ[1, 3 ..] -- Infinite list
Haskell has three list primitives.1. Construct an empty list: []2. Cons: list from first item, list of other items: head:tail3. Pattern matching for lists.
1 Mar 2017 CS F331 / CSCE A331 Spring 2017 4
ReviewHaskell: Lists [2/3]
Haskell has list comprehensions.
> [ x*y | x <- [3, 2, 1], y <- [10, 11, 12] ][30,33,36,20,22,24,10,11,12]
> [ x*x | x <- [1..6] ][1,4,9,16,25,36]
> [ x | x <- [1..20], x `mod` 2 == 1][1,3,5,7,9,11,13,15,17,19]
1 Mar 2017 CS F331 / CSCE A331 Spring 2017 5
ReviewHaskell: Lists [3/3]
A function that takes a list will often be recursive. Such a function will usually be organized as follows.§ The version that handles the empty list ([]) will be the base case.§ The version that handles nonempty lists (b:bs) will be the recursive
case. This will do a computation involving the head of the list (b) and make a recursive call with the tail (bs).
myFilter p [] = [] -- p for predicate: function-- returning Bool
myFilter p (x:xs) = if (p x) then x:restelse rest where
rest = myFilter p xs
1 Mar 2017 CS F331 / CSCE A331 Spring 2017 6
ReviewHaskell: 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 imperative PLs.
Key Idea. Things that are done with traditional flow-of-control constructs in imperative programming languages are often done differently in Haskell.
1 Mar 2017 CS F331 / CSCE A331 Spring 2017
For code, see flow.hs.
7
ReviewHaskell: Flow of Control — Pattern Matching, Recursion, Lazy Eval.
Haskell includes the versatile combination of pattern matching, recursion, and lazy evaluation.
Examples:
-- myIf – an if-else construct we can write ourselves.myIf True tVal _ = tValmyIf False _ fVal = fVal
-- listFrom n - returns infinite list [n, n+1, n+2, …].listFrom n = n:listFrom (n+1)
The lack of a base case is not a problem, thanks to lazy evaluation.The above code uses corecursion:
§ A stream of values is generated recursively.§ The recursion terminates when no more values are needed.
1 Mar 2017 CS F331 / CSCE A331 Spring 2017 8
ReviewHaskell: Flow of Control — Selection: Guards
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.
1 Mar 2017 CS F331 / CSCE A331 Spring 2017 9
Haskell: Flow of ControlEncapsulated 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
1 Mar 2017 CS F331 / CSCE A331 Spring 2017
continued
For code, see flow.hs.
10
Haskell: Flow of ControlEncapsulated Loops — map & filter [1/3]
Consider the following C++ code. v is a vector<int>.
vector<int> 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) vw = [ n `mod` 3 | n <- v ] -- Alternate form
1 Mar 2017 CS F331 / CSCE A331 Spring 2017 11
Haskell: Flow of ControlEncapsulated Loops — map & filter [2/3]
Another C++ snippet:
vector<int> w;for (auto n : v){
if (n > 6)w.push_back(n);
}
And the equivalent Haskell:
w = filter (> 6) vw = [ n | n <- v, n > 6 ] -- Alternate form
1 Mar 2017 CS F331 / CSCE A331 Spring 2017 12
Haskell: Flow of ControlEncapsulated 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.
1 Mar 2017 CS F331 / CSCE A331 Spring 2017 13
Haskell: Flow of ControlEncapsulated 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] "dog"[(8,'d'),(3,'o'),(7,'g')
Function zip stops when either of the given lists runs out.
> zip [8,3,7] "doggies"[(8,'d'),(3,'o'),(7,'g')
1 Mar 2017 CS F331 / CSCE A331 Spring 2017 14
Haskell: Flow of ControlEncapsulated 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 a fold operation in C++.
int result = 0; // Will hold sum of items in vfor (auto n : v){
result += n;}
1 Mar 2017 CS F331 / CSCE A331 Spring 2017 15
Haskell: Flow of ControlEncapsulated 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))
1 Mar 2017 CS F331 / CSCE A331 Spring 2017 16
Haskell: Flow of ControlEncapsulated Loops — Fold Operations [3/3]
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.
In many PLs, conversion to & from string is mixed up together with I/O. This makes sense, because when we do text I/O, the values we send and receive must, in the end, be composed of characters.
The above illustrates an interesting feature of Haskell.Both Haskell and C++ support function overloading: creating
distinct functions with the same name. In C++ we can overload only on the number and types of parameters; we must be able to choose which function to use based only on the number and types of the parameters.
But in Haskell, we can overload on the return type. There are various versions of function read; all have the same name, and all take a single String parameter. But they have different return types; Haskell can determine which to use based on this.
1 Mar 2017 CS F331 / CSCE A331 Spring 2017 26
Haskell: I/OSimple Output [1/3]
I/O necessarily involves side effects: changes that are visible outside a function. But Haskell does not allow side effects.
We do I/O in Haskell as follows: the return value of a program is a description of the side effects the program would like to do. The runtime environment will perform the side effects.
A side effect description is stored as a Haskell I/O action. For example, here is function putStr.
> :t putStrputStr :: String -> IO ()
Function putStr takes a String and returns an I/O action representing printing the String to the standard output.
1 Mar 2017 CS F331 / CSCE A331 Spring 2017
Return type: I/O action
27
Haskell: I/OSimple Output [2/3]
Entering an expression whose value is an I/O action at the GHCiprompt, results in the I/O being performed.
> putStrLn "Hello!"Hello!> putStrLn $ show $ map (\ x -> x*x) [1, 2, 3][1,4,9]
In a complete program, an I/O action can be returned by main. Here is a Haskell hello-world program.
main = putStrLn "Hello, world!"
1 Mar 2017 CS F331 / CSCE A331 Spring 2017
Like putStr, but add a newline at the end of the given String.
28
Haskell: I/OSimple Output [3/3]
Combine multiple I/O actions into a single I/O action using the >>operator.
We will eventually discuss a nicer way to combine I/O actions. But when we use it, this is what is
going on under the hood.
29
Haskell: I/OSimple Input [1/5]
The full story on I/O actions is that an I/O action represents:§ a description of a sequence of side effects, and§ a wrapped value.
Recall the “()” in the type of putStr.
putStr :: String -> IO ()
“()” means that the I/O action returned by putStr wraps a “nothing” value.
1 Mar 2017 CS F331 / CSCE A331 Spring 2017 30
Haskell: I/OSimple Input [2/5]
When we do input, we use an I/O action that wraps the value we are inputting.
> :t getLinegetLine :: IO String
getLine returns an I/O action that inputs a line of text from the standard input. The wrapped value is a String representing the line of text (without the ending newline).
Now, how do we access the wrapped String?
1 Mar 2017 CS F331 / CSCE A331 Spring 2017 31
Haskell: I/OSimple Input [3/5]
Using the >>= operator, we can combine§ an I/O action wrapping a value, and§ a function that takes such a value and returns an I/O action.
The wrapped value is passed to the function.The result is an I/O action that includes the side effects of both the
original I/O action and the one returned by the function. The value it wraps is that wrapped by the I/O action returned by the function.
1 Mar 2017 CS F331 / CSCE A331 Spring 2017
I/O ActionValue Function I/O
ActionValueThe >>= operator passes the wrapped value …
… to the given function.
Result: an I/O action including the side effects of both of these, wrapping whatever value the second I/O action wraps.
32
Haskell: I/OSimple Input [4/5]
For example, getLine returns an I/O action wrapping a String. Function putStrLn takes a String and returns an I/O action.
> getLine >>= putStrLnHowdy!Howdy!
1 Mar 2017 CS F331 / CSCE A331 Spring 2017
Typed by user
I/O ActionValue Function I/O
ActionValueThe >>= operator passes the wrapped value …
… to the given function.
Result: an I/O action including the side effects of both of these, wrapping whatever value the second I/O action wraps.
33
Haskell: I/OSimple Input [5/5]
I/O actions involving multiple side effects work, too.
> (putStr "Type something: " >> getLine) >>= putStrLnType something: I like hamsters!I like hamsters!
We can give the parameter of putStrLn a name:
> getLine >>= (\ line -> putStrLn line)Hamsters rule ...Hamsters rule ...> getLine >>= (\ line -> putStrLn (reverse line))... this planet and others like it..ti ekil srehto dna tenalp siht ...
1 Mar 2017 CS F331 / CSCE A331 Spring 2017
Next:the “nicer way”.
Same as putStrLn(right?)
Parentheses are actually unnecessary. The operators have equal precedence and are both left-associative.
34
Haskell: I/Odo Construction [1/2]
Haskell’s do construction offers a less messy way to write I/O.The keyword do is followed by an indented block. I/O actions in the
block are combined into a single I/O action. Internally, this is done using the >> and >>= operators.
Using operators:putStr s >> putStrLn t
Using a do construction:do
putStr sputStrLn t
1 Mar 2017 CS F331 / CSCE A331 Spring 2017
Using operators:getLine >>=
(\ line -> putStrLn line)
Using a do construction:do
line <- getLineputStrLn line
35
Variable linecan be used in the rest of the do block.
Haskell: I/Odo Construction [2/2]
Here is a Haskell program that prompts the user for a line of text and then prints the length of the line and the line itself, backwards.
main = doputStr "Type some text: "line <- getLineputStrLn ""putStrLn $ "Line length: " ++ show (length line)putStrLn ""putStr "Here is your text, backwards: "putStrLn $ reverse line