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.1416UseMethod() 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] 10Challenge
EasyShapes 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 = "")
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1S3 Basics
Working With FilesLists With a ClassConstructor FunctionsPrint and Format MethodsValidators and HelpersRecap - Temperatures4Encapsulation in R
Closures as ObjectsEnvironments as ObjectsAccessor FunctionsGuarding StateRecap - Parking Meter2S3 Generics and Methods
Generics and UseMethodDefault MethodsMethods for Base GenericsNextMethod BasicsRecap - Shape Areas8Reference Classes
Defining Reference ClassesMethods and Field UpdatesCopy SemanticsInheritance and callSuperRecap - Task Queue11Project: Library Management
Books and MembersBorrowing BooksPractice on your own: Online R compiler