CS 331 Spring 2013  >  Lecture Notes for Wednesday, February 20, 2013

CS 331 Spring 2013
Lecture Notes for Wednesday, February 20, 2013

Haskell: Functions

Defining Functions & Operators

To define an ordinary function in Haskell, give the function’s name, followed by parameter names, all separated by blanks, then an equals sign (“=”) and an expression giving the return value of the function.

[Haskell]

addem a b = a+b

A function call looks much like the part of the definition before the equals sign.

[Interactive Haskell]

> addem 4 10
14

Haskell also allows the definition of new binary infix operators. The name of an infix operator must consist of a sequence of one or more of the following twenty symbols, and it must not begin with a colon (“:”).

! # $ % & * + . / < = > ? @ \ ^ | - ~ :

Here is a definition:

[Haskell]

a +$+ b = 2*(a+b)

And a call:

[Interactive Haskell]

> 7 +$+ 1
16

Precedence and associativity can be set (look it up if you are interested).

Haskell functions and operators may be callable on multiple types. This is handled a bit like C++ templates, but with stricter type checking. For example, look at the type of function addem.

[Interactive Haskell]

> :t addem
addem :: Num a => a -> a -> a

The part at the right, “a -> a -> a”, means that addem is a function that takes two parameters of the same type (called “a” here), and returns a value of that same type. The earlier part, “Num a =>”, means that a must be a numeric type. Thus:

[Interactive Haskell]

> addem 1 2
3
> addem 1.2 2.3
3.5
> addem "abc" "def"  -- Not numeric
ERROR ...

Num is an example of a Haskell type class, a collection of types that all support a common interface. Every type in type class Num supports equality testing, addition, subtraction, and multiplication (but not necessarily division; there is no built-in integer division operator in Haskell, although function div does integer division).

The above facility for calling the same function with different types still executes the same code for each type. Haskell does support function and operator overloading in a way that allows for different code to be executed for different types. This is part of the type class concept; we will not discuss this now.

Function Application & Currying

Function application in Haskell is an invisible operator. This operator has high precedence, and it is left-associative.

[Interactive Haskell]

> addem 1 7
8
> (addem 1) 7
8
> addem (1 7)
ERROR ...

In the first two lines above, (addem 1) is actually a function, which is applied to 7.

So addem is really a function that returns a function. Pass addem the argument 1, and it returns a function that adds 1 to things. Pass 7 to that function, and the result is 8.

This is called currying (after Haskell Curry again). The reality is that every Haskell function has exactly one argument. We can simulate functions with two (or more) arguments using a function that returns a function. So the “a -> a -> a” that we saw above really means “a -> (a -> a)”: the function takes an a and returns another function, which takes an a and returns an a.

Lambda Functions

First-class values generally do not have to be named. For example suppose we wish to output 3+4. In C++, we can give this a name:

[C++]

int sum = 3+4;  // The name is "sum"
cout << sum << endl;

Or we can leave it unnamed:

[C++]

cout << 3+4 << endl;

C++ traditionally does not allow for unnamed functions (although C++11 has them). In Haskell, a function is an ordinary value; unnamed functions are common. Indeed, we have already seen one: (addem 1).

A general syntax for making unnamed functions is a lambda expression. This begins with a backslash (“\”)—because it looks a bit like a Greek lambda (“λ”). Next come parameter names, an arrow (“->”), and an expression for the return value of the function.

For example, here is the definition of a function to square a number.

[Haskell]

square x = x*x

Here it is as a lambda expression.

[Haskell]

\ x -> x*x

Both of these can be used.

[Interactive Haskell]

> square 5
25
> (\ x -> x*x) 5
25

These means we could define a square function two different ways:

[Haskell]

square x = x*x
square' = \ x -> x*x

Indeed, the first form above is essentially just shorthand for the second (without the “prime”).

We can also define addem in this way.

[Haskell]

addem = \ x y -> x+y

But remember currying. Function addem really has just one parameter, and it returns a function:

[Haskell]

addem a = \ y -> a+y

Alternatively:

[Haskell]

addem = \ x -> (\ y -> x+y)

Lambda functions allow us to do some things more concisely.

[Haskell]

square x = x*x
sqlist = map square [1,2,3,4]  -- sqlist is [1,4,9,16]

Or:

[Haskell]

sqlist = map (\ x -> x*x) [1,2,3,4]  -- sqlist is [1,4,9,16]

Higher-Order Functions

A higher-order function is a function that deals with functions. For example, here is the definition of a higher-order function rev.

[Haskell]

rev f a b = f b a

Think of rev as a one-argument function. It takes a two-argument function, and returns another one with its arguments reversed.

[Haskell]

sub a b = a-b
rsub = rev sub

Now the value of rsub x y is y-x.

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


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