3
> foo <- structure(list(one=1,two=2), class = "foo")

> cat(foo)
Error in cat(list(...), file, sep, fill, labels, append) : 
  argument 1 (type 'list') cannot be handled by 'cat'

OK I'll add this to the generic cat:

> cat.foo<-function(x){cat(foo$one,foo$two)}
> cat(foo)
Error in cat(list(...), file, sep, fill, labels, append) : 
  argument 1 (type 'list') cannot be handled by 'cat'

No dice.

Argalatyr
  • 4,639
  • 3
  • 36
  • 62
Jeremy Leipzig
  • 1,914
  • 3
  • 21
  • 26
  • Just writing a new function with name of the form function.class doesn't do much if the function wasn't generic in the first place. With that said I haven't tried it yet but I have a feeling the fact that cat has ... as its first parameter might cause some complications – Dason Feb 19 '14 at 19:57
  • Is there anything explicitly wrong with calling cat.foo directly? `cat.foo(foo)` – Dason Feb 19 '14 at 19:58
  • a shortcut solution is to define `print.foo`. – agstudy Feb 19 '14 at 20:00
  • ```> print.foo<-function(x){cat(foo$one,foo$two)} > cat(foo) Error in cat(list(...), file, sep, fill, labels, append) : argument 1 (type 'list') cannot be handled by 'cat'``` – Jeremy Leipzig Feb 19 '14 at 20:06
  • 2
    I think agstudy is implying that you use print instead of cat. Why do you *need* to use cat for this? `print` is already generic so it's easy enough to write your own methods for you own classes. – Dason Feb 19 '14 at 20:08
  • the whole point of generics is to allow users to reuse the commands they are familiar with – Jeremy Leipzig Feb 19 '14 at 20:26
  • @JeremyLeipzig except that `cat()` isn't generic, so you generally wouldn't expect it to behave differently with different inputs – hadley Feb 19 '14 at 22:21

2 Answers2

4

You can't. cat() is not a generic function so you can't write methods for it.

You could make new version of cat() that is generic:

cat <- function(..., file = "", sep = " ", fill = FALSE, labels = NULL,
                append = FALSE) {
  UseMethod("cat")
}
cat.default <- function(..., file = "", sep = " ", fill = FALSE, labels = NULL,
                append = FALSE) {
  base::cat(..., file = file, sep = sep, fill = fill, labels = labels, 
    append = append)
}

But the semantics of dispatching on ...is not well defined (I couldn't find where, if anywhere, it's documented). It looks like dispatch occurs based only on the first element in ...:

cat.integer <- function(...) "int"
cat.character <- function(...) "chr"
cat(1L)
#> [1] "int"
cat("a")
#> [1] "chr"

This means that the class of the second and all subsequent arguments is ignored:

cat(1L, "a")
#> [1] "int"
cat("a", 1L)
#> [1] "chr"

If you want to add a foo method to cat(), you just need a little extra checking:

cat.foo <- function(..., file = "", sep = " ", fill = FALSE, labels = NULL,
                    append = FALSE) {
  dots <- list(...)
  if (length(dots) > 1) {
    stop("Can only cat one foo at a time")
  }
  foo <- dots[[1]]
  cat(foo$one, foo$two, file = file, sep = sep, fill = fill, labels = labels, 
    append = append)
  cat("\n")
}
foo <- structure(list(one=1,two=2), class = "foo")
cat(foo)
#> 1 2
hadley
  • 102,019
  • 32
  • 183
  • 245
  • 2
    I don't think you make a convincing argument here. Are you trying to say nobody can make their own generic functions? (I know that's not the argument you're trying to make - but it isn't clear why you couldn't define cat to be a generic and have cat.default make a call to base::cat). I think I actually got it to work but it's a kludge and I don't like it so I'm not posting my workaround as a solution for the time being - but I would like to hear what you have to say on this. – Dason Feb 19 '14 at 20:12
  • @Dason well it's important to add methods to `base::cat` because it's not generic. You can of course write your own generic function and call it `cat()`, but the semantics of `...` dispatch are tricky, as I describe in my updated answer. – hadley Feb 19 '14 at 22:18
  • Now that's a much nicer answer. Thanks! – Dason Feb 20 '14 at 03:58
2

If the example in your post is what you're actually trying to achieve and not just some toy example to explain your point, you could simply redefine cat to handle lists in the desired way:

cat <- function(...) do.call(base::cat, as.list(do.call(c, list(...))))

R> cat(list(1,2))
1 2R> cat(list(1,2), sep=',')
1,2R> cat(c(1,2))
1 2R> cat(c(1,2), sep=',')
1,2R> 
Matthew Plourde
  • 43,932
  • 7
  • 96
  • 113