Haskell/More on functions

From Wikibooks, open books for an open world
< Haskell
Jump to: navigation, search

As functions are absolutely essential to functional programming, there are several nice features that make using functions easier.

let and where revisited[edit]

First, a few extra words about let and where, which are useful to make local function definitions. A function like addStr from the List processing chapter...

addStr :: Float -> String -> Float
addStr x str = x + read str
sumStr :: [String] -> Float
sumStr = foldl addStr 0.0

... can be rewritten using local bindings in order to reduce clutter on the top level of the program (which makes a lot of sense assuming addStr is only used as part of sumStr). We can do that either with a let binding...

sumStr =
   let addStr x str = x + read str
   in foldl addStr 0.0

... or with a where clause...

sumStr = foldl addStr 0.0
   where addStr x str = x + read str

... and the difference appears to be only one of style - bindings coming either before or after the rest of the definition. The relation between let and where, however, is similar to the one between if and guards, in that a let...in construct is an expression while a where clause isn't. That means we can embed a let binding, but not a where clause, in a complex expression in this way:

f x =
    if x > 0
        then (let lsq = (log x) ^ 2 in tan lsq) * sin x
        else 0

The expression within the outer parentheses is self-contained, and evaluates to the tangent of the square of the logarithm of x. Note that the scope of lsq does not extend beyond the parentheses; and therefore changing the then-branch to

        then (let lsq = (log x) ^ 2 in tan lsq) * (sin x + lsq)

wouldn't work - we would have to drop the parentheses around the let.

Still, where clauses can be incorporated into case expressions:

describeColour c = 
   "This colour "
   ++ case c of
          Black -> "is black"
          White -> "is white"
          RGB red green blue -> " has an average of the components of " ++ show av
             where av = (red + green + blue) `div` 3
   ++ ", yeah?"

In this example, the indentation of the where clause sets the scope of the av variable so that it only exists as far as the RGB red green blue case is concerned. Placing it at the same indentation of the cases would make it available for all cases. Here is an example with guards:

doStuff :: Int -> String
doStuff x
  | x < 3     = report "less than three"
  | otherwise = report "normal"
    report y = "the input is " ++ y

Note that since there is one equals sign for each guard there is no place we could put a let expression which would be in scope of all guards, as the where clause is. Here we have a situation in which where is particularly convenient.

Anonymous Functions - lambdas[edit]

An alternative to creating a private named function like addStr is to create an anonymous function, also known as a lambda function. For example, sumStr could have been defined like this:

sumStr = foldl (\x str -> x + read str) 0.0

The expression in the parentheses is a lambda function. The backslash is used as the nearest ASCII equivalent to the Greek letter lambda (λ). This example is a lambda function with two arguments, x and str, which evaluates to "x + read str". So, the sumStr presented just above is precisely the same as the one that used addStr in a let binding.

Lambdas are handy for writing one-off functions to be used with maps, folds and their siblings, especially where the function in question is simple - as cramming complicated expressions in a lambda can hurt readability.

Since variables are being bound in a lambda expression (to the arguments, just like in a regular function definition), pattern matching can be used in them as well. A trivial example would be redefining tail with a lambda:

tail' = (\(_:xs) -> xs)

Operators and sections[edit]

As noted in a number of occasions, operators such as the arithmetical ones can be used surrounded in parentheses and used prefix, like other functions:

2 + 4
(+) 2 4

Generalizing that point, we can now define the term "operator" clearly: as far as Haskell is concerned it's a function with two arguments and a name consisting entirely of non-alphanumeric characters. Unlike other functions, operators can be used infix straight away. We can define new operators in the usual way; just don't use any alphanumeric characters. For example, here's the set-difference definition from Data.List:

(\\) :: (Eq a) => [a] -> [a] -> [a]
xs \\ ys = foldl (\zs y -> delete y zs) xs ys

Note that, aside from just using operators infix, you can define them infix as well. This is a point that most newcomers to Haskell miss. I.e., although one could have written:

(\\) xs ys = foldl (\zs y -> delete y zs) xs ys

It's more common to define operators infix. However, do note that in type declarations, you have to write them with the parentheses.

Sections are a nifty piece of syntactical sugar that can be used with operators. An operator within parentheses and flanked by one of its arguments...

(2+) 4
(+4) 2

... is a new function in its own right. (2+), for instance, has the type (Num a) => a -> a. We can pass sections to other functions, e.g. map (+2) [1..4] == [3..6]. For another example, we can add an extra flourish to the multiplyList function we wrote back in More about lists:

multiplyList :: Integer -> [Integer] -> [Integer]
multiplyList m = map (m*)

If you have a "normal", prefix function, and want to use it as an operator, simply surround it by backticks:

1 `elem` [1..4]

This is called making the function infix. It's normally done for readability purposes: 1 `elem` [1..4] reads better than elem 1 [1..4]. You can also define functions infix:

elem :: (Eq a) => a -> [a] -> Bool
x `elem` xs = any (==x) xs

But once again notice that in the type signature you have to use the prefix style.

Sections even work with infix functions:

(1 `elem`) [1..4]
(`elem` [1..4]) 1

You can only make binary functions (that is, those that take two arguments) infix. Think about the functions you use, and see which ones would read better if you used them infix.

  • Lambdas are a nice way to avoid defining unnecessary separate functions. Convert the following let- or where-bindings to lambdas:
    • map f xs where f x = x * 2 + 3
    • let f x y = read x + y in foldr f 1 xs
  • Sections are just syntactic sugar for lambda operations. I.e. (+2) is equivalent to \x -> x + 2. What would the following sections 'desugar' to? What would be their types?
    • (4+)
    • (1 `elem`)
    • (`notElem` "abc")