R decorator to change both input and output

前端 未结 1 980
别那么骄傲
别那么骄傲 2021-01-13 18:34

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_inp         


        
1条回答
  •  离开以前
    2021-01-13 18:57

    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.

    0 讨论(0)
提交回复
热议问题