CS 331 Spring 2013 > Lecture Notes for Wednesday, February 20, 2013 |
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 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
.
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]
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.
ggchappell@alaska.edu