3.3 What a Function Returns

In this section we learn about return-values of functions.

3.3.1 The Final Expression Evaluated

Let’s write a small function to raise a number to a power:7

pow <- function(x,y) {
  x^y
}

Check to see that it works:

pow(2,3)
## [1] 8

All seems well.

If we like we can assign the result of pow() to some variable, for use later on:

a <- pow(2,4)
cat("I have", a, "cats.")
## I have 16 cats.

In computer programming parlance, pow(x, y) is said to return the numerical value x^y: pow(2,3) returns 8, pow(2,4) returns 16, and so on.

In R, what a function returns is: the value of the final expression that it evaluates. You can see this principle at work in the following example:

f <- function(x) {
  2*x + 3
  45
  "hello"
  x^2
}

f(4)
## [1] 16

We put in 4 as the argument for the parameter x, but:

  • we did not get back 11 (\(2 \times 4 +3\)),
  • nor did we get back 45,
  • nor did we get back the string “hello.”

When f() was called, R evaluated all of the expressions in its body, but returned only the value of the final expression it evaluated: \(4^2 = 16\).

3.3.2 The return() Function

R does have a special function to force a function to cease evaluation at a specific point. Its name, unsurprisingly, is return(). Here is an example:

g <- function(x) {
  val <- 3*x +7
  return(val)
  "Hello!"
}

g(1)
## [1] 10

We get \(3*1+7 = 10\), but we don’t get “Hello!” After returning the 10, the function stopped evaluating expressions: hence it never even bothered to evaluate “Hello,” much less to display it in the console.8

It follows that it does not matter whether or not you wrap the final expression of a function in return(). The following two functions do exactly the same thing:

f1 <- function(x) x^2
f2 <- function(x) return(x^2)

Some people—especially those who are familiar with other programming languages where return statements are required—like to wrap the final expression in return(), simply as a matter of clarity.

3.3.3 Writing a “Talky” Function

Suppose that you would like your function to raise a number to a power, returning the answer to the user, but you also want it to print out a message to the console. You might try writing your function like this:

talkySquare <- function(x) {
  result <- x^2
  result
  cat("The square of ", x, " is:  ", result, ".\n", sep = "")
}

We try it out:

talkySquare(4)
## The square of 4 is:  16.

All seems well. But what if we want to save the result in a variable, so that we could perhaps add a number to it later? Something like this, perhaps:

a <- talkySquare(4)
## The square of 4 is:  16.
a + 4
## numeric(0)

The results don’t really make sense. What happened, of course is that R dutifully returned the value of the final expression in the function’s body—the result of the cat() call, not the value of the variable result. If we want both the print-out and the square to be returned, the we have to write our functions like this:

talkySquare <- function(x) {
  result <- x^2
  cat("The square of ", x, " is:  ", result, ".\n", sep = "")
  result
}

This works out as expected:

a <- talkySquare(4)
## The square of 4 is:  16.
a + 4
## [1] 20

Well, maybe it doesn’t work exactly as we would like. It would nice if the function would talk to us only when we ask for the results in the console, not when we are simply assigning the results to a variable for later use. In Chapter 4 we will learn how to make our talky function keep quiet when we prefer silence.

3.3.4 The print() Function

Consider the following function:

grumpySquare <- function(x) {
  "OK, OK, I'm getting to it ... "
  x^2
}

We know by now not to expect to see the grumpy message:

grumpySquare(4)
## [1] 16

If we want to see the message, we could wrap it in cat(). Another possibility is to use the print() function:

grumpySquare <- function(x) {
  print("OK, OK, I'm getting to it ... ")
  x^2
}
grumpySquare(4)
## [1] "OK, OK, I'm getting to it ... "
## [1] 16

When R executes a call to print() it is forced to print something out to the console, even if it is in the midst of evaluating expressions in a function. The print-statement is not involved in what the function returns—that’s all up to the final expression, the x^2—but it does cause a result outside of the function itself. Any external result produced by a function (other than what the function returns) is called a side-effect of the function. cat() and print() are examples of functions that, when called inside of some other function, produce side-effects.

You should know that in R you have been calling the print() function quite a bit, without even knowing it. Consider the following line of code:

2+2
## [1] 4

R evaluates the expression 2+2, arriving at the value 4. But what makes the 4 appear on our console? Behind the scenes, R actually evaluated the expression

print(2+2)

That’s what got the 4 into the console! The fact is that whenever you make R evaluate an expression at “top level” (i.e., when you type the epxression into the Console) then R will call print() to put the value of the expression into the Console so you can see it.

At this point we don’t use print() explicitly very much—we just rely on R to call it for us when we are evaluating expressions at the console. Later on we will find that it has other uses.9

3.3.5 Practice Exercises

  1. What, if anything, does the following function return?

    f <- function(n) {
      cat("We have ", n, " dogs.\n", sep = "")
    }
  2. What, if anything, does the following function return?

    g <- function(n) {
      n + 3
      "Hello"
    }
  3. What, if anything, does the following function return?

    h <- function(n) {
      "Hello"
      n +3
    }

3.3.6 Solutions to the Practice Exercises

  1. Like any function, f() returns the value of the last expression that it evaluates. This time the final (and only) expression is the call to cat(). But what does cat() return? It turns out that cat() always returns NULL. You can see this in a couple of ways. One way is to store the result of a call to f() in a variable, and then print the variable:

    result <- f(4)
    ## We have 4 dogs.
    result
    ## NULL

    result got the value returned by the call f(4). result turned out to be 4. Hence the call f(4) must have returned NULL.

    Another way to see this is to rewrie the function so as to force the final expression to return its result visibly to the console. This is done by placing parentheses arund the expression, like this:

    f <- function(n) {
      (cat("We have ", n, " dogs.\n", sep = ""))
    }

    Now let’s call the functon:

    f(4)
    ## We have 4 dogs.
    ## NULL

    Yep, there’s NULL, visible in the Console!

  2. Well, let’s give it a try an see:

    result <- g(4)
    result
    ## [1] "Hello"

    Sure enough, g() returns the value of the last expression that it evaluates. In this case, it’s the expression "Hello".

  3. Like all functions, h() returns the value of the final expression that ti evaluates. Hence it will return three more than the value it was given for n.


  1. I know, I know—R already has the exponentiation operator. We just need an example to work with, here.↩︎

  2. You might wonder why anyone would write a function that contains expressions after a call to return(). We’ll learn why in Chapter 4.↩︎

  3. R is one of very few major programming languages that engage in behind-the-scenes calls to a print function. In many other languages you have to call its print-function explicitly if you want the value of an expression to be displayed.↩︎