R6 icon indicating copy to clipboard operation
R6 copied to clipboard

Cannot override field with method in subclass

Open hriebl opened this issue 5 years ago • 8 comments

I just figured out that it's not possible to override a field with a method in a subclass. Paradoxically, the opposite direction works. Maybe this is the desired behavior or I overlooked this aspect in the package documentation/the other GitHub issues. If so, please let me know.

field → field works:

parent <- R6Class("parent", list(
  x = 1
))

child <- R6Class("child", inherit = parent, list(
  x = 2
))

parent$new()
# <parent>
#   Public:
#     clone: function (deep = FALSE)
#     x: 1
child$new()
# <child>
#   Inherits from: <parent>
#   Public:
#     clone: function (deep = FALSE)
#     x: 2

field → method doesn't work:

parent <- R6Class("parent", list(
  x = 1
))

child <- R6Class("child", inherit = parent, list(
  x = function() 2
))

parent$new()
# <parent>
#   Public:
#     clone: function (deep = FALSE)
#     x: 1
child$new()
# <child>
#   Inherits from: <parent>
#   Public:
#     clone: function (deep = FALSE)
#     x: 1

method → method works:

parent <- R6Class("parent", list(
  x = function() 1
))

child <- R6Class("child", inherit = parent, list(
  x = function() 2
))

parent$new()
# <parent>
#   Public:
#     clone: function (deep = FALSE)
#     x: function ()
child$new()
# <child>
#   Inherits from: <parent>
#   Public:
#     clone: function (deep = FALSE)
#     x: function ()

parent$new()$x()
# [1] 1
child$new()$x()
# [1] 2

method → field works:

parent <- R6Class("parent", list(
  x = function() 1
))

child <- R6Class("child", inherit = parent, list(
  x = 2
))

parent$new()
# <parent>
#   Public:
#     clone: function (deep = FALSE)
#     x: function ()
child$new()
# <child>
#   Inherits from: <parent>
#   Public:
#     clone: function (deep = FALSE)
#     x: 2

parent$new()$x()
# [1] 1

hriebl avatar Nov 05 '18 11:11 hriebl

This is a bug. I think that it makes sense to disallow overriding in either direction, from method->field or field->method, because then the subclass will behave differently from the superclass.

wch avatar Nov 05 '18 16:11 wch

Actually, I was a bit surprised it didn't work, because there is usually no type checking in R. I was trying to write a "template class" with a lot of NA fields, which the subclasses would override with useful methods. But probably you're right and it would be more sane to prohibit this behavior altogether.

hriebl avatar Nov 06 '18 20:11 hriebl

I was wondering about the use case you had in mind. We've been thinking about interfaces for R6, but haven't yet decided on a course of action (#163).

wch avatar Nov 06 '18 20:11 wch

Interfaces seem like the proper way to implement what I was trying to achieve. Anyway, I think it would be a good idea to make the "field → method", "method → field" behavior consistent. :)

hriebl avatar Nov 06 '18 22:11 hriebl

I was trying to write a "template class" with a lot of NA fields, which the subclasses would override with useful methods. But probably you're right and it would be more sane to prohibit this behavior altogether.

@hriebl A different idea that works today is to define methods that don't do anything in your template class, or to be a little fancier, define methods that error out if called.

jayqi avatar Nov 11 '18 18:11 jayqi

Thank you, @jayqi, I ended up doing something like that. The whole bug is not a big deal, you can definitely work around it, but it's a bit weird...

hriebl avatar Nov 19 '18 09:11 hriebl

Is this related to the inability to access public fields via super$?

library(R6)

MySuper <- R6Class(
  "MySuper",
  public = list(
    public_member = NULL,
    initialize = function(...) {
      self$public_member <- 1
    },
    get_super_public_member = function() {
      self$public_member
    }
  )
)

MySub <- R6Class(
  "MySub",
  inherit = MySuper,
  public = list(
    get_public_member = function() {
      list(
        self = self$public_member,
        super = super$public_member
      )
    }
  )
)

my_sub <- MySub$new()
my_sub$get_super_public_member()
#> [1] 1
my_sub$get_public_member()
#> $self
#> [1] 1
#> 
#> $super
#> NULL

Created on 2020-12-11 by the reprex package (v0.3.0)

krlmlr avatar Dec 11 '20 17:12 krlmlr

I also just stumbled over this issue. I initially had defined the method as a field = NULL in the super class and defined the according method in the subclass. Would be great if this behavior could be made consistent or described in the documentation.

MSHelm avatar Sep 28 '21 14:09 MSHelm