Purpose: reduce tedium of S4 class definition

Given a prototypical list of elementary R data it is trivial but tedious to define a valid S4 class with appropriate getter and setter methods. DrS4’s ClassSupport class helps with this.

Example: Turtle

Stuart Lee’s blog post on S4 inspires the default parameter settings in the clgen function.

## function (clname = "Turtle", protolist = list(location = c(0, 
##     0), orientation = 0, path = matrix(c(0, 0), ncol = 2)), check_init = FALSE) 
## NULL

Generate the basic definitions

Try it:

tt = clgen()
tt
## ClassSupport instance for Turtle 
##  slots:  location orientation path

Learn a little more about this infrastructure:

getClass(class(tt))
## Class "ClassSupport" [package "DrS4"]
## 
## Slots:
##                                                         
## Name:     clname   defcode   getters   setters protolist
## Class: character character      list      list      list

We dump the code defining the class and its get/set methods.

dumpcs(tt)
## setClass('Turtle', slots= c(location='numeric', orientation='numeric', path='matrix'), prototype=list(location = c(0, 0), orientation = 0, path = structure(c(0, 0), .Dim = 1:2)))
## setGeneric(name='location', def=function(x) standardGeneric('location'))
## setMethod(f='location', signature='Turtle', function(x) slot(x, 'location'))
## setGeneric(name='orientation', def=function(x) standardGeneric('orientation'))
## setMethod(f='orientation', signature='Turtle', function(x) slot(x, 'orientation'))
## setGeneric(name='path', def=function(x) standardGeneric('path'))
## setMethod(f='path', signature='Turtle', function(x) slot(x, 'path'))
## setGeneric(name='location<-', def=function(x, value) standardGeneric('location<-'))
## setMethod(f='location<-', signature='Turtle', function(x, value) {slot(x, 'location') = value; x})
## setGeneric(name='orientation<-', def=function(x, value) standardGeneric('orientation<-'))
## setMethod(f='orientation<-', signature='Turtle', function(x, value) {slot(x, 'orientation') = value; x})
## setGeneric(name='path<-', def=function(x, value) standardGeneric('path<-'))
## setMethod(f='path<-', signature='Turtle', function(x, value) {slot(x, 'path') = value; x})

To use the code, we can source it, or place in a package.

tf = tempfile()
dumpcs(tt, tf)
source(tf)
## Creating a new generic function for 'path' in the global environment
## Creating a new generic function for 'path<-' in the global environment
tur = new("Turtle")
tur
## An object of class "Turtle"
## Slot "location":
## [1] 0 0
## 
## Slot "orientation":
## [1] 0
## 
## Slot "path":
##      [,1] [,2]
## [1,]    0    0
path(tur)
##      [,1] [,2]
## [1,]    0    0
path(tur) = matrix(c(1,1), nr=1)
tur
## An object of class "Turtle"
## Slot "location":
## [1] 0 0
## 
## Slot "orientation":
## [1] 0
## 
## Slot "path":
##      [,1] [,2]
## [1,]    1    1

Extend the class

Stuart showed how a more functional turtle can be defined – one that holds a pen. We’ll need a new definition and new getters and setters.

We defined a method on our ClassSupport class to produce the new class.

newprops = list(colour = "pink", thickness = 1, on = FALSE)
nn = extendClassSupport(tt, newprops, "TurtleWithPen")
nn
## ClassSupport instance for TurtleWithPen 
##  slots:  colour thickness on
dumpcs(nn)
## setClass('TurtleWithPen', contains='Turtle', slots=c(colour='character', thickness='numeric', on='logical'))
## setGeneric(name='colour', def=function(x) standardGeneric('colour'))
## setMethod(f='colour', signature='TurtleWithPen', function(x) slot(x, 'colour'))
## setGeneric(name='thickness', def=function(x) standardGeneric('thickness'))
## setMethod(f='thickness', signature='TurtleWithPen', function(x) slot(x, 'thickness'))
## setGeneric(name='on', def=function(x) standardGeneric('on'))
## setMethod(f='on', signature='TurtleWithPen', function(x) slot(x, 'on'))
## setGeneric(name='colour<-', def=function(x, value) standardGeneric('colour<-'))
## setMethod(f='colour<-', signature='TurtleWithPen', function(x, value) {slot(x, 'colour') = value; x})
## setGeneric(name='thickness<-', def=function(x, value) standardGeneric('thickness<-'))
## setMethod(f='thickness<-', signature='TurtleWithPen', function(x, value) {slot(x, 'thickness') = value; x})
## setGeneric(name='on<-', def=function(x, value) standardGeneric('on<-'))
## setMethod(f='on<-', signature='TurtleWithPen', function(x, value) {slot(x, 'on') = value; x})

We can now source that code to allow composition of methods for TurtleWithPen.

Envoi

There is something unsavory about this approach to code generation. I produced it in the face of a task involving a configuration object that seems to require many slots. I wanted to avoid the temptation to use simple functions and the at sign. My hope is that programs along the lines of those demonstrated here will help lower the barrier to adoption of formal class and method definitions in R programming. There are surely many improvements to be made in what has been proposed here.