Replacement Functions

Recall the following example from Chapter 2:

> x <- c(1,2,4)
> names(x)
NULL
> names(x) <- c("a","b","ab")
> names(x)
[1] "a"  "b"  "ab"
> x
 a  b ab
 1  2  4

Consider one line in particular:

> names(x) <- c("a","b","ab")

Looks totally innocuous, eh? Well, no. In fact, it’s outrageous! How on Earth can we possibly assign a value to the result of a function call? The resolution to this odd state of affairs lies in the R notion of replacement functions.

The preceding line of R code actually is the result of executing the following:

x <- "names<-"(x,value=c("a","b","ab"))

No, this isn’t a typo. The call here is indeed to a function named names<-(). (We need to insert the quotation marks due to the special characters involved.)

Any assignment statement in which the left side is not just an identifier (meaning a variable name) is considered a replacement function. When encountering this:

g(u) <- v

R will try to execute this:

u <- "g<-"(u,value=v)

Note the “try” in the preceding sentence. The statement will fail if you have not previously defined g<-(). Note that the replacement function has one more argument than the original function g(), a named argument value, for reasons explained in this section.

In earlier chapters, you’ve seen this innocent-looking statement:

x[3] <- 8

The left side is not a variable name, so it must be a replacement function, and indeed it is, as follows.

Subscripting operations are functions. The function "["() is for reading vector elements, and "[<-"() is used to write. Here’s an example:

> x <- c(8,88,5,12,13)
> x
[1]  8 88  5 12 13
> x[3]
[1] 5


> "["(x,3)
[1] 5
> x <- "[<-"(x,2:3,value=99:100)
> x
[1]   8  99 100  12  13

Again, that complicated call in this line:

> x <- "[<-"(x,2:3,value=99:100)

is simply performing what happens behind the scenes when we execute this:

x[2:3] <- 99:100

We can easily verify what’s occurring like so:

> x <- c(8,88,5,12,13)
> x[2:3] <- 99:100
> x
[1]   8  99 100  12  13

Suppose we have vectors on which we need to keep track of writes. In other words, when we execute the following:

x[2] <- 8

we would like not only to change the value in x[2] to 8 but also increment a count of the number of times x[2] has been written to. We can do this by writing class-specific versions of the generic replacement functions for vector subscripting.

Note

This code uses classes, which we’ll discuss in detail in Chapter 9. For now, all you need to know is that S3 classes are constructed by creating a list and then anointing it as a class by calling the class() function.

1    # class "bookvec" of vectors that count writes of their elements
2
3    # each instance of the class consists of a list whose components are the
4    # vector values and a vector of counts
5
6    # construct a new object of class bookvec
7    newbookvec <- function(x) {
8       tmp <- list()
9       tmp$vec <- x  # the vector itself
10      tmp$wrts <- rep(0,length(x))  # counts of the writes, one for each element
11      class(tmp) <- "bookvec"
12      return(tmp)
13    }
14
15    # function to read
16    "[.bookvec" <- function(bv,subs) {
17       return(bv$vec[subs])
18    }
19
20    # function to write
21    "[<-.bookvec" <- function(bv,subs,value) {
22       bv$wrts[subs] <- bv$wrts[subs] + 1  # note the recycling
23       bv$vec[subs] <- value
24       return(bv)
25    }
26    \end{Code}
27
28    Let's test it.
29
30    \begin{Code}
31    > b <- newbookvec(c(3,4,5,5,12,13))
32    > b
33    $vec
34    [1]  3  4  5  5 12 13
35
36    $wrts
37    [1] 0 0 0 0 0 0
38
39    attr(,"class")
40    [1] "bookvec"
41    > b[2]
42    [1] 4
43    > b[2] <- 88  # try writing
44    > b[2]  # worked?
45    [1] 88
46    > b$wrts  # write count incremented?
47    [1] 0 1 0 0 0 0

We have named our class "bookvec", because these vectors will do their own bookkeeping—that is, keep track of write counts. So, the subscripting functions will be [.bookvec() and [.bookvec().

Our function newbookvec() (line 7) does the construction for this class. In it, you can see the structure of the class: An object will consist of the vector itself, vec (line 9), and a vector of write counts, wrts (line 10).

By the way, note in line 11 that the function class() itself is a replacement function!

The functions [.bookvec() and [<-.bookvec() are fairly straightforward. Just remember to return the entire object in the latter.