CS 331 Spring 2013  >  Lecture Notes for Monday, February 25, 2013

CS 331 Spring 2013
Lecture Notes for Monday, February 25, 2013

Haskell: Flow of Control

A key idea: things that are done with flow-of-control in other languages may be done differently in Haskell.

Pattern Matching & Recursion

We have already seen this. Recursion, particularly tail recursion, is our fundamental replacement for iteration; it will often be optimized to avoid excessive stack usage.

[Haskell]

myLen [] = 0
myLen (x:xs) = 1 + myLen xs

Selection

Guards

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

\[ \mathrm{myAbs}(x) = \begin{cases} x, & \text{if } x \ge 0.\\ -x, & \text{otherwise}. \end{cases} \]

In Haskell:

[Haskell]

myAbs x
  | x >= 0     = x
  | otherwise  = -x

Above, each vertical bar is followed by a boolean expression. The value used is that corresponding to the first such expression that is true. We typically want the last value to handle all remaining cases (as above), and so we want a boolean expression that is always true. “True” would work fine; “otherwise” is simply another way to say the same thing.

[Interactive Haskell]

> otherwise
True

Another example:

[Haskell]

stringSign x
  | x > 0      = "positive"
  | x < 0      = "negative"
  | otherwise  = "zero"

Guards give us a way to do an if-then-else. We could make it a three-argument function.

[Haskell]

myIf cond tval fval
  | cond       = tval
  | otherwise  = fval

Note that, due to laziness, only one of tval and fval will be evaluated.

If-Then-Else

Haskell actually does have an if-then-else construction. We could replace “myIf cond tval fval” with the following.

[Haskell]

if cond then tval else fval

This construction has been criticized as being un-Haskell-ish. Regardless, it is part of the language.

Error Handling

Note: The Haskell Standard Library and documentation tosses around the terms “error” and “exception” rather loosely; they may not be used in the precise senses that you are familiar with.

Functions like “assert

The C assert macro is given a boolean expression. It crashes the program if the expression is false.

A somewhat similar idea can be found in Haskell. Function error is given a string argument. Whenever this function is executed, it crashes, with the given string as an error message. Function error has a return value of an arbitrary type, so that it may be used anywhere Haskell requires a value.

[Interactive Haskell]

> :t error
error :: [Char] -> a

Similar to error is undefined. This also crashes, but it takes no argument; a standard error message is printed.

Simulating Exceptions

The Haskell 98 standard does not include exceptions as a separate flow-of-control mechanism. But they can be simulated by allowing for multiple kinds of return values.

An “Either” type has a value that is either “Left” followed by a value of one type, or “Right” followed by a value of a different type. An example:

[Haskell]

stringOrNum :: Num a => String -> a -> Either String a
stringOrNum stringFlag value
  | stringFlag  = Left (show value)
  | otherwise   = Right value

Function show converts a value to a String.

Here is how it works:

[Interactive Haskell]

> stringOrNum True 3.2
Left "3.2"
> stringOrNum False 3.2
Right 3.2

Now we can simulate exceptions by having a function return its normal return value, as a Left value, or an exception, as a Right value. Other functions can then propagate exceptions.

Here is a simple example. My Left return values will be Double values. my Right exceptions will be String values intended for outputting to the user. I will use standard arithmetic operator names, prefixed with “@”.

[Haskell]

infixl 6 @-
(Right s) @- _ = Right s  -- Propagate exception
_ @- (Right s) = Right s  -- Propagate exception
(Left x) @- (Left y) = Left (x-y)
                          -- No exception; compute value

infixl 7 @/
(Right s) @/ _ = Right s  -- Propagate exception
_ @/ (Right s) = Right s  -- Propagate exception
(Left x) @/ (Left 0) = Right "Division by zero!"
                          -- Raise exception
(Left x) @/ (Left y) = Left (x/y)
                          -- No exception; compute value

-- Some numerical values, for convenience
n0 = Left 0.0
n1 = Left 1.0
n2 = Left 2.0
n3 = Left 3.0

Here is how it works:

[Interactive Haskell]

