What are my options to workaround the fact that R6 does not support multiple inheritance?
I know that R is primarily a functional language. However, it does also have very powerful object-orientation built in. Plus: I don't see what's wrong with mimicking OOD principles/behavior when you
know you're prototyping for an object-oriented language such as C#, Java, etc.
your prototypes of apps need to be self-sufficient ("full stack" including DB-backends, business logic and frontends/UI)
you have such great "prototyping technology" like R6 and shiny at your disposal
My R prototypes for web apps need to be both "full stack"/self sufficient and as close as possible to design patterns/principles and dependency injection containers (proof of concept of simple DI in R) used in our production language (C#/.NET).
In that regard, I came to like the use of interfaces (or abstract classes) very much in order to decouple code modules and to comply with the D (dependency inversion principle) of the SOLID principles of OOD (detailed explanation by "Uncle Bob").
Even though R6 does not explicitly support interfaces, I can nevertheless perfectly mimick them with R6 classes that define nothing but "abstract methods" (see example below). This helps me a lot with communicating my software designs to our OO-programmers that aren't very familiar with R. I strive for as little "conceptional conversion effort" on their part.
However, I need to give up my value for inherit
in R6Class
for that which becomes a bit of a problem when I actually want to inherit from other concrete (as opposed to "abstract-like" mimicked interface classes) because this would mean to define not one but two classes in inherit
.
Before inversion of dependency:
Foo
depends on concrete class Bar
. From an OOD principles' view, this is pretty bad as it leads to code being tightly coupled.
Bar <- R6Class("Bar",
public = list(doSomething = function(n) private$x[1:n]),
private = list(x = letters)
)
Foo <- R6Class("Foo",
public = list(bar = Bar$new())
)
inst <- Foo$new()
> class(inst)
> class(inst$bar)
[1] "Bar" "R6"
After inversion of dependency:
Foo
and Bar
are decoupled now. Both depend on an interface which is mimicked by class IBar
. I can decide which implementation of that interface I would like to plug in to instances of Foo
at runtime (realized via Property Injection: field bar
of Foo
)
IBar <- R6Class("IBar",
public = list(doSomething = function(n = 1) stop("I'm the inferace method"))
)
Bar <- R6Class("Bar", inherit = IBar,
public = list(doSomething = function(n = 1) private$x[1:n]),
private = list(x = letters)
)
Baz <- R6Class("Baz", inherit = IBar,
public = list(doSomething = function(n = 1) private$x[1:n]),
private = list(x = 1:24)
)
Foo <- R6Class("Foo",
public = list(bar = IBar$new())
)
inst <- Foo$new()
inst$bar <- Bar$new()
> class(inst$bar)
[1] "Bar" "IBar" "R6"
> inst$bar$doSomething(5)
[1] "a" "b" "c" "d" "e"
inst$bar <- Baz$new()
[1] "Baz" "IBar" "R6"
> inst$bar$doSomething(5)
[1] 1 2 3 4 5
A bit mor on why this makes sense with regard to OOD: Foo
should be completely agnostic of the the way the object stored in field bar
is implemented. All it needs to know is which methods it can call on that object. And in order to know that, it's enough to know the interface that the object in field bar
implements (IBar
with method doSomething()
, in our case).
Using inheritance from base classes to simplify design:
So far, so good. However, I'd also like to simplify my design by definining certain concrete base classes that some of my other concrete classes can inherit from.
BaseClass <- R6Class("BaseClass",
public = list(doSomething = function(n = 1) private$x[1:n])
)
Bar <- R6Class("Bar", inherit = BaseClass,
private = list(x = letters)
)
Baz <- R6Class("Bar", inherit = BaseClass,
private = list(x = 1:24)
)
inst <- Foo$new()
inst$bar <- Bar$new()
> class(inst$bar)
[1] "Bar" "BaseClass" "R6"
> inst$bar$doSomething(5)
[1] "a" "b" "c" "d" "e"
inst$bar <- Baz$new()
> class(inst$bar)
[1] "Baz" "BaseClass" "R6"
> inst$bar$doSomething(5)
[1] 1 2 3 4 5
Combining "interface implementation" and base clases inheritance:
This is where I would need multiple inheritance so something like this would work (PSEUDO CODE):
IBar <- R6Class("IBar",
public = list(doSomething = function() stop("I'm the inferace method"))
)
BaseClass <- R6Class("BaseClass",
public = list(doSomething = function(n = 1) private$x[1:n])
)
Bar <- R6Class("Bar", inherit = c(IBar, BaseClass),
private = list(x = letters)
)
inst <- Foo$new()
inst$bar <- Bar$new()
class(inst$bar)
[1] "Bar" "BaseClass" "IBar" "R6"
Currently, my value for inherit
is already being used up "just" for mimicking an interface implementation and so I lose the "actual" benefits of inheritance for my actual concrete classes.
Alternative thought:
Alternatively, it would be great to explicitly support a differentiation between interface and concrete classes somehow. For example something like this
Bar <- R6Class("Bar", implement = IBar, inherit = BaseClass,
private = list(x = letters)
)
Multiple Inheritance is a feature of an object-oriented concept, where a class can inherit properties of more than one parent class. The problem occurs when there exist methods with the same signature in both the superclasses and subclass.
Multiple Inheritance is a feature of C++ where a class can inherit from more than one classes. The constructors of inherited classes are called in the same order in which they are inherited. For example, in the following program, B's constructor is called before A's constructor.
Diagram of the Multiple Inheritance In the above diagram, there are two-parent classes: Base Class 1 and Base Class 2, whereas there is only one Child Class. The Child Class acquires all features from both Base class 1 and Base class 2. Therefore, we termed the type of Inheritance as Multiple Inheritance.
Multiple Inheritance isn't supported in C#. To implement multiple inheritances, use Interfaces.
For those interested:
I gave it a second thought and realized that's it's not really multiple inheritance per se that I want/need, but rather some sort of better mimicking the use of interfaces/abstract classes without giving up inherit
for that.
So I tried tweaking R6
a bit so it would allow me to distinguish between inherit
and implement
in a call to R6Class
.
Probably tons of reasons why this is a bad idea, but for now, it gets the job done ;-)
You can install the tweaked version from my forked branch.
devtools::install_github("rappster/R6", ref = "feat_interface")
library(R6)
Correct implementation of interface and "standard inheritance":
IFoo <- R6Class("IFoo",
public = list(foo = function() stop("I'm the inferace method"))
)
BaseClass <- R6Class("BaseClass",
public = list(foo = function(n = 1) private$x[1:n])
)
Foo <- R6Class("Foo", implement = IFoo, inherit = BaseClass,
private = list(x = letters)
)
> Foo$new()
<Foo>
Implements interface: <IFoo>
Inherits from: <BaseClass>
Public:
clone: function (deep = FALSE)
foo: function (n = 1)
Private:
x: a b c d e f g h i j k l m n o p q r s t u v w x y z
When an interface is not implemented correctly (i.e. method not implemented):
Bar <- R6Class("Bar", implement = IFoo,
private = list(x = letters)
)
> Bar$new()
Error in Bar$new() :
Non-implemented interface method: foo
This is a little draft that elaborates a bit on the motivation and possible implementation approaches for interfaces and inversion of dependency in R6.
Plus: I don't see what's wrong with mimicking OOD principles/behavior when you know you're prototyping for an object-oriented language such as C#, Java, etc.
What’s wrong with it is that you needed to ask this question because R is simply an inadequate tool to prototype an OOD system, because it doesn’t support what you need.
Or just prototype those aspects of your solution which rely on data analysis, and don’t prototype those aspects of the API which don’t fit into the paradigm.
That said, the strength of R is that you can write your own object system; after all, that’s what R6 is. R6 just so happens to be inadequate for your purposes, but nothing stops you from implementing your own system. In particular, S3 already allows multiple inheritance, it just doesn’t support codified interfaces (instead, they happen ad-hoc).
But nothing stops you from providing a wrapper function that performs this codification. For instance, you could implement a set of functions interface
and class
(beware name clashes though) that can be used as follows:
interface(Printable,
print = prototype(x, ...))
interface(Comparable,
compare_to = prototype(x, y))
class(Foo,
implements = c(Printable, Comparable),
private = list(x = 1),
print = function (x, ...) base::print(x$x, ...),
compare_to = function (x, y) sign(x$x - y$x))
This would then generate (for instance):
print.Foo = function (x, ...) base::print(x$x, ...)
compare_to = function (x, y) UseMethod('compare_to')
compare_to.foo = function (x, y) sign(x$x - y$x)
Foo = function ()
structure(list(x = 1), class = c('Foo', 'Printable', 'Comparable'))
… and so on. In fact, S4 does something similar (but badly, in my opinion).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With