CS 331 Spring 2009  >  Additional Lecture Notes for Monday, February 23, 2009

CS 331 Spring 2009
Additional Lecture Notes for Monday, February 23, 2009

Some of this material is review from the January 28 lecture.

Haskell: The Language

We now begin a brief study of the Haskell programming language.

Haskell, named for logician Haskell Curry, is a relatively popular functional language. It was introduced in 1990, in order to consolidate various ideas in field of functional languages into one language that could serve as a platform for research and experimentation. Haskell was standardized in 1998, the standard being known as Haskell 98.

Functional

Haskell is functional. This means that all it can do is compute values. Haskell has no statements (in the normal sense of the word), no side effects, and no mutable (changeable) values.

For example, either line of the following code is fine by itself, but together they do not compile.

x = 1
x = 2  -- error: cannot change x

To emphasize that fact that there are absolutely no side effects, Haskell, and similar languages, are often called purely functional, or simply pure. Languages like C++, in contrast, are known as imperative.

Functional languages typically have first-class functions. This means that functions can be treated like any other data type. They can be passed as parameters and stored in data structures. Also, functions can create functions and return them. As a result, a Haskell program often has a very different design from a C++ program that accomplishes the same task.

For example, the function addOneFirst, defined below, takes a function f and returns a new function whose value is obtained by adding one to the parameter, and then applying f.

addOneFirst f = g where
    g x = f (x + 1)

Now, suppose we do this.

dubl x = 2 * x

qq = addOneFirst dubl

Now if we type “qq 3” at the Hugs prompt, the result is “8”, since 2 * (3 + 1) = 8.

Typing

Like C++, Haskell makes use of static typing. This means that type checking is done at compile time. In contrast, languages like Python use dynamic typing, in which type checking is primarily a runtime activity.

Unlike C++, Haskell has implicit typing. The compiler can usually figure out what type something needs to be, without the programmer telling it.

For example consider the following code.

dubl x = 2 * x

dubla :: Num a => a -> a
dubla x = 2 * x

dublb :: Integer -> Integer
dublb x = 2 * x

Here we have three functions, defined the same way, that differ only in how their types are declared.

Function dubl is implicitly typed. Typing “:t dubl” at the Hugs prompts gives “dubl :: Num a => a -> a”, which means that dubl is a function that takes some type and returns the same type (“a -> a”), and furthermore that this type must be numeric (“Num a =>”). Thus, we can call function dubl with an Integer or a Double, but not a string.

Function dubla is the same as dubl, except that its type is given explicitly. For the given code, this changes nothing. However, if the definition of dubla were changed so that it returned some other type, then it would not compile. Change “2 * x” to “(2 * x, 0)” for an example.

Function dublb is like dubla, except that its type is more restrictive. While dubla will take a value of any numeric type, and return the same type, function dublb will only take and return a value of type Integer. Thus, “dubla 3.5” would compile, while “dublb 3.5” would not.

Laziness

Haskell does lazy evaluation. This means that values are not evaluated until they need to be. C++, in contrast uses strict evaluation.

An interesting consequence of lazy evaluation is the possibility of infinite data structures. Consider the following code.

a = [10 .. 20]
b = [10 ..]

Variable “a” is a list of the integers from 10 to 20. Variable “a” is a list of all integers 10 and up. The operator !! does look-up by index, like the bracket operator in C++. Thus, “b !! 100000”, after a short delay, gives “100010”. The reason that we can have an infinite list, is that its contents are not evaluated until they need to be. For the majority of the items in list b, this means never.

Haskell: Basics

To write a function in Haskell, give its name, followed by an equals sign, followed by an expression. If the function has parameters, give them after the name. For example:

three = 3

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

As illustrated above, function definitions may use pattern matching. When function fibo is called, the first definition whose parameter matches the pattern will be used.

We may continue a function definition on another line, as long as we indent the line more.

triplea x = 3 * x  -- correct

tripleb x =
  3 * x            -- correct

triplec x =
3 * x              -- INCORRECT; DOES NOT COMPILE

tripled
 x
    =
   3
  *
   x               -- correct, but icky

We can use where to define local variables and functions (not that there is much difference between these two). Here is a much faster fibo.

fibo n = snd (fibpair n) where
    fibpair 0 = (1, 0)
    fibpair n = (b, a+b) where
        (a, b) = fibpair (n-1)

The first non-space character after where gives the new indentation level, which continues until a line with a nonspace character to the left of that level is found.

We use (......) for a tuple. Tuples can hold 2 items, 3, 4, etc., and the items can be of different types. Special functions fst and snd return the first and second items (respectively) of a 2-item tuple (i.e., a pair).


CS 331 Spring 2009: Additional Lecture Notes for Monday, February 23, 2009 / Updated: 25 Feb 2009 / Glenn G. Chappell / ffggc@uaf.edu Valid HTML 4.01!