3

This might seem like a silly question, but for the life of me I cannot figure it out so appreciate any help.

I am trying to pass .x (from purrr::map) to a function so in the end I get a list of functions (I will use this eventually to pass a list of column definitions with functions inside to reactable). This is a very basic example of what I want to do:

a = list("b", "c")

t <- map(a, ~{
  function(.x){
    print(x)
  }
})

The result I am looking for is:

> t
[[1]]
function(b){
    print(x)
  }

[[2]]
function(c){
    print(x)
  }

but I get:

> t
[[1]]
function(.x){
    print(x)
  }

[[2]]
function(.x){
    print(x)
  }

Is what I am looking for possible?

Gakku
  • 337
  • 2
  • 8
  • 1
    Can you clarify what you want your functions to do? As written it looks like they would all just print whatever is passed to them, just with different argument names. Maybe you’re looking for something more like `~ function() print(!!.x)`? – zephryl Feb 12 '22 at 12:57
  • Thanks - the list will eventually be part of a reactable table describing the definitions of each column (eg name, content of cells etc). The function needs to have two arguments, the first a column name and the second called index. My problem is that I need a dynamic way to modify the first argument. I could do it all manually but I'd like to learn if there is a way of using map (or lapply or similar) to do this. So essentially I need the content of list a above to be passed on as the argument in the function. (Sorry if not terribly clear) – Gakku Feb 12 '22 at 13:06
  • 1
    caldwellst’s elegant answer should get you there — but for general reference, the [chapter on function factories in *Advanced R*](https://adv-r.hadley.nz/function-factories.html) is helpful. The sections on metaprogramming may also be relevant. – zephryl Feb 12 '22 at 13:20
  • @zephryl many thanks. This is all slightly beyond my level of R but hopefully will get there! – Gakku Feb 12 '22 at 15:13

1 Answers1

3

You can get what you want by using a wrapper function to create a new function. Within the wrapper, create an empty function, set the args to the value x passed in (so if x="a", with give the equivalent of alist(a=)), and then also parse a string to the body. Then the returned list will be the functions you want.

library(purrr)

function_crafter <- function(x) {
  f <- function() {}
  args <- setNames(list(bquote()), x)
  formals(f) <- args
  body(f) <- parse(text = paste0("print(", x, ")"))
  f
}

a <- list("b", "c")

fl <- map(a, function_crafter)

fl[[1]](b=2)
#> [1] 2
fl[[2]](c=39)
#> [1] 39

fl
#> [[1]]
#> function (b) 
#> print(b)
#> <environment: 0x7fb68752aae0>
#> 
#> [[2]]
#> function (c) 
#> print(c)
#> <environment: 0x7fb687552818>

If you actually wanted the print() in each function to simply be print(x) rather than print(b) or print(c), then that's even easier, just define the body of the function in its initialization.

caldwellst
  • 5,719
  • 6
  • 22
  • Many thanks this is really helpful! I had never created a function in this way and I can see how it can be extremely useful in many different situations. – Gakku Feb 13 '22 at 07:35