14

I am trying to write an rbind method for a particular class. Here's a simple example where it doesn't work (at least for me):

rbind.character <- function(...) {
    do.call("paste", list(...))
}

After entering this function, I seemingly can confirm that it is a valid method that R knows about:

> methods("rbind")
[1] rbind.character  rbind.data.frame rbind.rootogram* rbind.zoo*      
see '?methods' for accessing help and source code

However, it is not recognized if I try to use it:

> rbind("abc", "xyz")
     [,1] 
[1,] "abc"
[2,] "xyz"
> #### compared with ####
> rbind.character("abc", "xyz")
[1] "abc xyz"

The help page says that dispatch is performed internally as follows:

  1. For each argument we get the list of possible class memberships from the class attribute.
  2. We inspect each class in turn to see if there is an applicable method.
  3. If we find an applicable method we make sure that it is identical to any method determined for prior arguments. If it is identical, we proceed, otherwise we immediately drop through to the default code.

With rbind("abc", "xyz"), I believe all these criteria are satisfied. What gives, and how can I fix it?

Russ Lenth
  • 5,922
  • 2
  • 13
  • 21
  • 2
    See `?InternalMethods` and, also, something like `x = "abc"; y = "xyz"; is.object(x) || is.object(y); rbind(x, y); attr(x, "class") = "character"; attr(y, "class") = "character"; is.object(x) || is.object(y); rbind(x, y)` – alexis_laz Dec 01 '15 at 18:14

3 Answers3

9
attributes("abc")
#NULL

A character vector doesn't have a class attribute. I don't think a method can be dispatched by rbind for the implicit classes.

Roland
  • 127,288
  • 10
  • 191
  • 288
  • What about `class("abc") # [1] "character"`? – thothal Dec 01 '15 at 16:11
  • 2
    Seems misleading that `class("abc")` is `"character"`. – Akhil Nair Dec 01 '15 at 16:11
  • 5
    It's an implicit class. `rbind`'s method dispatch only checks for class attributes, that is explicit classes. – Roland Dec 01 '15 at 16:12
  • Thanks for the insight. – thothal Dec 01 '15 at 16:12
  • 2
    This seems likely given the source of [rbind](https://github.com/wch/r-source/blob/b156e3a711967f58131e23c1b1dc1ea90e2f0c43/src/main/bind.c#L1039). It seems to be test if it's an object first. This is somewhat different than how other functions do dispatching. – MrFlick Dec 01 '15 at 16:12
  • Thank you. Obviously the object I really need this for will have an explicit class. I was just testing the idea with my toy example, and it got me into trouble. It DOES work correctly for my real situation. – Russ Lenth Dec 01 '15 at 16:38
  • @Roland So it is only possible to get S3 method dispatch for objects `x` where `is.object(x)` returns `TRUE`? – cryo111 Dec 01 '15 at 16:45
  • 1
    @Frank My answer is specific to `rbind`'s method dispatch. If you have a normal S3 generic it doesn't apply. – Roland Dec 01 '15 at 17:52
3

A workaround would be to define your own class:

b <- "abc"
class(b) <- "mycharacter"
rbind.mycharacter <- function(...) {
   do.call("paste", list(...))
}
rbind(b, b)
# [1] "abc abc"

The reason why it does not work with character was nicely explained by Roland in his comment.

thothal
  • 16,690
  • 3
  • 36
  • 71
  • 1
    You can, also, work with "character"s if you specify "b" "character" as an object's class: `attributes(b) = list(class = "character"); rbind(b, b)` – alexis_laz Dec 01 '15 at 18:18
1

rbind is not a standard S3 function, so you cannot "intercept" it for character.

Luckily, you can override the default implementation. Try:

rbind.character <- function(...) {

  print("hello from rbind.character")
}

rbind <- function(...) {

  args <- list(...)

  if (all(vapply(args, is.character, logical(1)))) {

    rbind.character(...)
  } else {

    base::rbind(...)
  }
}

Basically, we check if the arguments are all characters. If so, we call our character function. If not, we call the default implementation.

sdgfsdh
  • 33,689
  • 26
  • 132
  • 245