Ramiro Magno
Ramiro Magno

Reputation: 3175

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

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?

Upvotes: 2

Views: 778

Answers (2)

Artem Sokolov
Artem Sokolov

Reputation: 13731

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

Upvotes: 2

akrun
akrun

Reputation: 887851

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

Upvotes: 2

Related Questions