पाठ 20 / 25

Functions as Objects, Environments and S3/R6 Classes

Understand environments, closures and R's object systems.

How R organises objects

Every function has an environment: names are looked up in the function's own environment, then in the environment where the function was defined, then outwards to the global environment and loaded packages. This lexical scoping enables closures, functions that remember variables from where they were created, such as a counter factory. R has several object-oriented systems. S3 is the simplest and most common: an object is usually a list with a class attribute, and generic functions such as print(), summary() and plot() dispatch to methods named generic.class (for example print.invoice). That is why summary() works differently on a data frame and on an lm model. S4 adds formal class definitions and validity checks, used heavily in Bioconductor. R6 (a package) provides classic mutable objects with methods and fields, useful for things with state such as a database connection pool or a simulation; R6 objects use reference semantics, unlike R's usual copy-on-modify values. Most analysis code needs only functions and data frames; S3 is worth knowing to read and extend packages.

An S3 class with methods, and an R6 counter

S3 dispatch through generics; R6 objects are mutable references.

new_invoice <- function(id, items, prices) {
  stopifnot(length(items) == length(prices))
  structure(list(id = id, items = items, prices = prices), class = "invoice")
}

print.invoice <- function(x, ...) {
  cat("Invoice", x$id, "with", length(x$items), "items, total Rs",
      format(sum(x$prices), big.mark = ","), "\n")
  invisible(x)
}

summary.invoice <- function(object, ...) {
  data.frame(item = object$items, price = object$prices)
}

inv <- new_invoice("INV-7", c("pen", "notebook"), c(49.5, 1200))
inv                 # calls print.invoice automatically
summary(inv)

Counter <- R6::R6Class("Counter",
  public = list(
    count = 0,
    add = function(n = 1) { self$count <- self$count + n; invisible(self) }
  )
)
c1 <- Counter$new()
c1$add()$add(5)     # method chaining
c1$count            # 6

Generic job titles

An S3 generic is a job title like "print". When an invoice asks to be printed, R looks for the invoice-specific way of doing that job; if there is none, it falls back to the default way.

त्वरित जाँच: How does S3 method dispatch decide which function to call for print(x)?

  • It always calls print.default
  • It uses the first argument's length
  • S3 does not support dispatch
  • It looks for print.<class of x>, falling back to print.default
Answer

It looks for print.<class of x>, falling back to print.default — S3 generics dispatch on the class attribute to methods named generic.class.