Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple inheritance for R6 classes

Actual question

What are my options to workaround the fact that R6 does not support multiple inheritance?

Disclaimer

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

  1. know you're prototyping for an object-oriented language such as C#, Java, etc.

  2. your prototypes of apps need to be self-sufficient ("full stack" including DB-backends, business logic and frontends/UI)

  3. you have such great "prototyping technology" like R6 and shiny at your disposal

Context

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.

Example

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)
)
like image 546
Rappster Avatar asked Feb 15 '16 16:02

Rappster


People also ask

Does multiple inheritance apply to classes?

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.

How do you inherit multiple classes?

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.

How many classes are there in multiple inheritance?

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.

Do C# classes support multiple inheritance?

Multiple Inheritance isn't supported in C#. To implement multiple inheritances, use Interfaces.


2 Answers

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.

Example

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

Proof of concept for dependency injection

This is a little draft that elaborates a bit on the motivation and possible implementation approaches for interfaces and inversion of dependency in R6.

like image 151
Rappster Avatar answered Oct 13 '22 21:10

Rappster


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).

like image 37
Konrad Rudolph Avatar answered Oct 13 '22 21:10

Konrad Rudolph