Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to have an active binding know if it's called as a function?

Tags:

I would like something like that:

makeActiveBinding("f", function() {   called_as_a_function <- ... # <- insert answer here   if(called_as_a_function) {      sqrt   } else {     1   } }, .GlobalEnv)  # Expected output  f + f #> 2  f(4) + f #> 3 

I use f here, should work with any function

In the example above f returns 1 and f(4) returns sqrt(4). In my real use case the naked f (not f()) will return a function object, so the workaround proposed by Michal cannot be used as is.

I use + here for simplicity, but it might be any function or none, including NSE functions like quote(), so for instance quote(f) and quote(f()) should not have their input changed by the solution.

I tried to play with the sys.calls() but couldn't get anything robust.

Answers using low level code are welcome too, who knows maybe dark magic can help.

These won't be called at the top level so if you cannot make the above work but can get the following to work for instance that's good too, and in practice it won't be the .GlobalEnv so if you can make it work in another environment that's good too.

identity(f + f) #> 2  identity(f(4) + f) #> 3 

If you have solutions that just get me closer you might post them, for instance if your solution works only if f and f() are not used in the same call it's still useful to me.


Since I was asked about the real context here it is, but solving the above is all I ask.

  • My package {boomer} provides a way to curry a function f by modifying its environment and populating its new enclosure with shims of every function f calls, we say that we rig f.
  • These shims print the calls and their outputs, but behave the same apart from side effects, so f and rigged f are expected to return the same
  • However if the shims are returned, or if their body is manipulated by f, the output will be unexpected
  • By treating shim and shim() differently I avoid the more obvious corner cases, shim() will show side effects, and shim would return the original function.

The issue is here and package in action is showed here

And also tbh I'm generally curious about if it's possible.

like image 668
Moody_Mudskipper Avatar asked Jun 22 '21 20:06

Moody_Mudskipper


People also ask

What is active binding?

An active binding is similar in that we bind a variable name (like "x") to something, but that something is not just a constant value, but rather a function that will be run every time we try to access x .

What is active binding in R?

A cool feature of {R6} is active binding , which is the process of using a symbol which looks like a variable but behave as a function. You can create these with the active method when defining your class.


2 Answers

One trick that comes to my mind is to create two nested environments, one being a parent of another and each having a different definition of f. Then you can evaluate f + f() in the "child" and it will work:

e1 <- new.env() e2 <- new.env(parent = e1) assign("f", sqrt, envir = e1) assign("f", 1, envir = e2) eval(expression(f + f(4)), envir=e2) #> [1] 3 
like image 53
Michał Avatar answered Jan 24 '23 19:01

Michał


Here is a method using the walkast package. It essentially replaces function objects named f with f_fun.

f_fun <- sqrt f <- 1  evaluate <- function(expr) {   expr <- substitute(expr)      eval(     walkast::walk_ast(       expr,       walkast::make_visitor(         hd = function(fun) {           if (all.names(fun) == "f") {             f_fun           } else {             fun           }         }       )     )   ) } 

Expressions need to be wrapped in evaluate.

evaluate(f + f(4)) #> 3  evaluate(f + f) #> 2  evaluate(f(f + f(9)) + f(4)) #> 4 
like image 45
Paul Avatar answered Jan 24 '23 17:01

Paul