Capturing calls in the context of higher order functions

Hello,

I am trying to build a function f that wraps any other function g and returns two things:

  • the result of g applied to the supplied arguments
  • the call of g

I came up with the following:

my_function <- function(.f){

  function(...){

    the_functions <- enquo0(.f)

    the_args <- enquos0(...)

    list(the_result = .f(...),
         the_function = the_function,
         the_args = the_args)


  }

}

which kinda works, but something surprising (to me) happens:

sqrt2 <- my_function(sqrt)

If I call it once on sqrt2 I get something looking ok:

sqrt2(2)
$the_result
[1] 1.414214

$the_function
<quosure>
expr: ^sqrt
env:  global

$the_args
$the_args[[1]]
<quosure>
expr: ^2
env:  empty

but if I run it a second time, the result of the_function is now different:

$the_result
[1] 1.414214

$the_function
<quosure>
expr: ^.Primitive("sqrt")
env:  empty

$the_args
$the_args[[1]]
<quosure>
expr: ^2
env:  empty

the quosure the_function now shows ^.Primitive("sqrt") instead of just sqrt as before. The environment is also different, and I don’t understand why that is.

Also, ideally, what I’d like would to get an output similar to this:


$the_result
[1] 1.414214

$the_call
sqrt(2)

basically sqrt2 would return the result of sqrt applied to 2, and the call, in this case, sqrt(2) (notice I’m talking about sqrt() not sqrt2().

Something like this would be nice as well:

mtcars %>%
  filter2(am == 1)

here the returned call would look like filter(mtcars, am == 1). I fell like I’m getting close! Any help appreciated.

Likley has something to do with this: Functional programming · Advanced R., specifically the section called "mutable state".

I think this does it

my_function <- function(.f){
  require(rlang)
  
  fstring <-  deparse(substitute(.f))
  function(...){
    args <- paste0(enexprs(...),collapse=",")
    the_function_call <- paste0(fstring,"(",args,")")
    
    list(the_result = .f(...),
         the_function_call = the_function_call
    )   
  }
}

the filter as is more complex, as its a method. but we can do

filter2 <- my_function(dplyr:::filter.data.frame)
filter2(mtcars,am == 1)

the piped version will also work, only it hides the left side pipe terms as its represented by the . shortcut

Reading your code, I let out a big "OOF"; I tried using deparse(substitute(.f)), but did so inside the internal function, and not the enclosing function as you did! :man_facepalming:

Thank you very much, this really helps me! However, I’m not sure I understand your point regarding filter, as the following works as expected:

filter2 <- my_function(dplyr::filter)

mtcars %>%
  filter2(am == 1)

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.