Search code examples
rfunctionpurrrtidyeval

Passing . . . to `map()` using tidyeval


I want to pass dots ... to my map() using a quosure so I can evaluate the argument provided.

Passing ... directly works, but quoting ... and splicing using !!! causes an error.

I've tried using the various rlang functions i.e. syms, quos, enquos, etc. I've done my best to read through Advanced-R Chapters 17-20.

I've created a function f which draws from a random normal distribution for every number up until n. I can pass other parameters to rnorm() using the dots. This works fine.

library(magrittr)
library(purrr)


f <- function(n, ...) {
1:n %>% 
  map(rnorm, ...)
}


f(2, mean = 10)
#> 
#> [[1]]
#> [1] 10.90561
#> 
#> [[2]]
#> [1] 11.19031 12.16856
f(2)
#> [[1]]
#> [1] -1.197873
#> 
#> [[2]]
#> [1]  1.023398 -2.023645

I've created a second function g() which is intended to do the same thing but using tidy eval.

g <- function(n, ...) {

  1:n %>% 
    map(rnorm, !!!enquos(...))
}

g(3)

#> 
#> [[1]]
#> [1] NA
#> 
#> [[2]]
#> [1] NA NA
#>
#> Warning messages:
#> 1: In .f(.x[[i]], ...) : NAs produced
#> 2: In .f(.x[[i]], ...) : NAs produced

This implementation produces NA.

I've tried using list2() for this but still am unable to get it to evaluate. Is there a way to evaluate the dots using tidyeval within a map call?


Solution

  • There is no way to do this with purrr::map(). The dots are passed to the mapped function as is, without any changes. So it is the responsibility of the mapped function to implement tidy dots by capturing ... with enquos() or list2().

    What you could do is create your own mapping function that takes dots with !!! support, creates a call with the arguments spliced in, and maps that call over the inputs:

    map_splice <- function(.x, .f, ...) {
      n <- length(.x)
      out <- vector("list", n)
    
      # Capture arguments with `!!!` support
      args <- list2(...)
    
      # Create call with arguments spliced in
      call <- expr(.f(.x[[i]], !!!args))
    
      for (i in seq_len(n)) {
        out[[i]] <- eval(call)
      }
    
      out
    }
    
    h <- function(n, ...) {
      map_splice(seq_len(n), rnorm, ...)
    }
    
    h(3)
    #> [[1]]
    #> [1] 0.198766
    #>
    #> [[2]]
    #> [1]  0.99824414 -0.08231719
    #>
    #> [[3]]
    #> [1]  0.5288825 -0.6098536  0.7858720
    
    args <- list(mean = 50, sd = 3)
    h(3, !!!args)
    #> [[1]]
    #> [1] 49.32423
    #>
    #> [[2]]
    #> [1] 57.51101 46.69594
    #>
    #> [[3]]
    #> [1] 53.80943 50.00880 54.96616