How to use purrr::map (not to be iterated) additional arguments?

Viewed 337

I thought I had understood how to use the additional arguments argument (...) of purrr::map. Here is some code that hopefully illustrates the (to me) unexpected behaviour of purrr::map:

It seems that passing argument a as additional argument in purrr::map is not working:

library(purrr)

f <- function(a, b) {
  a + b
}

g <- function(a = 0, b) {
  a + b
}

map(1:3, .f = ~ f(b = .x, a = 1))
#> [[1]]
#> [1] 2
#> 
#> [[2]]
#> [1] 3
#> 
#> [[3]]
#> [1] 4
map(1:3, .f = ~ f(b = .x), a = 1)
#> Error in f(b = .x): argument "a" is missing, with no default

map(1:3, .f = ~ g(b = .x, a = 1))
#> [[1]]
#> [1] 2
#> 
#> [[2]]
#> [1] 3
#> 
#> [[3]]
#> [1] 4
map(1:3, .f = ~ g(b = .x), a = 1)
#> [[1]]
#> [1] 1
#> 
#> [[2]]
#> [1] 2
#> 
#> [[3]]
#> [1] 3

lapply(1:3, function(b, a = 1) f(a, b))
#> [[1]]
#> [1] 2
#> 
#> [[2]]
#> [1] 3
#> 
#> [[3]]
#> [1] 4
lapply(1:3, function(b, a) f(a, b), a = 1)
#> [[1]]
#> [1] 2
#> 
#> [[2]]
#> [1] 3
#> 
#> [[3]]
#> [1] 4

My question is why does the code:

map(1:3, .f = ~ f(b = .x), a = 1)

throw an error?

2 Answers

We could pass the remaining arguments without any anonymous function

library(purrr)
map(1:3, f, a = 1)
#[[1]]
#[1] 2

#[[2]]
#[1] 3

#[[3]]
#[1] 4

Or another option is rlang::as_function or purrr:as_mapper

map(1:3, as_mapper(f), a = 1)

Or create the f on the fly

map(1:3, as_mapper(~ .x + .y), a = 1)

Or call it in invoke

map(1:3, ~ invoke(f, b = .x, a = 1))
#[[1]]
#[1] 2

#[[2]]
#[1] 3

#[[3]]
#[1] 4

This would make it more easier to read than the .f = ~ f(b = .x), a = 1

Behind the scenes, map() calls as_mapper(). We can do this by hand to see what's going on:

purrr::as_mapper( ~ f(b = .x, a = 1) )
# <lambda>
# function (..., .x = ..1, .y = ..2, . = ..1) 
# f(b = .x, a = 1)                                <----
# attr(,"class")
# [1] "rlang_lambda_function" "function"


purrr::as_mapper( ~ f(b = .x), a=1 )
# <lambda>
# function (..., .x = ..1, .y = ..2, . = ..1) 
# f(b = .x)                                       <----
# attr(,"class")
# [1] "rlang_lambda_function" "function"           

I highlighted the important distinction with <---. Notice that in the second case, the lambda function that gets created does not incorporate your extra a=1 parameter, which leads to the error you are observing.

To address your comment, a=1 actually is being passed to the lambda function. Your lambda function just isn't doing anything with it. To properly incorporate a, the lambda function definition needs to handle the ... dots:

g <- function(a, b, ...) {a + b}               # ... are needed to catch all extra 
                                               #   arguments from as_mapper

purrr::as_mapper( .f = ~ g(b=.x, ...) )
# <lambda>
# function (..., .x = ..1, .y = ..2, . = ..1) 
# g(b = .x, ...)                               <-- dots are now forwarded to g()
# attr(,"class")
# [1] "rlang_lambda_function" "function"            

purrr::map(1:3, .f = ~ g(b=.x, ...), a=1 )     # a now properly gets passed to g
# [[1]]
# [1] 2
#
# [[2]]
# [1] 3
#
# [[3]]
# [1] 4
Related