diff --git a/NEWS.md b/NEWS.md index 9ccfaa5..114aa45 100644 --- a/NEWS.md +++ b/NEWS.md @@ -7,6 +7,10 @@ R6 2.5.1.9000 * When a superclass is not cloneable, then subclasses cannot be cloneable (@IndrajeetPatil, #247). +* `clone` can now be overriden. A new private `post_clone` method can be defined + to modify private fields after cloning an object. `clone` may now have arbitrary + arguments. `deep_clone` may accept additional extra arguments. (@zeehio, #273). + R6 2.5.1 ======== diff --git a/R/clone.R b/R/clone.R index 04163b9..e595852 100644 --- a/R/clone.R +++ b/R/clone.R @@ -1,6 +1,6 @@ -# This function will be added as a method to R6 objects, with the name 'clone', +# This function will be added as a method to R6 objects, with the name '.clone', # and with the environment changed. -generator_funs$clone_method <- function(deep = FALSE) { +generator_funs$.clone_method <- function(deep = FALSE, post_clone_args = list(), deep_clone_args = NULL) { # Need to embed these utility functions inside this closure because the # environment of this function will change. @@ -237,7 +237,8 @@ generator_funs$clone_method <- function(deep = FALSE) { deep_clone, names(binding_copies), binding_copies, - SIMPLIFY = FALSE + SIMPLIFY = FALSE, + MoreArgs = deep_clone_args ) } @@ -263,7 +264,8 @@ generator_funs$clone_method <- function(deep = FALSE) { deep_clone, names(private_copies), private_copies, - SIMPLIFY = FALSE + SIMPLIFY = FALSE, + MoreArgs = deep_clone_args ) } private_copies <- remap_func_envs(private_copies, old_new_enclosing_pairs) @@ -367,5 +369,15 @@ generator_funs$clone_method <- function(deep = FALSE) { class(new_1_binding) <- class(old_1_binding) + if (has_private && is.function(new[[1]]$private$post_clone)) { + do.call(new[[1]]$private$post_clone, post_clone_args) + } + new_1_binding } + +# This is the public default clone() method, that may be overriden: +generator_funs$clone <- function(deep = FALSE) { + dot_clone <- get(".clone", envir = self) + dot_clone(deep = deep) +} \ No newline at end of file diff --git a/R/r6_class.R b/R/r6_class.R index 5aaf8f7..4e5bbb1 100644 --- a/R/r6_class.R +++ b/R/r6_class.R @@ -66,17 +66,84 @@ #' #' To make a deep copy, you can use \code{x$clone(deep=TRUE)}. With this #' option, any fields that are R6 objects will also be cloned; however, -#' environments and reference class objects will not be. +#' environments and reference class objects will not be unless you customize +#' deep cloning. #' -#' If you want different deep copying behavior, you can supply your own +#' @section Customizing cloning: +#' +#' You can override the \code{clone} method to modify +#' the original object and/or the created one, as you need. +#' +#' The default \code{clone} method is fairly simple: +#' +#' \code{ +#' clone = function(deep = FALSE) \{ +#' self$.clone(deep = deep) +#' \} +#' } +#' +#' This internal \code{.clone} method cannot be overriden. Taking care of +#' all the cloning becomes very hard when there is inheritance and active +#' bindings, so it is recommended that you customize your \code{clone} method +#' using \code{self$.clone()}. +#' +#' +#' \code{ +#' clone = function(deep = FALSE) \{ +#' # Modify self or private from the existing object if you have to +#' # ... +#' # Create the new object: +#' new <- self$.clone(deep = deep) +#' # Private access to new$ is not directly possible here... See post_clone below +#' # ... +#' # Do not forget to return the new object +#' new +#' \} +#' } +#' +#' If you need private access to fields and methods from the new object, you +#' can define a private \code{post_clone} method. \code{self$.clone} will +#' call \code{post_clone} if it exists right after creating the cloned object. +#' \code{post_clone} runs from the new object, so its private members are +#' accessible. \code{post_clone} may have any arbitrary arguments. You will need +#' to pass values for those arguments to \code{self$.clone()} as a list using the +#' \code{post_clone_args} argument: +#' +#' \code{new <- self$.clone(deep = deep, post_clone_args = list(arg1 = value1, arg2 = value2))} +#' +#' \code{self$.clone} will expand \code{post_clone_args} and pass them to \code{post_clone} +#' that, for the example above, should have been defined as a private method +#' with signature: +#' +#' \code{ +#' post_clone = function(arg1, arg2) \{ +#' # ... +#' \} +#' } +#' +#' +#' @section Customizing deep cloning: +#' +#' The default \code{clone} method (via \code{self$.clone()}) even if using +#' \code{deep=TRUE} will not copy environments and reference class objects by +#' default. +#' +#' If you just want different deep copying behavior, you can supply your own #' private method called \code{deep_clone}. This method will be called for -#' each field in the object, with two arguments: \code{name}, which is the +#' each field in the object, with two required arguments: \code{name}, which is the #' name of the field, and \code{value}, which is the value. Whatever the #' method returns will be used as the value for the field in the new clone #' object. You can write a \code{deep_clone} method that makes copies of #' specific fields, whether they are environments, R6 objects, or reference #' class objects. #' +#' Your \code{deep_clone} method may define additional arguments besides \code{name} +#' and \code{value}. If that is your case, you will need to define a custom +#' \code{clone} method, and place the additional arguments that \code{deep_clone} +#' needs in a list, that you will pass to \code{self$.clone()} as +#' the \code{deep_clone_args} argument. \code{self$.clone()} will expand that +#' list and pass it to your \code{deep_clone} method. +#' #' @section S3 details: #' #' Normally the public environment will have two classes: the one supplied in @@ -343,6 +410,64 @@ #' b$remove() #' #> [1] 20 #' +#' # Custom cloning behaviour (clone and post_clone()) --------------- +#' # We have an class representing a living organism. The organism has a name and +#' # will reproduce itself by cloning. We will keep track of the number of generations +#' # up to the first living organism, and the number of older siblings each living +#' # organism has, as well as the number of children. +#' +#' Organism <- R6Class("Organism", +#' public = list( +#' initialize = function(name) { +#' # This is the first organism +#' private$name <- name +#' private$generation <- 1L +#' private$num_older_siblings <- 0L +#' private$num_children <- 0L +#' }, +#' print = function() { +#' cat(private$name, +#' ": generation ", private$generation, +#' ", older siblings ", private$num_older_siblings, +#' ", n_children ", private$num_children, "\n", +#' sep = "" +#' ) +#' }, +#' clone = function(deep = FALSE, child_name) { +#' post_clone_args <- list( +#' name = child_name, +#' num_older_siblings = private$num_children +#' ) +#' # Create a clone. private$post_clone() will be called passing the post_clone_args: +#' child <- self$.clone(deep = deep, post_clone_args = post_clone_args) +#' # We can further modify the parent: +#' private$num_children <- private$num_children + 1L +#' # And finally return the child: +#' child +#' } +#' ), +#' private = list( +#' name = NA_character_, +#' generation = NA_integer_, +#' num_older_siblings = NA_integer_, +#' num_children = NA_integer_, +#' post_clone = function(name, num_older_siblings) { +#' private$name <- name +#' private$generation <- private$generation + 1L +#' private$num_older_siblings <- num_older_siblings +#' private$num_children <- 0L +#' } +#' ) +#' ) +#' +#' alex <- Organism$new(name = "Alex") +#' bart <- alex$clone(child_name = "Bart") +#' beau <- alex$clone(child_name = "Beau") +#' cleo <- bart$clone(child_name = "Cleo") +#' print(alex) +#' print(bart) +#' print(beau) +#' print(cleo) #' #' # Deep clones ----------------------------------------------------- #' @@ -478,8 +603,11 @@ R6Class <- encapsulate(function(classname = NULL, public = list(), if (any(duplicated(allnames))) stop("All items in public, private, and active must have unique names.") - if ("clone" %in% allnames) - stop("Cannot add a member with reserved name 'clone'.") + if ("clone" %in% c(names(private), names(active))) + stop("Cannot add a private or active member with reserved name 'clone'.") + + if (".clone" %in% allnames) + stop("Cannot add a member with reserved name '.clone'.") if (any(c("self", "private", "super") %in% c(names(public), names(private), names(active)))) @@ -516,8 +644,12 @@ R6Class <- encapsulate(function(classname = NULL, public = list(), generator$public_methods <- get_functions(public) generator$private_methods <- get_functions(private) - if (cloneable) - generator$public_methods$clone <- generator_funs$clone_method + if (cloneable) { + generator$public_methods$.clone <- generator_funs$.clone_method + if (!"clone" %in% names(generator$public_methods)) { + generator$public_methods$clone <- generator_funs$clone + } + } # Capture the unevaluated expression for the superclass; when evaluated in # the parent_env, it should return the superclass object. diff --git a/man/R6Class.Rd b/man/R6Class.Rd index 7921e08..05111d5 100644 --- a/man/R6Class.Rd +++ b/man/R6Class.Rd @@ -132,16 +132,86 @@ instantiate the object and assign it. To make a deep copy, you can use \code{x$clone(deep=TRUE)}. With this option, any fields that are R6 objects will also be cloned; however, - environments and reference class objects will not be. + environments and reference class objects will not be unless you customize + deep cloning. +} + +\section{Customizing cloning}{ + + + You can override the \code{clone} method to modify + the original object and/or the created one, as you need. + + The default \code{clone} method is fairly simple: + + \code{ + clone = function(deep = FALSE) \{ + self$.clone(deep = deep) + \} + } + + This internal \code{.clone} method cannot be overriden. Taking care of + all the cloning becomes very hard when there is inheritance and active + bindings, so it is recommended that you customize your \code{clone} method + using \code{self$.clone()}. + + + \code{ + clone = function(deep = FALSE) \{ + # Modify self or private from the existing object if you have to + # ... + # Create the new object: + new <- self$.clone(deep = deep) + # Private access to new$ is not directly possible here... See post_clone below + # ... + # Do not forget to return the new object + new + \} + } + + If you need private access to fields and methods from the new object, you + can define a private \code{post_clone} method. \code{self$.clone} will + call \code{post_clone} if it exists right after creating the cloned object. + \code{post_clone} runs from the new object, so its private members are + accessible. \code{post_clone} may have any arbitrary arguments. You will need + to pass values for those arguments to \code{self$.clone()} as a list using the + \code{post_clone_args} argument: + + \code{new <- self$.clone(deep = deep, post_clone_args = list(arg1 = value1, arg2 = value2))} + + \code{self$.clone} will expand \code{post_clone_args} and pass them to \code{post_clone} + that, for the example above, should have been defined as a private method + with signature: + + \code{ + post_clone = function(arg1, arg2) \{ + # ... + \} + } +} + +\section{Customizing deep cloning}{ + - If you want different deep copying behavior, you can supply your own + The default \code{clone} method (via \code{self$.clone()}) even if using + \code{deep=TRUE} will not copy environments and reference class objects by + default. + + If you just want different deep copying behavior, you can supply your own private method called \code{deep_clone}. This method will be called for - each field in the object, with two arguments: \code{name}, which is the + each field in the object, with two required arguments: \code{name}, which is the name of the field, and \code{value}, which is the value. Whatever the method returns will be used as the value for the field in the new clone object. You can write a \code{deep_clone} method that makes copies of specific fields, whether they are environments, R6 objects, or reference class objects. + + Your \code{deep_clone} method may define additional arguments besides \code{name} + and \code{value}. If that is your case, you will need to define a custom + \code{clone} method, and place the additional arguments that \code{deep_clone} + needs in a list, that you will pass to \code{self$.clone()} as + the \code{deep_clone_args} argument. \code{self$.clone()} will expand that + list and pass it to your \code{deep_clone} method. } \section{S3 details}{ @@ -382,6 +452,64 @@ b$remove() b$remove() #> [1] 20 +# Custom cloning behaviour (clone and post_clone()) --------------- +# We have an class representing a living organism. The organism has a name and +# will reproduce itself by cloning. We will keep track of the number of generations +# up to the first living organism, and the number of older siblings each living +# organism has, as well as the number of children. + +Organism <- R6Class("Organism", + public = list( + initialize = function(name) { + # This is the first organism + private$name <- name + private$generation <- 1L + private$num_older_siblings <- 0L + private$num_children <- 0L + }, + print = function() { + cat(private$name, + ": generation ", private$generation, + ", older siblings ", private$num_older_siblings, + ", n_children ", private$num_children, "\n", + sep = "" + ) + }, + clone = function(deep = FALSE, child_name) { + post_clone_args <- list( + name = child_name, + num_older_siblings = private$num_children + ) + # Create a clone. private$post_clone() will be called passing the post_clone_args: + child <- self$.clone(deep = deep, post_clone_args = post_clone_args) + # We can further modify the parent: + private$num_children <- private$num_children + 1L + # And finally return the child: + child + } + ), + private = list( + name = NA_character_, + generation = NA_integer_, + num_older_siblings = NA_integer_, + num_children = NA_integer_, + post_clone = function(name, num_older_siblings) { + private$name <- name + private$generation <- private$generation + 1L + private$num_older_siblings <- num_older_siblings + private$num_children <- 0L + } + ) +) + +alex <- Organism$new(name = "Alex") +bart <- alex$clone(child_name = "Bart") +beau <- alex$clone(child_name = "Beau") +cleo <- bart$clone(child_name = "Cleo") +print(alex) +print(bart) +print(beau) +print(cleo) # Deep clones ----------------------------------------------------- diff --git a/tests/testthat/test-aslist.R b/tests/testthat/test-aslist.R index d7d943d..3b64bca 100644 --- a/tests/testthat/test-aslist.R +++ b/tests/testthat/test-aslist.R @@ -20,7 +20,7 @@ test_that("list of public members is generated as expected by as.list.R6 method" expect_type(annList, "list") expect_equal( names(annList), - c(".__enclos_env__", "hair", "name", "clone", "set_hair", "initialize") + c(".__enclos_env__", "hair", "name", "clone", ".clone", "set_hair", "initialize") ) expect_equal(annList$hair, ann$hair) diff --git a/tests/testthat/test-clone.R b/tests/testthat/test-clone.R index 268a8f2..ed34b60 100644 --- a/tests/testthat/test-clone.R +++ b/tests/testthat/test-clone.R @@ -1,9 +1,14 @@ -test_that("Can't use reserved name 'clone'", { - expect_error(R6Class("AC", public = list(clone = function() NULL))) +test_that("Can't use reserved name 'clone' in private or active bindings", { expect_error(R6Class("AC", private = list(clone = function() NULL))) expect_error(R6Class("AC", active = list(clone = function() NULL))) }) +test_that("Can't use reserved name '.clone'", { + expect_error(R6Class("AC", public = list(.clone = function() NULL))) + expect_error(R6Class("AC", private = list(.clone = function() NULL))) + expect_error(R6Class("AC", active = list(.clone = function() NULL))) +}) + test_that("Can disable cloning", { AC <- R6Class("AC", public = list(x = 1), cloneable = FALSE) @@ -11,6 +16,161 @@ test_that("Can disable cloning", { expect_null(a$clone) }) +test_that("Can override cloning", { + AC <- R6Class("AC", + public = list( + x = 1, + clone = function(deep = FALSE) 42 + ) + ) + a <- AC$new() + expect_equal(a$clone(), 42) +}) + +test_that("Custom clone() can call .clone() and modify the original", { + AC <- R6Class("AC", + public = list( + x = 1, + clone = function(deep = FALSE) { + new <- self$.clone() + self$x <- 42 + new + } + ) + ) + a <- AC$new() + expect_equal(a$x, 1) + b <- a$clone() + expect_equal(a$x, 42) + expect_equal(b$x, 1) +}) + +test_that("post_clone() can change fields on new object", { + AC <- R6Class("AC", + public = list( + x = 1, + increment_counter = function() { + private$counter <- private$counter + 1 + }, + get_counter = function() { + private$counter + } + ), + private = list( + counter = 0, + post_clone = function() { + # reset the counter on clone() + private$counter <- 0 + } + ) + ) + + a <- AC$new() + a$increment_counter() + b <- a$clone() + # x is cloned: + expect_equal(a$x, 1) + expect_equal(b$x, 1) + # counter is reset: + expect_equal(a$get_counter(), 1) + expect_equal(b$get_counter(), 0) +}) + +test_that("post_clone() accepts custom arguments", { + AC <- R6Class("AC", + public = list( + x = 1, + increment_counter = function() { + private$counter <- private$counter + 1 + }, + get_counter = function() { + private$counter + }, + clone = function(deep = FALSE, new_counter = 0) { + self$.clone(deep = deep, post_clone_args = list(counter = new_counter)) + } + ), + private = list( + counter = 0, + post_clone = function(counter) { + # reset the counter on clone() + private$counter <- counter + } + ) + ) + + a <- AC$new() + expect_equal(a$get_counter(), 0) + a$increment_counter() + expect_equal(a$get_counter(), 1) + b <- a$clone() + # x field was cloned: + expect_equal(a$x, 1) + expect_equal(b$x, 1) + # counter was reset on the copy, but not on the original: + expect_equal(a$get_counter(), 1) + expect_equal(b$get_counter(), 0) +}) + +test_that("deep_clone accepts extra arguments", { + Child <- R6Class("Child", + public = list( + clone = function(deep = FALSE, x) { + self$.clone(post_clone_args = list(x = x)) + }, + getx = function() { + private$x + } + ), + private = list( + x = 0, + post_clone = function(x) { + private$x <- x + } + ) + ) + + ChildContainer <- R6Class("ChildContainer", + public = list( + to_be_copied = 0, + clone = function(deep = TRUE, x, child_x) { + self$.clone( + deep = deep, + post_clone_args = list(x = x), # Applies to ChildContainer$post_clone() + deep_clone_args = list(child_x = child_x) # Applies to ChildContainer$deep_clone() + ) + }, + getx = function() { + private$x + }, + getchildrenx = function() { + private$child$getx() + } + ), + private = list( + x = 0, + child = Child$new(), + post_clone = function(x) { + private$x <- x + }, + deep_clone = function(name, value, child_x) { + if (name == "child") { + return(value$clone(x = child_x)) + } + value + } + ) + ) + + child_cont <- ChildContainer$new() + child_cont$to_be_copied <- 42 + expect_equal(child_cont$getchildrenx(), 0) + child_cont2 <- child_cont$clone(x = 20, child_x = 30) + expect_equal(child_cont2$to_be_copied, 42) + expect_equal(child_cont2$getx(), 20) + expect_equal(child_cont2$getchildrenx(), 30) +}) + test_that("Cloning portable objects with public only", { parenv <- new.env() diff --git a/vignettes/Introduction.Rmd b/vignettes/Introduction.Rmd index 76328a0..283df8d 100644 --- a/vignettes/Introduction.Rmd +++ b/vignettes/Introduction.Rmd @@ -518,6 +518,76 @@ c2$v In the example `deep_clone` method above, we checked the name of each field to determine what to do with it, but we could also check the value, by using `inherits(value, "R6")`, or `is.environment()`, and so on. +#### Customizing clone() + +Occasionally we may require to customize our `$clone()` method. When cloning we +may have to make changes to the copy or the original, for instance due to managing +unique files associated to objects or updating references to other objects. + +Let's use an example: + +We have an class representing a living organism. The organism has a name and +will reproduce itself by cloning. We will keep track of the number of generations +up to the first living organism, and the number of older siblings each living +organism has, as well as the number of children. + +```{r} +Organism <- R6Class("Organism", + public = list( + initialize = function(name) { + # This is the first organism + private$name <- name + private$generation <- 1L + private$num_older_siblings <- 0L + private$num_children <- 0L + }, + print = function() { + cat(private$name, + ": generation ", private$generation, + ", older siblings ", private$num_older_siblings, + ", n_children ", private$num_children, "\n", + sep = "" + ) + }, + clone = function(deep = FALSE, child_name) { + post_clone_args <- list( + name = child_name, + num_older_siblings = private$num_children + ) + # Create a clone. private$post_clone() will be called passing the post_clone_args: + child <- self$.clone(deep = deep, post_clone_args = post_clone_args) + # We can further modify the parent: + private$num_children <- private$num_children + 1L + # And finally return the child: + child + } + ), + private = list( + name = NA_character_, + generation = NA_integer_, + num_older_siblings = NA_integer_, + num_children = NA_integer_, + post_clone = function(name, num_older_siblings) { + private$name <- name + private$generation <- private$generation + 1L + private$num_older_siblings <- num_older_siblings + private$num_children <- 0L + } + ) +) + +alex <- Organism$new(name = "Alex") +bart <- alex$clone(child_name = "Bart") +beau <- alex$clone(child_name = "Beau") +cleo <- bart$clone(child_name = "Cleo") +print(alex) +print(bart) +print(beau) +print(cleo) +``` + + + ### Printing R6 objects to the screen R6 objects have a default `$print()` method that lists all members of the object.