I am trying to refactor this. In Python, I would use a decorator. What's the 'R'tful way to do this? Say, we have this pattern
good_input <- format_input( bad_input )
bad_output <- use_this_func( good_input )
good_output <- format_output( bad_output )
And then again,
good_input <- format_input( bad_input )
bad_output <- use_this_other_func( good_input )
good_output <- format_output( bad_output )
As you can imagine, this proliferates like wild mushroom. I want something close to this solution
use_this_robust_func <- wrapper( use_this_func ) # Or wrapper( use_this_other_func )
good_output <- use_this_robust_func( bad_input )
I'm trying to wrap the call to use_this_func
and use_this_other_func
(and related functions) with format_input
and format_output
. Using in part this question, so far I have
wrapper <- function( func_not_robust ){
func_robust <- function( ... ){
# This is the bit I haven't figured out
... format_input( ) ... # supposed to convert bad input - the function argument - to good
bad_output <- func_not_robust( ... ) # supposed to take good input as argument
good_output <- format_output( bad_output )
return( good_output )
}
return( func_robust )
}
Sorry for the pseudo-code. Note I am not sure that this is the way to go in R. I'm not wedded to the sketch of the solution above, which is born from translating Python - and badly at that - to R. How would a R native do this? Thanks in advance.
I think you are pretty much there. Here's an example where the first stage of cleaning is to replace negative input values with NAs, and the output cleaning is simple to negate everything:
format_input <- function(x){
x[x<0] <- NA
return(x)
}
format_output <- function(x){
return(-x)
}
wrapper <- function(f){
force(f)
g = function(bad_input){
good_input = format_input(bad_input)
bad_output = f(good_input)
good_output = format_output(bad_output)
return(good_output)
}
g
}
Then:
> wrapper(sqrt)(c(-2,2))
[1] NA -1.414214
wrapper(sqrt)
returns a "closure", which is a function with enclosed data. The function f
has the value of the function sqrt
as part of that enclosure.
The force
call is needed since f
doesn't get evaluated when g
is created, and in some cases without it then f
won't get found when running the wrapped version due to R's lazy evaluation or "promises" or something. I'm never exactly sure when this happens but adding a force
call to unevaluated arguments to closure generators is zero-overhead. Its a bit cargo-cult programming but never a problem.
A more flexible solution might be to specify the input and output cleaning functions as functions to the closure generator, with defaults:
wrapper <- function(f, fi=format_input, fo=format_output){
force(f) ; force(fi); force(fo)
g = function(bad_input){
good_input = fi(bad_input)
bad_output = f(good_input)
good_output = fo(bad_output)
return(good_output)
}
g
}
Then I can wrap sqrt
with different input and output formatters. For example to change that negative function with a positive one:
> make_pos = function(x){abs(x)}
> wrapper(sqrt,fo=make_pos)(c(-2,2))
[1] NA 1.414214
An even more flexible solution is to spot that you are generating chains of functions here. Your output is format_output(sqrt(format_output(bad_input)))
. This is function composition and there's a function in the functional
package to do that:
> require(functional)
> w = Compose(format_input, sqrt, format_output)
> w(c(-2,2))
[1] NA -1.414214
This perhaps gets more useful when you have more than three functions in your composition, you could for example have a list of functions and compose them all together using do.call
....
Once you see patterns in functional programming its addictive. I'll stop now.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With