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.
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.