15.5 Method Chaining

In this Section we will explore a concise and convenient device for calling multiple methods on an object. The device is known as method chaining.

In order to illustrate method chaining, we’ll extend the class Lion a bit, with some new attributes and new methods. First of all, we’d like to add a data frame consisting of possible animals on which a lion might prey. The data frame will contain the name of each type of animal, and an associated weight.

animal <-  c("zebra", "giraffe", "pig", 
             "cape buffalo", "antelope", "wildebeast")
mass <- c(50, 100, 25, 60, 45, 55)
prey <- data.frame(animal, mass, stringsAsFactors =FALSE)

Let’s now add prey as a new attribute to class Lion. We will also add an attribute eaten: a character vector—initially empty—that will contain a record of all the animals that the lion has eaten.

Lion$set("public", "prey", prey, overwrite = TRUE)
Lion$set("public", "eaten", character(), overwrite = TRUE)

Let us now endow our lions with the capacity to eat a beast of prey, by adding the method eat():

Lion$set("public", "eat", function() {
  n <- nrow(self$prey)
  item <- self$prey[sample(1:n, size = 1), ]
  initLetter <- substr(item$animal, 1, 1)
  article <- ifelse(initLetter %in% c("a", "e", "i", "o", "u"), "An ", "A ")
  cat(article, item$animal, " was eaten just now ...\n\n", sep = "")
  self$eaten <- c(self$eaten, item$animal)
  self$weight <- self$weight + item$mass
  return(invisible(self))
}, overwrite = TRUE)

When it eats an animal—randomly selected from the prey data frame—the lion gains the amount of weight that is associated with its unfortunate victim, and the victim is added to the lion’s eaten attribute.

Note that the eat() method returns self invisibly. When the method is called on a lion, that very lion itself is returned as a value, but since it is returned invisibly it won’t be printed to the console. The usefulness of returning self will soon become apparent.

Lions enjoy talking about what they have eaten recently, and for some reason they monitor their weight obsessively. The new method report() accounts for these characteristics of lions:

Lion$set("public", "report", function() {
  n <- length(self$eaten)
  if ( n >= 1 ) {
    cat("My name is ", self$name, ".\n", sep = "")
    cat("My most recent meal consisted of: ", self$eaten[n], ".\n", sep = "")
  }
  cat("I now weigh ", self$weight, " pounds.\n", sep = "")
  return(invisible(self))
}, overwrite = TRUE)

Note that report() also returns the lion invisibly.

Let us now instantiate a new lion named Simba:

simba <- Lion$new(name = "Simba", age = 10, 
                  desire = "Hakuna Matata")
## Grr!  My name is Simba!
simba$set_weight(300)
## Simba has weight:  300.

Having been created after the addition of the prey and eaten attributes and the eat() and report() methods, simba has access to all of them. in particular, he can eat a random beast of prey:

simba$eat()
## A cape buffalo was eaten just now ...

simba has eaten, and he has presumably gained some weight as a result. Let’s see verify this by asking for his report:

simba$report()
## My name is Simba.
## My most recent meal consisted of: cape buffalo.
## I now weigh 360 pounds.

Let’s now have simba eat twice more, and then report. Because eat() and report() both return simba, we can

  • call eat() on `simba,
  • and then immediately call eat() on the result,
  • and finally call report() on the result of our second call.

All of this can be accomplished in one line of calls, in which the three method-calls are “chained” together with dollar-signs:

simba$eat()$eat()$report()
## A pig was eaten just now ...
## 
## A cape buffalo was eaten just now ...
## 
## My name is Simba.
## My most recent meal consisted of: cape buffalo.
## I now weigh 445 pounds.

In object-oriented programming languages you will see method-chaining used quite frequently.