3

I wondered about S3 classes in R, if there is an option to define a default output element and keep the remaining elements kind of hidden. As an example, lets say we have a toy function that calculates certain things and reports them back as a S3 class, like this:

toy <- function(x){
  resA <- mean(x)
  resB <- length(x)

  output <- list(resA=resA, resB=resB, x=x)
  class(output) <- "toy"
  output
}

When we access the result now via

res <- toy(c(1:10))
res

we get the whole list as an output, as it would be expected. But if we define then also an S3 print method

`print.toy` <- function(x){
  print(x$resA)
}

we can give a standard output for print that hides unnecessary information (in that case resB and x) and the user sees only resA. But this could cause some confusion, when you want to apply further calculations on your object of class toy, e.g.

res <- toy(c(1:10))
res 
# Produces an error
res + 1 
# Accesses the correct variable of class toy:
res$resA + 1

My question is now, is there a way to define the list item resA to be the standard value of a S3 class that should be taken if no variable is specified, so that the res + 1 call will work as well?

Thanks for reading this.

Argalatyr
  • 4,639
  • 3
  • 36
  • 62
Daniel Fischer
  • 3,280
  • 1
  • 18
  • 29

2 Answers2

4

One approach is to use a vector + attributes instead of a list. This is most suitable if you have one primary piece of data that should work like a regular vector, and a few additional pieces of metadata.

toy <- function(x) {
  resA <- mean(x)
  resB <- length(x)

  structure(resA, x = x, b = resB, class = "toy")
}
print.toy <- function(x, ...) {
  print(as.vector(x))
}
t <- toy(1:10)
t + 1
# [1] 6.5

You'll also need to override [, because the default method does not preserve attributes.

hadley
  • 102,019
  • 32
  • 183
  • 245
  • Thanks a lot! The `structure` was what I was looking for - basically as you said, the primary piece of data and then still some hidden information. – Daniel Fischer Mar 06 '13 at 17:13
  • Playing around with your answer, I found out that the function `'$.toy' <- function(x,y,...){ attr(x,as.character(y)) }` makes the hidden attributes accessible in the exact way what I had in mind, thanks again!!! (I am not sure, if it would be better to edit your answer and add this function to it or keep it here in the comment?!) – Daniel Fischer Mar 06 '13 at 17:30
  • I wouldn't recommend doing that without more thought - it obfuscates the fact that your object is based on an object with attributes, not a list. – hadley Mar 06 '13 at 18:52
  • You are right, that might be not the wisest idea. Though it was tempting, because it reassembled what I had in mind... – Daniel Fischer Mar 07 '13 at 06:29
2

As @Ari mentions, you might have to do something like this:

`+.toy` <- function(a, b) { 
     # you should check for class/mode/is.atomic here if necessary
     a$resA <- a$resA + b
     a
}

t <- toy(1:10)
t + 1
# [1] 6.5
Arun
  • 116,683
  • 26
  • 284
  • 387
  • That's a pity that overloading seems to be the only way, because this results then in overloading a whole set of functions. I hoped a bit for a way to make the `print` output to be the default element if no list item is specified. So that R extracts then this item and performs the requested operation on this one. – Daniel Fischer Mar 06 '13 at 08:09
  • While I understand your point, this is the way it is supposed to be. `+` can not by default assume operations on a list or any other object. – Arun Mar 06 '13 at 08:11