Search code examples
rr6

Creating dynamically methods in R6Class, magic of print(ls.str())


In R, I wanted to create a class (R6Class) that when calling initialize creates few dynamic methods (the number of methods and its names depends on parameters in initialize). But I get into strange problem with environments.

Here is a simplified version of code that doesn't work.

library(R6)

ffactory <- function(i) {
  function() i
}

A <- R6Class(
  lock_objects=FALSE,
  public=list(
    initialize=function(args) {
      for (i in args) {
        self[[i]] <- ffactory(i)
      }
    }
  )
)

a <- A$new(c('a', 'b', 'c'))

Now:

> a$a()
[1] "c"
> a$b()
[1] "c"
> a$c()
[1] "c"

In order to find what was wrong I had added a line that prints environment in ffactory function. That is

ffactory <- function(i) {
  print(ls.str())
  function() i
}

And now it has started to work!!!

> a$a()
[1] "a"
> a$b()
[1] "b"
> a$c()
[1] "c"

So why? There should be something I don't understand. Observer effect or what? :)

What is the magic of the line print(ls.str())? Actually I cannot remove neither print nor str from this line. Of course it is so silly to have a line like that. Not to mention garbage on the screen.


Solution

  • You have encountered lazy evaluation - R waits as long as it is able to before evaluating i - and in the former case, i will be evaluated at its last value in all instances. There's nothing really special about the combination of print and ls.str; anything that forces i to be evaluated prior to your method calls (a$a(), a$b(), etc...) will do the same.

    Formally, this is what force is used for:

    ffactory <- function(i) {
      force(i);
      function() i
    }
    
    R> a$a()
    #[1] "a"
    R> a$b()
    #[1] "b"
    R> a$c()
    #[1] "c"
    

    However, this also happens to do the job:

    ffactory <- function(i) {
      #force(i);
      .z <- capture.output(cat(i, "\n"))
      function() i
    }
    
    R> a$a()
    #[1] "a"
    R> a$b()
    #[1] "b"
    R> a$c()
    #[1] "c"
    

    There are presumably countless ways to force evaluation; I would argue that using force makes your intention most clear, though.

    Quoting the help file directly,

    force forces the evaluation of a formal argument. This can be useful if the argument will be captured in a closure by the lexical scoping rules and will later be altered by an explicit assignment or an implicit assignment in a loop or an apply function.

    and subsequently,

    This is semantic sugar: just evaluating the symbol will do the same thing (see the examples).

    In fact, looking at how force is defined,

    R> force
    #function (x) 
    #  x
    #<bytecode: 0x3b7b528>
    #<environment: namespace:base>
    

    You could even get away with

    ffactory <- function(i) {
      i; function() i
    }
    

    But as noted, I think the explicitly calling force will make your code more readable.