Object-Oriented Programming in R: The Setter Methods

With a little guidance from the indefatigable Hadley Wickham, I figured out today how to implement the setter methods that were missing from my example user class. To review, let’s rebuild the getter methods for my user object:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
user <- list(id = 1,
             password = '286755fad04869ca523320acce0dc6a4',
             email = 'jmw@johnmyleswhite.com')
 
class(user) <- 'user'
 
id <- function(x)
{
  UseMethod('id', x)
}
 
id.user <- function(user)
{
  return(user[['id']])
}
 
password <- function(x)
{
  UseMethod('password', x)
}
 
password.user <- function(user)
{
  return(user[['password']])
}
 
email <- function(x)
{
  UseMethod('email', x)
}
 
email.user <- function(user)
{
  return(user[['email']])
}

With these working, my goal was to write setter methods that would work like the ideal code below:

1
2
3
4
5
6
id(user) <- 2
 
if (id(user) == 2)
{
  print("Succeeded in editing the user's id attribute.")
}

Defining this sort of setter method turned out to require a little effort. I wasted quite a lot of time walking down dead ends, but, thankfully, Hadley gave me the exact piece of information I was missing early this morning. The dead ends were interesting in themselves, though, so I’ll review them in another post tomorrow.

For now I’ll just explain the correct implementation for my desired setter methods, which you can see below:

1
2
3
4
5
6
7
8
9
10
'id<-' <- function(x, value)
{
  UseMethod('id<-', x)
}
 
'id<-.user' <- function(x, value)
{
  x[['id']] <- value
  return(x)
}

There are three important ideas here:

  1. The generic setter method for id should be called 'id<-', which must always be quoted to prevent parsing errors.
  2. The class level setter method should be called 'id<-.user', which isn't surprising, though I had imagined it might be called 'id.user<-' at one point.
  3. The setter method should make a change to a copy of the original object and return the edited copy to the caller. R handles the assignment of this edited return value to the original object behind the scenes. In fact, not using this return value approach will make otherwise plausible looking code fail to edit your object correctly. I stumbled on that for quite a while before I was shown the light.

With this in mind, a final user class looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
user <- list(id = 1,
             password = '286755fad04869ca523320acce0dc6a4',
             email = 'jmw@johnmyleswhite.com')
 
class(user) <- 'user'
 
id <- function(x)
{
  UseMethod('id', x)
}
 
id.user <- function(user)
{
  return(user[['id']])
}
 
password <- function(x)
{
  UseMethod('password', x)
}
 
password.user <- function(user)
{
  return(user[['password']])
}
 
email <- function(x)
{
  UseMethod('email', x)
}
 
email.user <- function(user)
{
  return(user[['email']])
}
 
'id<-' <- function(x, value)
{
  UseMethod('id<-', x)
}
 
'id<-.user' <- function(x, value)
{
  x[['id']] <- value
  return(x)
}
 
'password<-' <- function(x, value)
{
  UseMethod('password<-', x)
}
 
'password<-.user' <- function(x, value)
{
  x[['password']] <- value
  return(x)
}
 
'email<-' <- function(x, value)
{
  UseMethod('email<-', x)
}
 
'email<-.user' <- function(x, value)
{
  x[['email']] <- value
  return(x)
}

6 responses to “Object-Oriented Programming in R: The Setter Methods”

  1. John Chandler-Pepelnjak

    John,

    Thanks for posting this series. I’ve been an R user since 2000 but I’m an unsophisticated programmer so I’ve avoided creating my own classes. Anyway, this was *incredibly* useful and taught me more about R than I’ve learned in a year. I’m sure it was out there in 50 different books and websites that I’ve seen, but this is the first explanation that made sense to me.

    Thanks,
    John

  2. Aleksandar Blagotic

    Dear John,

    I would like to express my gratitude for this tremendous post! Though I’ve been using R constantly for about 3 years now, I still consider myself a newborn in the world of R programming (though I have some experience in web programming). I find it quite easy to create OOP code in Python and PHP, but OOP in R was rather unfamiliar to me. Although this post provides certain closure on R’s OOP capabilities, as a self-indulgent hard-coded R programmer, I’m looking for a bit crystallized, sophisticated, “noob friendly” tutorial… an ebook should suffice! =D
    Any suggestion?

    Kind regards,
    Aleksandar

  3. gappy3000

    This is the most common approach to defining setters in S3; however, there is a fundamental problem with this approach. The setter method returns (by value) the entire object. This is perhaps by design, since the approach maintains referential transparency. The downside is that, if the object is large, the function will be *much* slower than modifying the attribute via an unprotected accessor to the attribute. A solution to this would be to use reference classes. I am not a great fan of them however. Alternatives and suggestions are welcome.

  4. gappy3000

    Yes, unfortunately this is the way R works. In Clojure objects are immutable, but assignments are incremental. Alas. And yes, S4 is also pass-by-value. Reference classes are implemented in S4 using environments.

    On a separate note, keep the Julia posts coming. Julia design principles seem just right: incremental learning, fast, lispy. A lot of important decisions are still undetermined though, and for work-related reasons I can’t be an early adopter. So I am relying on a few people like you to stay up to date and jump in when the time is ready..