Menu
Coddy logo textTech

Generics and UseMethod

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

A generic is a function whose body is UseMethod("name"). When it is called, it looks at the class of its first argument and calls the method name.class. print() and format() work like this; you can write your own generics too:

speak <- function(x) UseMethod("speak")
speak.dog <- function(x) paste(x$name, "says woof")
speak.cat <- function(x) paste(x$name, "says meow")
rex <- structure(list(name = "Rex"), class = "dog")
tom <- structure(list(name = "Tom"), class = "cat")
print(speak(rex))
print(speak(tom))

Output:

[1] "Rex says woof"
[1] "Tom says meow"

The caller writes one call, and each object answers in its own way. This is polymorphism: code that loops over a mixed list does not need an if for every class:

speak <- function(x) UseMethod("speak")
speak.dog <- function(x) paste(x$name, "says woof")
speak.cat <- function(x) paste(x$name, "says meow")
pets <- list(structure(list(name = "Rex"), class = "dog"),
             structure(list(name = "Tom"), class = "cat"),
             structure(list(name = "Bo"), class = "dog"))
for (p in pets) cat(speak(p), "\n")

Output:

Rex says woof 
Tom says meow 
Bo says woof 

A method takes the generic's arguments. Give the generic a ... parameter so that methods can accept extra arguments of their own, such as digits for a circle:

area <- function(shape, ...) UseMethod("area")
area.square <- function(shape, ...) shape$side^2
area.circle <- function(shape, digits = 2, ...) round(pi * shape$r^2, digits)
sq <- structure(list(side = 3), class = "square")
ci <- structure(list(r = 1), class = "circle")
print(area(sq))
print(area(ci))
print(area(ci, digits = 4))

Output:

[1] 9
[1] 3.14
[1] 3.1416

UseMethod() hands the call over to the method and never comes back: whatever the method returns is the result of the generic, and code after UseMethod() does not run. A generic is therefore one line:

size <- function(x) {
  UseMethod("size")
  cat("this line never runs\n")
}
size.box <- function(x) x$w * x$h
b <- structure(list(w = 2, h = 5), class = "box")
print(size(b))

Output:

[1] 10
challenge icon

Challenge

Easy

Shapes are split over three files. Complete the generic area() in Shape.R, then add the methods area.circle() (pi times the radius squared) in Circle.R and area.rectangle() (width times height) in Rectangle.R. The constructors are given.

The supplied code reads lines circle 2 or rectangle 3 4 and prints each shape's class and area with two decimal places: circle: 12.57.

Your code goes in Shape.R, Circle.R and Rectangle.R. main.R holds the supplied input/output code and cannot be edited.

Try it yourself

source("Shape.R")
source("Circle.R")
source("Rectangle.R")

# Supplied input/output code: keep it as it is
input <- suppressWarnings(readLines(file("stdin")))
for (line in input) {
  p <- strsplit(line, " ")[[1]]
  s <- if (p[1] == "circle") new_circle(as.numeric(p[2])) else new_rectangle(as.numeric(p[2]), as.numeric(p[3]))
  cat(sprintf("%s: %.2f", class(s), area(s)), "\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