Menu
Coddy logo textTech

Dispatch on Two Arguments

Part of the Object Oriented Programming section of Coddy's R journey. Lesson 29 of 57.

An S4 generic can choose its method by the classes of several arguments. The signature lists one class per argument, so each pair of classes can have its own method:

setClass("Asteroid", slots = c(size = "numeric"))
setClass("Ship", slots = c(name = "character"))
invisible(setGeneric("collide", function(a, b) standardGeneric("collide")))
setMethod("collide", signature("Asteroid", "Ship"), function(a, b) paste(b@name, "is hit"))
setMethod("collide", signature("Ship", "Ship"), function(a, b) "two ships bump")
rock <- new("Asteroid", size = 3)
enterprise <- new("Ship", name = "Enterprise")
print(collide(rock, enterprise))
print(collide(enterprise, enterprise))

Output:

[1] "Enterprise is hit"
[1] "two ships bump"

The class "ANY" matches every class. A method for signature("ANY", "ANY") is the fallback that runs when no more specific pair fits:

setClass("Ship", slots = c(name = "character"))
invisible(setGeneric("collide", function(a, b) standardGeneric("collide")))
setMethod("collide", signature("Ship", "Ship"), function(a, b) "two ships bump")
setMethod("collide", signature("ANY", "ANY"), function(a, b) "nothing happens")
s <- new("Ship", name = "A")
cat(collide(s, s), "/", collide(s, 42), "/", collide("x", "y"))

Output:

two ships bump / nothing happens / nothing happens

The order of the arguments matters: signature("A", "B") and signature("B", "A") are different methods. Basic classes such as numeric and character can appear in a signature too:

invisible(setGeneric("join", function(x, y) standardGeneric("join")))
setMethod("join", signature("character", "numeric"), function(x, y) paste(x, "number", y))
setMethod("join", signature("numeric", "character"), function(x, y) paste(y, "after number", x))
print(join("item", 3))
print(join(3, "item"))

Output:

[1] "item number 3"
[1] "item after number 3"

The class "missing" in a signature matches an argument that was not given at all, so one generic can offer a short and a long form:

invisible(setGeneric("greet", function(name, greeting) standardGeneric("greet")))
setMethod("greet", signature("character", "missing"), function(name, greeting) paste("Hello,", name))
setMethod("greet", signature("character", "character"), function(name, greeting) paste0(greeting, ", ", name))
print(greet("Ada"))
print(greet("Ada", "Good morning"))

Output:

[1] "Hello, Ada"
[1] "Good morning, Ada"
challenge icon

Challenge

Easy

Shipping prices depend on the item and the zone. The classes are declared in Item.R (Letter, Box, each with a slot kg) and Zone.R (Domestic, Abroad, each with a slot name). In Rates.R create the generic shipping(item, zone) and one method per pair:

  • Letter at home: 1; abroad: 3.
  • Box at home: 5 plus 2 per kg; abroad: 12 plus 4 per kg.
  • Any other pair: stop with no rate for this item and zone (use call. = FALSE).

The supplied code reads lines box home 2.5 (item, zone, kg), where unknown words stay plain text, and prints box to home: 10.00 or error: and the message.

Your code goes in Item.R, Zone.R and Rates.R. main.R holds the supplied input/output code and cannot be edited.

Try it yourself

source("Item.R")
source("Zone.R")
source("Rates.R")

# Supplied input/output code: keep it as it is
input <- suppressWarnings(readLines(file("stdin")))
for (line in input) {
  p <- strsplit(line, " ")[[1]]
  kg <- as.numeric(p[3])
  item <- switch(p[1], letter = new("Letter", kg = kg), box = new("Box", kg = kg), kg)
  zone <- switch(p[2], home = new("Domestic", name = "home"), abroad = new("Abroad", name = "abroad"), p[2])
  out <- tryCatch(sprintf("%s to %s: %.2f", p[1], p[2], shipping(item, zone)),
                  error = function(e) paste("error:", conditionMessage(e)))
  cat(out, "\n", sep = "")
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming

Practice on your own: Online R compiler