简体   繁体   中英

Prevent coersion to a single type in unlist() or c(); passing arguments to wrapper functions

Is there a simple way to flatten a list while retaining the original types of list constituents?.. Is there a way to programmatically construct a flattened list out of different constituent types?..

For instance, I want to create a simple wrapper for functions like png(filename,width,height) that would take device name, file name, and a list of options. The naive approach would be something like

my.wrapper <- function(dev,name,opts) { do.call(dev,c(filename=name,opts)) }

or similar code with unlist(list(...)) . This doesn't work because opts gets coerced to character, and the resulting call is eg png(filename,width="500",height="500") .

If there's no straightforward way to create heterogeneous lists like that, is there a standard idiomatic way to splice arguments into functions without naming them explicitly (eg do.call(dev,list(filename=name,width=opts["width"]) )?

-- Edit --

Gavin Simpson answered both questions below in his discussion about constructing wrapper functions. Let me give a summary of the answer to the title question:

It is possible to construct a list with c() provided at least one of the arguments to c() is a list. To wit:

> foo <- c("a","b"); bar <- 1:3
> c(foo,bar)
[1] "a" "b" "1" "2" "3"
> typeof(c(foo,bar)) 
[1] "character"  ## note that the result is not a list and that coercion occurred
> c(list(foo),list(bar)) ## try also c(foo,list(bar))
[[1]]     [1] "a" "b"
[[2]]     [1] 1 2 3
> typeof(c(foo,list(bar)))  
[1] "list"       ## now the result is a list, so no need to coerce to same type
> c(as.list(foo),as.list(bar)) ## this creates a flattened list, as desired
[[1]]     [1] "a"
[[2]]     [1] "b"
[[3]]     [1] 1
[[4]]     [1] 2
[[5]]     [1] 3

No, as unlist and c (when applied to atomic vectors of different types) are creating an atomic vector and by definition they must be of a single type. A list in R is the most generic of vectors and you can use it, in fact the "args" argument of do.call asks for a list and you are supplying an atomic vector (through your use of c() ).

Why use do.call when all you are doing is generating a new device? If all you want is a wrapper to png that sets some defaults so you don't have to type all these out each time you want to use a png device, then you are being too complicated. Something like this will suffice:

my.png <- function(name, height = 500, width = 500, ...) {
    png(filename = name, height = height, width = width, ...)
}

If you want a more general wrapper, then something like:

my.wrapper <- function(dev, name, ...) {
    dev(filename = name, ...)
}

should suffice. You would use it like:

my.wrapper(png, "my_png.png", height = 500, width = 200, pointsize = 12)

or

my.wrapper(pdf, "my_pdf.pdf", height = 8, width = 6, version = "1.4")

If you want to work with ... , you can, eg:

my.wrapper2 <- function(dev, name, ...) {
    dotargs <- list(...)
    writeLines("my.wrapper2, called with the following extra arguments:")
    print(dotargs)
    ## do nothing now...
}

Which gives:

> my.wrapper2(pdf, "foo.pdf", height = 10, width = 5, pointsize = 8, 
+             version = "1.3")
my.wrapper2, called with the following extra arguments:
$height
[1] 10

$width
[1] 5

$pointsize
[1] 8

$version
[1] "1.3"

So then you can programatically pull out the arguments you want and o what you want with them.

Something else that just occurred to me that might be of use is that you can use c to concatenate extra components on to lists:

> c(filename = "name", list(height = 500, width = 500))
$filename
[1] "name"

$height
[1] 500

$width
[1] 500

So if you really wanted to apply do.call to a set of arguments, then

my.wrapper3 <- function(dev, name, ...) {
   dotargs <- list(...)
   ## concatenate with name
   callArgs <- c(filename = name, dotargs)
   ## use do.call
   do.call(dev, args = callArgs)
}

But that can be more easily achieved with my.wrapper above.

dev <- "png"
filename <- "foo.png"
width <- 480
height <- 480
opts <- c(width=width, height=height)

my.wrapper <- function(dev,name,opts) {
  name <- list(filename=name)
  do.call(dev, c(name, opts)) 
}
my.wrapper(dev, filename, opts) 

Note that this will work only if opts is a named vector, as do.call needs a list with named entries.

I think the reason you have to add that extra step is that c() does different things depending on the types of its argument - if you want a list, one of the arguments to it has to be a list, otherwise it does the necessary type conversion on all its arguments so that it can create a vector.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM