Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clean, simple function factories in R

Short example. I am exploring the behavior of a function by testing it with different "specs", f(spec). I wrote down one spec by hand, spec1, and am creating new specs as variations on it. To do this, I decided to write a function:

spec1 = list(fy = list(a = 1), fx = list(f1 = function(x) 10-x, f2 = function(x) 2-x))

make_spec = function(f = function(x) 10-x, xtheta = 2)
    list(fy = list(a = 1), fx = list(f1 = f, f2 = function(x) xtheta-x))

res1 = make_spec()

# first problem: they don't match

    all.equal(res1,spec1)
    # [1] "Component “fx”: Component “f2”: target, current do not match when deparsed"
    # ^ this happens, even though...
    res1$fx$f2(4) == spec1$fx$f2(4)
    # TRUE

# second problem: res1 is fugly

    res1
    # $fy
    # $fy$a
    # [1] 1
    # 
    # 
    # $fx
    # $fx$f1
    # function (x) 
    # 10 - x
    # <environment: 0x000000000f8f2e20>
    # 
    # $fx$f2
    # function (x) 
    # xtheta - x
    # <environment: 0x000000000f8f2e20>

    str(res1)
    # even worse

My goals for make_spec are...

  1. all.equal(spec1, res1) and/or identical(spec1, res1)
  2. for str(res1) to be human-readable (no <environment: ptr> tags or srcfilecopy)
  3. to avoid substitute and eval altogether if possible (not a high priority)
  4. to avoid writing out the second arg of substitute (see "full" example below)

Is there an idiomatic way to achieve some or all of these goals?


Full example. I'm not sure if the example above fully covers my use case, so here's the latter:

spec0 = list(
    v_dist = list(
        pdf  = function(x) 1,
        cdf  = function(x) x,
        q    = function(x) x,
        supp = c(0,1)
    )
    ,
    ucondv_dist = {
        ucondv_dist = list()
        ucondv_dist$condmean    = function(v) 10-v
        ucondv_dist$pdf         = function(u,v) dnorm(u, ucondv_dist$condmean(v), 50)
        ucondv_dist$cdf         = function(u,v) pnorm(u, ucondv_dist$condmean(v), 50)
        ucondv_dist
    }
)

make_spec = function(ycondx_condmean = function(x) 10-x, ycondx_sd = 50){

  s = substitute(list(
    x_dist = list(
      pdf  = function(x) 1,
      cdf  = function(x) x,
      q  = function(x) x,
      supp = c(0,1)
    )
    ,
    ycondx_dist = {
      ycondx_dist = list()
      ycondx_dist$condmean  = ycondx_condmean
      ycondx_dist$pdf     = function(u,v) dnorm(u, ycondx_dist$condmean(v), ycondx_sd)
      ycondx_dist$cdf     = function(u,v) pnorm(u, ycondx_dist$condmean(v), ycondx_sd)
      ycondx_dist
    }
  )
  , list(ycondx_condmean=ycondx_condmean, ycondx_sd = ycondx_sd))

  eval(s, .GlobalEnv)
}

res0 = make_spec()

Side note. I don't know if "function factory" is the right term here, since I am not a computer scientist, but it seems related. I found only a paragraph on the concept related to R.

like image 769
Frank Avatar asked Mar 22 '16 18:03

Frank


People also ask

What are the functions of factory?

The Factory Function is similar to constructor functions/class functions, but instead of using new to create an object, factory functions simply creates an object and returns it. Factory Functions are a very useful tool in JavaScript.

What is Factory R?

The Factory-R maintains and extends the typical features of the Factory release. These features allow you to keep the engine weight low: use of carbon components, titanium screws, fully machined components.

What is closure R?

Closures are used for creating functions within a function in the R environment. These are useful when the functions are changing/ repeating in the code with the same dataset.

Why is R functional programming?

Strictly speaking, R isn't a functional programming language because it doesn't require that you write pure functions. However, you can certainly adopt a functional style in parts of your code: you don't have to write pure functions, but you often should.


1 Answers

The enclosing environments of the functions are different leading to the difference in output/difference in deparsing. So, there are two things to do to get the desired output:

  • make the environments the same
  • substitute the variables from the enclosing environments into the function bodies.

However, doing it this way you get a double dose of the eval/substitute you didn't want, so maybe there would be an alternative.

make_spec <- function(f = function(x) 10-x, xtheta = 2) {
  e <- parent.frame()
  fixClosure <- function(func)
    eval(eval(substitute(substitute(func)), parent.frame()), e)

  list(fy = list(a = 1), fx = list(
    f1 = fixClosure(f), 
    f2 = fixClosure(function(x) xtheta-x)
  ))
}

spec1 <- list(fy = list(a = 1), fx = list(f1 = function(x) 10-x, f2 = function(x) 2-x))
res1 <- make_spec()

all.equal(res1, spec1)
[1] TRUE
like image 95
Rorschach Avatar answered Sep 30 '22 01:09

Rorschach