The most basic way of defining a function in Haskell is to ``declare'' what it does. For example, we can write:
double :: Int -> Int double n = 2*n
Here, the first line specifies the type of the function and the second line tells us how the output of double depends on its input. We shall see soon that the ``definition'' of double is computed by treating the equality as a rule for rewriting expressions. We shall also see that, in Haskell, the type of a function can be inferred automatically, so we can omit the type when defining a function.
We are not restricted to having single line definitions for functions. We can use multiple definitions combined with implicit pattern matching. For instance consider the function:
power :: Float -> Int -> Float power x 0 = 1.0 power x n = x * (power x (n-1))
Here, the first equation is used if the second argument to power is 0. If the second argument is not 0, the first definition does not ``match'', so we proceed to the second definition. When multiple definitions are provided, they are scanned in order from top to bottom.
Here is another example of a function specified via multiple definitions, using pattern matching.
xor :: Bool -> Bool -> Bool xor True True = False xor False False = False xor x y = True
Here, the first two lines explicitly describe two interesting patterns and the last line catches all combinations that do not match.
Another way to provide multiple definitions is to use conditional guards. For example:
max :: Int -> Int -> Int max i j | (i >= j) = i | (i < j) = j
In this definition the vertical bar indicates a choice of definitions and each definition is preceded by a boolean condition that must be satisfied for that line to have effect. The boolean guards need not be exhaustive or mutually exclusive. If no guards are true, none of the definitions are used. If more than one guard is true, the earliest one is used.
It is important to note that all variables used in patterns are substituted independently--we cannot directly ``match'' arguments in the pattern by using the same variable for two arguments to implicitly check that they are the same. For instance, the following will not work.
isequal :: Int -> Int -> Bool isequal x x = True isequal y z = False
Instead, we must write
isequal :: Int -> Int -> Bool isequal y z | (y == z) = True | (y /= z) = False
or, more succinctly,
isequal :: Int -> Int -> Bool isequal y z = (y == z)
When using conditional guards, the special guard otherwise can be used as a default value if all other guards fail, as shown in the following example.
max3 i j k | (i >= j) && (i >= k) = i | (j >= k) = j | otherwise = k
Basic types can be combined into -tuples--for instance:
-- (x,y) :: (Float,Float) represents a point in 2D distance :: (Float,Float) -> (Float,Float) -> Float distance (x1,y1) (x2,y2) = sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1))
The first line in the definition above is a comment. Observe the use of pattern matching to directly extract the components of the tuples being passed to distance.
Auxiliary functions can be locally defined using let or where. The two notations are quite similar in effect, except that let can be nested while where is only allowed at the top level of a function definition. Formally, let forms part of the syntax of Haskell expressions while where is part of the syntax of function declarations. A definition using where can be used across multiple guarded options. Each choice in a guarded set of options is an independent Haskell expression and a definition using let is restricted to the expression in which it occurs, so we have to use a separate let for each guarded clause.
distance (x1,y1) (x2,y2) = let xdistance = x2 - x1 ydistance = y2 - y1 sqr z = z*z in in sqrt((sqr xdistance) + (sqr ydistance))
distance (x1,y1) (x2,y2) = sqrt((sqr xdistance) + (sqr ydistance)) where xdistance = x2 - x1 ydistance = y2 - y1 sqr z = z*z