Estoy escribiendo una función que acepta un número variable de argumentos. Además, me gustaría que el usuario pudiera dejar algunos de estos argumentos como faltantes.

Considere simplemente la tarea de convertir ... en una lista de argumentos. Aqui esta mi primer intento:

f <- function(...) list(...)

Esto falla:

f(1,,2)
## Error in f(1, , 2) : argument is missing, with no default

Me gustaría que el resultado fuera list(1, NULL, 2). (No me preocupa distinguir f(1,,2) de f(1,NULL,2)).

Aquí está mi segundo intento:

g <- function(...)
{
    args <- match.call()
    miss <- vapply(args, identical, NA, quote(expr=))
    args[miss] <- list(NULL)
    args[[1L]] <- quote(list)
    eval.parent(args)
}

Esto funciona (más o menos):

identical(g(1,,2), list(1, NULL, 2))
## [1] TRUE

Sin embargo, falla al reenviar puntos:

gg <- function(...) g(...)
identical(gg(1,,2), list(1,NULL,2))
## [1] FALSE

¿Hay alguna manera de lograr los mismos resultados que g, pero de una manera que permita reenviar puntos y sin usar match.call()?

Editar: problemas con el reenvío de puntos.

Es tentador probar lo siguiente:

h <- function(...)
{
    args <- substitute(list(...))
    miss <- vapply(args, identical, NA, quote(expr=))
    args[miss] <- list(NULL)
    eval.parent(args)
}

Sin embargo, como señala @lionel, habrá problemas si intenta reenviar ... desde otra función:

hh <- function(...) h(letters, ...)
local({ a <- "foo"; h(a) })
## Error in eval(expr, p) : object 'a' not found

Parece que necesita la funcionalidad expand.dots de match.call, o necesita usar un quosure (del paquete rlang ).

r
4
Patrick Perry 15 nov. 2017 a las 22:45

2 respuestas

La mejor respuesta

Con el paquete rlang, tiene la opción de ignorar los argumentos vacíos al capturar puntos:

my_dots <- function(...) {
  rlang::dots_list(..., .ignore_empty = "all")
}

my_dots(1, , 2, )
#> [[1]]
#> [1] 1
#>
#> [[2]]
#> [1] 2

Si realmente necesita traducir argumentos vacíos como NULL, no hay otra opción que citar los puntos y evaluarlos después de haber convertido los argumentos faltantes. Ahora bien, si hace eso con eval(substitute(alist(...)), tendrá problemas si intenta reenviar puntos a través de varias llamadas a funciones. Base R no proporciona forma de evaluar el argumento citado en su entorno original.

Afortunadamente, es fácil de hacer capturando los puntos en cotizaciones:

library("purrr")  # For convenient map_if()

my_dots <- function(...) {
  quos <- rlang::quos(..., .ignore_empty = "none")
  quos <- map_if(quos, rlang::quo_is_missing, function(x) NULL)
  map(quos, rlang::eval_tidy)
}

my_dots(1, , 2, )
#> [[1]]
#> [1] 1
#>
#> [[2]]
#> NULL
#>
#> [[3]]
#> [1] 2
#>
#> [[4]]
#> NULL
10
Lionel Henry 15 nov. 2017 a las 20:18

@lionel me dio la mayor parte de la información que necesitaba, así que estoy marcando su respuesta como correcta. Lo siguiente parece funcionar:

h <- function(...)
{
    args <- substitute(list(...))
    miss <- vapply(args, identical, NA, quote(expr=))
    args[miss] <- list(NULL)
    eval.parent(args)
}

Sin embargo, puede haber problemas con el reenvío de ... desde otras funciones.

0
Patrick Perry 16 nov. 2017 a las 03:40