r-lib / R6

Encapsulated object-oriented programming for R
https://R6.r-lib.org
Other
409 stars 56 forks source link

Fix cloning inheritance #267

Closed IndrajeetPatil closed 1 year ago

IndrajeetPatil commented 1 year ago

Closes #247 Closes #165

Example

library(R6)

Creature <- R6Class("Creature", cloneable = TRUE)

Sheep <- R6Class("Sheep", inherit = Creature, cloneable = TRUE)
sheep <- Sheep$new()
class(sheep$clone())
#> [1] "Sheep"    "Creature" "R6"

Human <- R6Class("Human", inherit = Creature, cloneable = FALSE)
human <- Human$new()
human$clone()
#> Error in eval(expr, envir, enclos): attempt to apply non-function

Creature <- R6Class("Creature", cloneable = FALSE)

Sheep <- R6Class("Sheep", inherit = Creature, cloneable = TRUE)
sheep <- Sheep$new()
#> Subclass wants to allow cloning, but superclass has turned it off. Therefore, cloning will also be turned off for subclass.
class(sheep$clone())
#> Error in eval(expr, envir, enclos): attempt to apply non-function

Human <- R6Class("Human", inherit = Creature, cloneable = FALSE)
human <- Human$new()
human$clone()
#> Error in eval(expr, envir, enclos): attempt to apply non-function

Created on 2022-10-27 with reprex v2.0.2

wch commented 1 year ago

If I understand correctly, this will change the default behavior so that if someone creates a subclass and does not explicitly specify a value for cloneable, then:

Am I understanding this correctly?

IndrajeetPatil commented 1 year ago

That's correct.

If cloneable is not specified for superclass, then the default behaviour remains the same.

library(R6)

Creature <- R6Class("Creature")
Sheep <- R6Class("Sheep", inherit = Creature)
Sheep$new()
#> <Sheep>
#>   Inherits from: <Creature>
#>   Public:
#>     clone: function (deep = FALSE)

But, if it is set to FALSE, then the default behaviour changes with this PR. Now, the subclass would override superclass setting because there is a mismatch.

Creature <- R6Class("Creature", cloneable = FALSE)
Sheep <- R6Class("Sheep", inherit = Creature)
Sheep$new()
#> Sub and superclass have different cloneable properties. Subclass property will override superclass property.
#> <Sheep>
#>   Inherits from: <Creature>
#>   Public:
#>     clone: function (deep = FALSE)

Created on 2022-10-26 with reprex v2.0.2

I'd run a revdepcheck to see how many deps are affected by this, but {R6} has many deps, and I don't think my desktop can handle it.

I am open to suggestions on how to let the subclass override subclass cloning property when there is a mismatch, but in a backward-compatible manner.

wch commented 1 year ago

Some thoughts:

IndrajeetPatil commented 1 year ago

@wch I've addressed the first two points.

library(R6)

# superclass non-cloneable  ---------------------

Creature <- R6Class("Creature", cloneable = FALSE)

Sheep <- R6Class("Sheep", inherit = Creature, cloneable = TRUE)
sheep <- Sheep$new()
#> Subclass wants to allow cloning, but superclass has turned it off. Therefore, cloning will also be turned off for subclass.
class(sheep$clone())
#> Error in eval(expr, envir, enclos): attempt to apply non-function

Human <- R6Class("Human", inherit = Creature, cloneable = FALSE)
human <- Human$new()
human$clone()
#> Error in eval(expr, envir, enclos): attempt to apply non-function

# superclass cloneable  ---------------------

Creature <- R6Class("Creature", cloneable = TRUE)

Sheep <- R6Class("Sheep", inherit = Creature, cloneable = TRUE)
sheep <- Sheep$new()
class(sheep$clone())
#> [1] "Sheep"    "Creature" "R6"

Human <- R6Class("Human", inherit = Creature, cloneable = FALSE)
human <- Human$new()
human$clone()
#> Error in eval(expr, envir, enclos): attempt to apply non-function

Created on 2022-10-27 with reprex v2.0.2

Addressing the third point in this PR feels like expanding its scope. I am also hesitant to work on it because I am not sure what the expected API change looks like (should default be NULL, FALSE, NA, etc.?), or what impact it might have.

wch commented 1 year ago

I think that the way you addressed the first two points actually solves the third point.