4

I've read several SO answers and still not able to piece together how S3 methods need to be documented when they have different arguments. In the example below, I want the user to be able to enter two objects or pipe in a list. The code works fine. It just spits out a warning about method consistency. I've tried moving the list param around but always get some combination of the warning of either foo != foo.list or foo != foo.default. There is a dummy reprex in the collapsed code section and relevant links at the end. Thanks!

#' does some stuff
#' @param list for list methods
#' @param x arg1
#' @param y arg2
#' @param ... some argument
#' @export
foo <- function(list, x, y, ...) UseMethod('foo')

#' @export
foo.default <- function(x, y, ...) paste(x, y, ...)

#' do stuff for lists
#' @export
#' @param list for list methods
#' @inheritParams foo
foo.list <- function(list, x, y, ...) foo(list[[x]], list[[y]])

# create dummy package
tmp <- tempdir() 
setwd(tmp)
devtools::create(path = "test")
setwd("test")
usethis::use_mit_license()

# add R code
writeLines(
  con = "R/test.R",
  text = 
"#' does some stuff
#' @param list for list methods
#' @param x arg1
#' @param y arg2
#' @param ... some argument
#' @export
foo <- function(list, x, y, ...) UseMethod('foo')

#' @export
foo.default <- function(x, y, ...) paste(x, y, ...)

#' do stuff for lists
#' @export
#' @param list for list methods
#' @inheritParams foo
foo.list <- function(list, x, y, ...) foo(list[[x]], list[[y]])"
)

devtools::document()
devtools::load_all()

# examples
foo(1, 2)
list(a = 1, b = 2) |> foo("a", "b")

# check
devtools::check(document = FALSE)
 W  checking S3 generic/method consistency (561ms)
 foo:
   function(list, x, y, ...)
     foo.default:
   function(x, y, ...)
     
     See section 'Generic functions and methods' in the 'Writing R
    Extensions' manual.

Here are some related SO posts:

M--
  • 25,431
  • 8
  • 61
  • 93
yake84
  • 3,004
  • 2
  • 19
  • 35
  • 1
    It's not clear what behavior you are trying to achieve here. S3 dispatch works only on the `class()` of the first parameter. You seem to want to dispatch basaed on the number of parameters which S3 can't do. All your `foo` functions need to have the same parameters up to the `...` as the generic version. They can have different parameters after. – MrFlick Aug 07 '21 at 18:06
  • Thanks, I didn't know that about `...` The issue I'm having is getting the help to show in RStudio. If I only put `...` in the generic, the functions work correctly but it doesn't show anything via IntelliSense (autocomplete). The user is usually going to pass a single value `x` and then combine it with `y` but if the user pipes in a list, I want the function to grab the values from the list. What's the best way to do document this so that IntelliSense picks it up? – yake84 Aug 08 '21 at 15:10

2 Answers2

3

I am not sure I would use S3 here. Not answering your specific question, but consider something like this. Of course, this is irrelevant if your actual use case is far enough away from your example.

foo <- function(x, y) {
  if (is.list(x)) return(do.call(foo, x))
   
  paste(x, y)
}

So now you can get the behavior you want, and just document x as being a list of arguments or the first argument. Otherwise, you'll create some annoying things such as requiring the user to explicitly name the argument every time.

foo("a", "b")
# [1] "a b"

foo(list(x = "a", y = "b"))
# [1] "a b"

foo(list(y = "a", x = "b"))
# [1] "b a"

foo("a", "b", z = "c")
# Error in foo("a", "b", z = "c") : unused argument (z = "c")

foo(list("a", "b", z = "c"))
# Error in (function (x, y)  : unused argument (z = "c")

If compelled, you can accomplish the same thing using S3. But either way, you probably want to just name the argument the same thing and document it as having two meanings.

foo <- function(x, y, ...) UseMethod('foo')

foo.default <- function(x, y, ...) paste(x, y)

foo.list <- function(x, y, ...) do.call(foo, x)

An example of this is the plot function in base.

x the coordinates of points in the plot. Alternatively, a single plotting structure, function or any R object with a plot method can be provided.

y the y coordinates of points in the plot, optional if x is an appropriate structure.

Here, x is either the x coordinate or a valid plotting structure. And then y is only used if needed.

1

As @MrFlick mentioned, if you want an S3 method that dispatches based on the number of parameters, this cannot be done. I'm not sure how you want IntelliSense to show the arguments, but maybe you can start with just one argument?

#' does some stuff
#' @param x argument 1, could be a list
#' @param \dots Further arguments passed to each method
#' @export
foo <- function(x, ...) UseMethod('foo', x)

#' @export
foo.default <- function(x, ...) paste(x, ...)

#' do stuff for lists
#' @export
#' @inheritParams foo
#' @param a an element of x
#' @param b another element of x
foo.list <- function(x, a, b, ...) foo(x[[a]], x[[b]])
trangdata
  • 111
  • 1
  • 4