10

I'm confused about the usage of setReplaceMethod(). Looking at ?setReplaceMethod does not provide an explanation and Googling is less than helpful.

QUESTION : Please explain setReplaceMethod(), it's usage and how it works (preferably with an example).

irritable_phd_syndrome
  • 4,631
  • 3
  • 32
  • 60

2 Answers2

7

Here is what I found. As pointed by @Hong Ooi in the comments setReplaceMethod("fun") is the same as setMethod("fun<-"), so setReplaceMethod is used to create a method for a generic replacement function in the S4 object system of R.

What is a replacement function is explained in what-are-replacement-functions-in-r. Very rougly, if you have a function called fun<-, because its name ends with <-, you can write fun(x)<-a and R will read x <- "fun<-"(x,a).

The S4 object system is described in S4 - Advanced R.

To give an example maybe it is easier to start by creating a method for an S4 generic function, which is not a replacement function:

## Define an S4 class 'Polygon' and an object of this class
setClass("Polygon", representation(sides = "integer"))
p1 <- new("Polygon", sides = 33L)
## Define a generic S4 function 'sides'
sides <- function(object){ NA }
setGeneric("sides")
## sides returns NA
sides( p1 )
## Define a method for 'sides' for the class 'Polygon'
setMethod("sides", signature(object = "Polygon"), function(object) {
  object@sides
})
## Now sides returns the sides of p1
sides( p1 )

Creating a method for a generic replacement function is similar:

## Define a generic replacement function 'sides<-'
"sides<-" <- function(object, value){ object }
setGeneric( "sides<-" )
## The generic 'sides<-' doesn't change the object
sides( p1 ) <- 12L
sides( p1 )
## Define a method for 'sides<-' for the class 'Polygon',
## setting the value of the 'sides' slot
setMethod( "sides<-", signature(object = "Polygon"), function(object, value) {
  object@sides <- value
  object
})
## Now 'sides<-' change the sides of p1
sides( p1 ) <- 12L
sides( p1 )

You asked also about $<-. My guess is this: x$name<-value is interpreted as "$"(x,name)<-value and then as x <- "$<-"(x,name,value). Note that a generic function $<- is already defined (isGeneric("$<-")), so we only define a method for our class Polygon:

setMethod( "$<-", signature(x = "Polygon"), function(x, name, value) {
  if( name=="sides" ){
    x@sides <- value
  }
  x
})
## Nothing changes if we try to set 'faces'
p1$faces <- 3L
p1
## but we can set the 'sides'
p1$sides <- 3L
p1

Note that the arguments x, name and value are dictated by the generic.

user7669
  • 638
  • 4
  • 13
  • So running `p1 <- "$<-"(p1,sides,777)` or `"$"(x,sides)<-777` returns an error. I think your interpretation is incorrect for the last half of your answer – irritable_phd_syndrome Mar 16 '18 at 14:56
  • @irritable_phd_syndrom That's because `sides` was defined to be an integer and `777` is not an integer in `R`! You have to write `777L`: compare `class(777)` and `class(777L)`. In fact `p1 <- "$<-"(p1,sides,777L)` and `"$"(p1,sides)<-777L` work. – user7669 Mar 17 '18 at 23:34
  • How about that...That is perplexing b/c `4+5` yields `9`. I guess I don't understand when it decides a number is a numeric vs. something else – irritable_phd_syndrome Mar 18 '18 at 00:27
  • Also, where is the `isGeneric("$<-")` defined? – irritable_phd_syndrome Mar 18 '18 at 00:31
  • @irritable_phd_syndrom Have a look at https://www.burns-stat.com/pages/Tutor/R_inferno.pdf, pg. 91 and `?isGeneric`. – user7669 Mar 18 '18 at 17:06
5

For an extraction method such as $, [, or names(), a replacement method such as $<-, [<-, or names<- replaces the value that would have been extracted.

Take, for example, a list:

example_object <- list(a = 1, b = 2)
# the extract method $, when called with arguments example_object and a,
# extracts and returns the value 1
example_object$a
# [1] 1
# the replace method $<-, when called with arguments example_object, a, and 42,
# replaces the value 1 (at example_object$a) with the value 42
example_object$a <- 42
example_object
# $a
# [1] 42
# 
# $b
# [1] 2

So, for S4 classes, setMethod("$", ...) will define the behavior of the extraction method $ while setMethod("$<-", ...) or equivalently setReplaceMethod("$", ...) will define the behavior of the replacement method $<-. setReplaceMethod("$") is just meant to be more expressive than setMethod("$<-"), to make clear that you are defining the replacement method for $.

An example using an S4 class:

setClass("MyClass", representation(a = "numeric", b = "numeric"))

setMethod("$", signature = "MyClass",
          function (x, name) {
              if ( name == "a" ) {
                  return(x@a)
              }
              else if ( name == "b" ) {
                  return(x@b)
              }
              else {
                  stop(paste("No slot", name, "for MyClass"), call. = FALSE)
              }
          }
)
# [1] "$"

my_object <- new("MyClass", a = 1, b = 2)
my_object@a
# [1] 1
my_object$a
# [1] 1

my_object@a <- 42
my_object@a
# [1] 42
my_object$a <- 3.14 # will not work because we have not set the method for $<-
# Error in `$<-`(`*tmp*`, a, value = 3.14) : 
#   no method for assigning subsets of this S4 class
my_object@a
# [1] 42

setReplaceMethod("$", signature = "MyClass",
                 function(x, name, value) {
                     if ( name == "a" ) {
                         x@a <- value
                         return(x)
                     }
                     else if ( name == "b" ) {
                         x@b <- value
                         return(x)
                     }
                     else {
                         stop(paste("No slot", name, "for MyClass"),
                              call. = FALSE)
                     }
                 }
)
# [1] "$<-"

my_object$a <- 3.14
my_object@a
# [1] 3.14
duckmayr
  • 16,303
  • 3
  • 35
  • 53
  • So this can be used to create the strange (at least from a C/Python programmers perspective) lefthand function calling and assignment? E.g. `df<-as.data.frame(cbind(c(1,2,3), c(4,5,6)))` then `colnames(df) <- c("name1", "name2")`. What is this called? – irritable_phd_syndrome Mar 13 '18 at 11:29
  • @irritable_phd_syndrom Yes; I have generally seen it called a replace method. For example, `help("colnames<-")` brings up the helpfile for row and column names, and the usage section shows both the extract method `colnames(x, ...)` and the replace method `colnames(x) <- value`. – duckmayr Mar 13 '18 at 11:37
  • This is a good answer, however, could you address the general case where `setReplaceMethod()` is used on a general function? Your answer seems to address a special case for when it is an extraction method. – irritable_phd_syndrome Mar 16 '18 at 14:56