> n3 @/ n2
Left 1.5
> n2 @- n2
Left 0.0
> n3 @/ (n2 @- n2)
Right "Division by zero!"
> n0 @- n3 @/ (n2 @- n2) @- n1
Right "Division by zero!"

The Haskell Standard Library does include some functionality that can simplify the above structure a bit (look into the “Error monad”). Also, using type classes we could overload the regular arithmetic operators, thus avoiding the “@” prefix. But the above does show the essential idea.

Encapsulated Loops

A very important flow-of-control idea in functional programming is that many loops can be encapsulated as functions. We will look at three such ways to encapsulate loops: map, filter, and fold.

For example, suppose we have the following C++ function.

[C++]

int square(int n)
{
   return n*n;
}

Now we want to do the following, given a vector<int> v.

[C++]

vector<int> w;
for (int i = 0; i != v.size(); ++i)
    w.push_back(square(v[i]));

So we are applying function square to each item of the first vector.

In Haskell, we can define square as follows.

[Haskell]

square n = n*n;

Now, given a list v, we can apply function square to each item.

[Haskell]

w = [ square k | k <- v ]

Here is another way to say the same thing.

[Haskell]

w = map square v

Function map takes a function and a list. It applies the function to every item of the list, and returns the results as a new list.

We can define map as follows (I say “myMap” below to avoid interfering with a Standard Library function.)

[Haskell]

myMap f [] = []
myMap f (x:xs) = f x : myMap f xs

We see that function map encapsulates a certain kind of loop—using a construct that is not flow-of-control at all, but rather a function.

Similarly, we can grab only certain items from a list.

[Haskell]

w = [ k | k <-v, mod k 2 == 0 ]

This idea is encapsulated in function filter.

[Haskell]

isEven k = (mod k 2 == 0)
w = filter isEven v

And here is our definition.

[Haskell]

myFilter f [] = []
myFilter f (x:xs)
  | f x        = x:therest
  | otherwise  = therest where
  therest = myFilter f xs

A final example is “fold”. This encapsulated-loop idea does not correspond to a list comprehension. A fold—also called reduce—is when a single value is computed based on a sequence (for example, the sum of the sequence, or the maximum value).

Haskell function foldl takes a two-argument function, a starting value, and a list. For example, the following two expressions are essentially the same.

[Interactive Haskell]

> foldl (+) 0 [2,5,4,8]
19
> (((0+2)+5)+4)+8  -- Same as above
19

The “0” in the second expression above corresponds to the second parameter of foldl.

Function foldr is similar, except that it groups things differently.

[Interactive Haskell]

> foldr (+) 0 [2,5,4,8]
19
> 2+(5+(4+(8+0)))  -- Same as above
19

Functions foldl1 and foldr1 are similar, except that they do not require a starting value. Instead, they use the first or last item of the list as the starting value. Thus, they cannot be called on empty lists.

[Interactive Haskell]

> foldl1 (+) [2,5,4,8]
19
> ((2+5)+4)+8  -- Same as above
19
> foldr1 (+) [2,5,4,8]
19
> 2+(5+(4+8))  -- Same as above
19

As another example, here is how to compute a maximum using a fold.

[Haskell]

maxVal xs = foldl1 bigger xs where
    bigger x y
      | x > y      = x
      | otherwise  = y

There are various other kinds of encapsulated loops (e.g., look into “zip”), but a large number of loops can be done elegantly as maps, filters, or folds.

Preview: do

A final flow-of-control structure, which we will look at when we study Haskell I/O, is the do block.

Here is an example:

[Haskell]

reverseIt = do
    putStr "Type something: "
    line <- getLine
    putStr "You typed (backwards): "
    putStrLn (reverse line)

Here is how it works:

[Interactive Haskell]

> reverseIt
Type something: Howdy!
You typed (backwards): !ydwoH

A “do” block is syntactic sugar around a pipeline of functions. Essentially, each function takes the current state as an argument and returns the new state, possibly modified by an I/O action. For example, each of the indented lines above represents an I/O action. We will discuss this idea further next time.

See haskell_flow.hs for Haskell source code related to today’s lecture.


CS 331 Spring 2013: Lecture Notes for Monday, February 25, 2013 / Updated: 27 Feb 2013 / Glenn G. Chappell / ggchappell@alaska.edu