CS 331 Spring 2016  >  Notes for February 29, 2016


CS 331 Spring 2016
Notes
for February 29, 2016

Haskell: Flow of Control

Key Idea: things that are done with traditional flow-of-control constructs in imperative programming languages may be done differently in Haskell.

Pattern Matching & Recursion

As we have already seen, 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.

[Haskell]

isEmpty [] = True
isEmpty (x:xs) = False

In many of the places we would tend to use an if...else construction in an imperative programming language, we might use pattern matching in Haskell.

In addition, Haskell programs make heavy use of recursion. Recursion can be less costly in Haskell than it is in programming languages like C++, because of Haskell’s tail-call optimization (TCO). TCO means that a tail call does not require additional stack space.

[Haskell]

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

In many of the places we would tend to use a loop in an imperative programming language, we might use recursion in Haskell.

Pattern matching and recursion form a powerful combination. Really, we can do just about anything we need to, using only these two. For example, here is how we can write our own if...else.

[Haskell]

-- myIf condition tVal fVal
-- condition is a Bool. Returns tVal if conditions is True,
-- fVal otherwise.
myIf True tVal fVal = tVal
myIf False tVal fVal = fVal

Note that, due to laziness, only one of tVal and fVal will ever be evaluated. So myIf is actually efficient.

In the first definition above, we do not use fVal. In the second definition, we do not use tVal. The Haskell pattern “_” matches anything, just like a variable name, but it results in no binding. It thus marks an unused parameter.

Here is a rewrite of myIf using this pattern.

[Haskell]

myIf True tVal _ = tVal
myIf False _ fVal = fVal

Here are some uses of myIf

[Haskell]

-- fibo
-- As usual.
fibo n = myIf (n <= 1) n (fibo (n-2) + fibo (n-1))

-- evenOddString
-- Given an integer, returns "even" if it is even, and "odd"
-- if it is odd.
evenOddString n = myIf (n % 2 == 0) "even" "odd"

-- myAbs
-- Given a number, returns its absolute value.
myAbs n = myIf (n >= 0) n (-n)

Selection

myIf, as defined above, is a bit unwieldy. But we do not need it, since Haskell has other built-in selection constructs. The most important is the guards construction. We will also look briefly at the if-then-else and case constructions

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 evaluates to 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"

With guards, we can rewrite function myIf to be a little nicer looking.

[Haskell]

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

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

Haskell’s if-then-else construction has been criticized as being un-Haskell-ish. I have found it natural to use when doing I/O (discussed later); otherwise, I generally prefer to use guards.

Case

Haskell’s case construction is analogous to switch in C++. Here is an example.

[Haskell]

fibo n = case n of
    0 -> 0
    1 -> 1
    _ -> fibo (n-2) + fibo (n-1)

The above is exactly equivalent to

[Haskell]

fibo 0 = 0
fibo 1 = 1
fibo n = fibo (n-2) + fibo (n-1)

Since a case construction can always be replaced by multiple function definitions, and the latter is generally clearer, I almost never use case.

Regardless, case does have an important role in Haskell: it is the actual machinery behind pattern-matching. Multiple function definitions are actually syntactic sugar over a case construction.

Fatal Errors

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, and they may be used inconsistently.

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

Use error only for fatal errors: places where a program should exit. Usually we do then when the program detects a bug in its own code.

[Haskell]

fibo n
    | n < 0      = error "fibo: negative parameter"
    | n <= 1     = n
    | otherwise  = fibo (n-2) + fibo (n-1)

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

Simulating Exceptions

Consider what an exception is in C++. Roughly speaking, it is an alternate return value for a function. The caller may not be able to handle it, in which case it simply passes through the caller—thus being returned by the caller, to its caller—until it reaches code that can deal with it.

So we might be able to simulate an exception by having a special value that a function returns to indicate a problem. If a caller sees this value, it can either handle it, or return the same value to its caller.

Suppose we want to write a square root function that uses this ide. Here is a first try.

[Haskell]

mySqrt1 x
    | x >= 0.0   = sqrt x
    | otherwise  = special_value  -- Not sure what value is

Suppose function foo calls the square root function, but it cannot handle errors. If it sees special_value then it returns that same value to its caller.

[Haskell]

foo x y = foo_helper (mySqrt1 x) y where
    foo_helper special_value _ = special_value
    foo_helper sqx y = ...    -- Does whatever it needs to

But there is a problem here. What should special_value be? We can solve this problem by creating a new type with an added special value in it. Haskell enables this through Maybe types.

A “Maybe” type has a value that is either “Just” followed by a value of some specified type, or “Nothing”. We can use Nothing to indicate an error.

[Haskell]

mySqrt2 :: Double -> (Maybe Double)
mySqrt2 x
    | x >= 0.0   = Just (sqrt x)
    | otherwise  = Nothing

The above function is a little odd, since it does not return the same type as it is given. After all, we might want to take the square root of a square root. But we can fix this.

[Haskell]

mySqrt :: (Maybe Double) -> (Maybe Double)
mySqrt Nothing = Nothing
mySqrt (Just x)
    | x >= 0.0   = Just (sqrt x)
    | otherwise  = Nothing

The above function works as before, but if it is given an error value, then it just passes it on through.

Here is how we could use the above function:

[Interactive Haskell]

> mySqrt (Just 3.0)
Just 1.7320508075688772
> mySqrt (Just (-3.0))
Nothing
> mySqrt (mySqrt (Just (-3.0)))
Nothing

We can write a whole numerical computation package this way. Here is division.

[Haskell]

infixl 7 @/  -- Set left associativity, precedence for @/
(@/) :: (Maybe Double) -> (Maybe Double) -> (Maybe Double)
Nothing @/ _ = Nothing
_ @/ Nothing = Nothing
(Just _) @/ (Just 0.0) = Nothing
(Just x) @/ (Just y) = Just (x / y)

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

Here is how we could use the above:

[Interactive Haskell]

> n3 @/ n2
Left 1.5
> n2 @/ n0
Nothing
> (n2 @/ n0) @/ n3
Nothing

The Haskell Standard Library does include some functionality that can make the above structure a bit prettier. 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 the various fold operations.

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

Or, using a lambda function:

[Haskell]

w = filter (\ k -> mod k 2 == 0) v

And here is our definition.

[Haskell]

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

A final example is fold. This encapsulated-loop idea does not correspond to a list comprehension. A fold—called reduce in some contexts—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 construction.

Here is an example:

[Haskell]

reverseIt = do
    putStr "Type something: "
    line <- getLine
    putStrLn ""
    putStr "Your line, reversed: "
    putStrLn (reverse line)

Here is how it works (user input is in boldface):

[Interactive Haskell]

> reverseIt
Type something: Howdy!

Your line, reversed: !ydwoH

A do construction 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 flow.hs for Haskell source code related to today’s lecture.