Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

R decorator to change both input and output

Tags:

decorator

r

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.

like image 749
vathymut Avatar asked Mar 19 '23 20:03

vathymut


1 Answers

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.

like image 57
Spacedman Avatar answered Mar 31 '23 22:03

Spacedman