Skip to content

Should R7 offer encapsulated OO? #202

@hadley

Description

@hadley

It turns out to be trivial (#191) to add encapsulated methods to R7 (i.e. methods that belong to the class/object):

foo <- new_class("foo",
  properties = list(
    x = "numeric",
    y = "numeric"
  ),
  methods = list(
    bar = function(self) {
      print("Hi!")
    },
    baz = function(self) {
      self@bar()
      self@x + self@y
    },
    change = function(self) {
      self@x <- self@x + 1
      self
    }
  ),
)

x <- foo(x = 1, y = 2)
x@baz()

The current trivial implementation:

  • Handles within package inheritance of methods. Cross-package inheritance needs a little more work to ensure that we don't take a dependency at build-time, rather than load-time (so that environments of the methods match the currently installed package). Would need to adjust super() implementation to support method access.

  • Doesn't have particularly good performance. Prformance will improve a little with a base C implementation of @, but would require byte code compiler transformation for high speed.

Currently mutability "just works" if you inherit from environment:

foo2 <- new_class("foo", "environment",
  properties = list(
    x = "numeric"
  ),
  methods = list(
    up = function(self) {
      self@x <- self@x + 1
      invisible(self)
    }
  )
)

x <- foo2()
x@x <- 1
x@up()
x@x

But a full implementation would need careful consideration of how validate() should work to ensure that objects aren't left in an invalid state.

Overall, I'm pretty neutral on this proposal. There are some big upsides, but also some big downsides. Here are a few of the pros/cons that I've considered so far:

  • It's often handy to encapsulate code that only operates on objects of a certain type in to the class, rather than leaving as a free floating function (which typically needs some prefix to avoid clashes with other function names).
  • From the outside, encapsulated methods behave similarly to dynamic properties, which we already allow.
  • Encapsulated OO allows R7 to also offer an equivalent to R6 (although the performance won't be as good). For better or worse, folks coming from other languages usually find this style of OO more familiar.
  • Most modern language use special syntax for in-place mutation. In the code above there's no way to know that x@up() is actually modifying x in place: this is potentially extremely confusing.
  • Would need to think about encapsulation of methods. Maybe a convention that internal methods start with . would be sufficient? Since they wouldn't be documented and wouldn't be suggested by auto-complete, it would be unlikely for uses to accidentally use them.
  • It's confusing to provide two different styles of OO in one system. How would developers know which to use when?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions