Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Strange bug with capturing namespaces #94

Closed
dselivanov opened this issue Sep 5, 2016 · 2 comments

Comments

@dselivanov
Copy link

commented Sep 5, 2016

First of all many thanks for the huge amount of work done by R6 developers and contributors.
I use R6 v2.1.3.
Recently I discovered a strange behaviour of R6 classes (looks like a bug) - when instance of the class is cloned, it can't correctly capture namespace/environment of the member function.
Description above can looks unclear, so it will be simpler to consider following example (my use case a little bit more complex - I use R6 to create iterators which can apply some function to the chunk after $next() call):

# create trait-like class with a placeholder for function
fun_caller = R6::R6Class(
  public = list(
    initialize = function(f_) self$f = f_,
    f = NULL,
    call_f = function(x) self$f(x)
  )
)
library(tokenizers)
# from this library we will need `tokenize_words()` function. 
# It internally use some utility function `check_input()` which is not exposed!
fun_caller_instance = fun_caller$new(tokenize_words)

fun = function(caller, txt) {
  # clone object because I don't want to modify input to be consistent with usual R behaviour
  internal_caller = caller$clone() # fails for both deep = TRUE or FALSE
  internal_caller$call_f(txt)
}
fun(fun_caller_instance, "abc def")

Error in self$f(x) : could not find function "check_input"
3. self$f(x)
2. internal_caller$call_f(txt)

  1. fun(fun_caller_instance, "abc def")

check_input - is a internal function in tokenizers package and it is not exported!

However this works fine:

# note that there is no clone() call
fun2 = function(caller, txt) {
  caller$call_f(txt)
}
fun2(fun_caller_instance, "abc def")

[[1]]
[1] "abc" "def"

And this also works:

tokenize_words2 = function(x, ...) tokenize_words(x, ...)
fun_caller_instance = fun_caller$new(tokenize_words2)

fun3 = function(caller, txt) {
  internal_caller = caller$clone() # fails for both deep = TRUE or FALSE
  internal_caller$call_f(txt)
}
fun3(fun_caller_instance, "abc def")

[[1]]
[1] "abc" "def"

@dselivanov

This comment has been minimized.

Copy link
Author

commented Sep 9, 2016

@wch If this involves deep digging, can you suggest some workarounds?

@wch

This comment has been minimized.

Copy link
Member

commented Sep 9, 2016

The reason it happens is because the clone method makes this assumption: any members of the R6 object that are functions are also methods on the object, and it will reassign the environment of the function after it copies it to the new object. I'm not sure whether this is behavior that I'd want to keep or not -- there are some pros and cons to it.

One thing you could do for now is simply wrap the function in a list, so that clone won't think it's a method and reassign the environment. So you'd have something like this:

fun_caller = R6::R6Class(
  public = list(
    initialize = function(f_) self$f = list(f_),
    f = list(),
    call_f = function(x) self$f[[1]](x)
  )
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants
You can’t perform that action at this time.