Closed gaborcsardi closed 10 years ago
I'm wary of multiple inheritance - I think I'd need to know the problem space better before I'd really consider adding it. Coincidentally, I saw this recently: http://arstechnica.com/information-technology/2014/08/why-is-multiple-inheritance-so-abhorred/
I agree it is a difficult problem. That's probably the reason why many languages screwed it up, and now people are wary. :) Anyway, imho that link and the original SE question list a lot of problems that do not come up it you do things "right".
Some of the problems simply don't come up, because R has dynamic typing. Others can be handled easily. E.g. somebody says that the order in which the superclasses are listed matters. Well, no, it does not matter if you do it properly. (Yes, it might for C++, Java or whatever your language is.) Also, nobody seems to be aware of the nice MI implementation in Eiffel.
This said, I think
class
attribute?So I think I'll close this now, sorry for the noise.
The need for multiple inheritance has arisen for a package I'm developing that using R6 classes for the Google Analytics Management and Google Tag Manager APIs. I'm working around the limitation of R6 not currently supporting multiple inheritance, by copying shared fields and methods. This is achieved by accessing class definitions via the elements of the class generators, e.g. my_class_gen$private_fields
and my_class_gen$private_methods
, but I would prefer to not to use this technique as it feels like a hack.
Here are links to the source code to see what this is looking like:
.goolgeApi
class..gaManagementApi
class.I've found it much easier to get where I've wanted to with R6, but now that I have a working solution I'm considering translating this to S4 so that I can attempt to redefine the solution in a way that supports multiple inheritance - this will be a challenge though. Any suggestions would be appreciated.
@jdeboer: I've ran into a similar requirement (multiple inheritance). I tried to follow your hack, but it seems that the underlying scripts have changed at least for your pointer to Ressource and collection super classes. Would you mind updating these reference? I'm really eager to find out how you made it happen.
@wch: +1 for @gaborcsardi's request. As my R prototypes need to be as close as possible to design patterns and dependency injection containers used in our production language (C#), I've come to like to use Interface (or abstract) classes to decouple my code and follow the D of the SOLID principles of OOD: simplified explanation, detailed explanation. Eventhough R/R6 does not explicitly support interfaces, I can nevertheless perfectly mimick them with R6:
As my R prototypes for web-app-like stuff need to be as close as possible to design patterns and dependency injection containers used in our production language (C#), I've come to like to use interface (or abstract) classes to decouple my code and comply with the D of the SOLID principles of OOD:
Even though R6 does not explicitly support interfaces, I can nevertheless perfectly mimick them with R6 (see example below). This helps me a lot with communicate my software designs to our OO-programmers.
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 interfaces and so I lose the benefits of inheritance for my actual concrete classes.
Alternatively, it would be great to explicitly support a differentiation between interface and concrete classes somehow. For example:
Bar <- R6Class("Bar", implements = IBar, inherit = BaseClass,
private = list(x = letters)
)
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 purpose.
So I went ahead and had a go at tweaking R6 a bit so it would allow me to distinguish between inherit
and implement
.
Probably tons of reasons why this is a bad idea ;-) But for now, it gets the job done.
Hi @rappster Here are the source file in ganalytics
where you can see what I did to overcome the limitation of single inheritance in R6. I'm not sure its the solution you're looking for though: https://github.com/jdeboer/ganalytics/blob/dev/R/ga-api-classes.R
https://github.com/jdeboer/ganalytics/blob/dev/R/management-api-classes.R
I've actually started working on a new approach, which is currently on hold and not yet functioning at the moment, where I intend on using R6 and S4 in combination: https://github.com/jdeboer/ganalytics/blob/dev/R/WIP/management-api-classes-V3.R
Wish I had more time to work on this at the moment, but I've enrolled myself in full time study while also working, so a bit stretched. My apologies for taking a while to get back to you.
Maybe my use case is no multiple inheritance at all, maybe it is to short sighted ... but let's see.
Some background info:
Playing around with R6 I developed a class binding together text and data (rtext). On my way I split up parts of the class because they are separate blocks of functionality and reading code and maintainig code should be better this way.
So what I end up is somthing like a class dependecy like that
| - rtext_extension_a |
R6 - R6_extension - rtext_base | - rtext_extension_2 | - rtext
| - rtext_extension_foo |
Which I have to write like this ...
R6_extension <- R6::R6Class( classname = "R6_extension", ... )
rtext_base <- R6::R6Class( classname = "rtext_base", inherit = R6_extension, ... )
rtext_extension_1 <- R6::R6Class( classname = "rtext_extension_1", inherit = rtext_base, ... )
rtext_extension_2 <- R6::R6Class( classname = "rtext_extension_2", inherit = rtext_extension_1, ... )
rtext_extension_3 <- R6::R6Class( classname = "rtext_extension_3", inherit = rtext_extension_2, ... )
rtext <- R6::R6Class( classname = "rtext", inherit = rtext_extension_3, ... )
... which is (possible but) tedious whenever I extend the functionality by another 'module' (sub-class).
Now would it not be nice to have something like that:
rtext <-
R6::R6Class(
classname = "rtext",
inherit =
list(
R6_extension,
rtext_base,
rtext_extension_1,
rtext_extension_2,
rtext_extension_3
),
...
)
... which works like just a series of inherits?
Why is this issue closed since 3 years ? It seems like multiple inheritences are still not availiable in R6.
Will you reconsider implementing the possibility of multiple inheritance ? Did you decide not to ever do it ?
If so, why ? Thx for your responce.
So how do you propose the diamond problem is handled?
Well there is a lot of ways to handle a diamond issue in a multiple inheritance framework. My english is a little bad so i'm not gonna sketch them, i refer you to some exemples there : https://en.wikipedia.org/wiki/Multiple_inheritance#Mitigation
My thoughts : there are 2 easy way and a harder one of handling the issue : Quick and dirty : Just default the inheritance to the first declared parent. Quick and not-so-dirty : Refuse to inherit methods if a diamond issue is founded. -- the user would have to re-define the problematic method in the child, making him aware of the problem. Hard and Neat : Copy the Eiffel way of doing it. Eiffel explicitely asks the user wich methods and attributes should be inherited from wich parent when the child is declared, and therefore avoid any diamond problem.
I'll sketch a little more the second solution :
Supose the Parent class has 2 child, Son and Daughter wich both inerits from Parent. Supose a IncestuousChild class inherit from Son and Daughter.
The methods of each are as follows : Parent has two atribute name and familyname and a method great Son and Daughter both inherits name, both re-implement great but only Daughter re-implements familyname. IncestuousChild will them inherit name normaly because neither Son or Daughter re-implemented it. IncestuousChild WONT inherit great because there are 2 possibilities : either inehrit Son's or Daughter's. IncestuousChild WONT inherit familyname because there are also 2 possibilities : inherit directly from Son or from Parent through Daughter.
For the last 2, user will have to redefine the methods.
I'm not enough aware of the implementation choices in R6 to be able to choose the best option, since there are a lot of them -- sorry -- , but there is definitely a solution to the problem.
Glad to explain more if needed :)
Yep that makes sense. What about the initializers, would they have to have the same (or closely-related) signatures? super would need to return a list, rather than a single object.
Well taking super as a list could make a lot of sence if we choose the ordered parrent way of dispatching methods.
But the goal is to keep things as they are and not to break everyone's code by changing the definition of super. Keep in mind that the system will have to stay the same if no multiple inheritance are defined. keep also in mind that if no diamond problems are found, everything should behave the same.
Wouldn't have to be necessarily ordered, a named list would be handy, where the names are the classes respectively, something like this perhaps for example:
A = R6::A('A')
B1 = R6::R6Class('B1',inherit=A)
B2 = R6::R6Class('B2',inherit=A)
C = R6::R6Class('C',inherit=c(B1,B2),
public = list(
initialize = function(){
super$B1$initialize()
super$B2$initialize()
## So forth
}
)
)
You'l loose the
super$method()
syntax, and have to replace it by
super$NameOfParent$method()
?
I'll prefere tweaking the super object such that :
super$methodFromDad()
super$methodFromMom()
still works.
Yeah you would probably lose first syntax. I am still trying to decide whether that is a good or a bad thing.
Just an idea for a cool feature. Probably not needed much, but sometimes handy, especially if it is possible to implement it in a simple and useful way. And not too many languages have done this properly, so R6 would be a standout. :)
A possible use-case is a class that implements a feature that can be useful in many other classes as well. E.g. you could have a
pretty_printer
class that has some fancyprint
method. This is potentially useful for any class. So in a project, your classfoo
just inherits frompretty_printer
, in addition to its normal inheritance hierarchy, and thenfoo
has a niceprint
method. There can be many similar features if course.There could be of course several other examples. E.g. think about a class hierarchy of visualization types. You can have
dynamic_plot
s, and alsoline_plot
s, but of course it should be possible to inherit from both of these to create adynamic_line_plot
. An alternative would be to have adynamic_plot
and aline_plot
as members, but that is less expressive.I personally like the implementation of multiple inheritance in the Eiffel language:
undefine
the one(s) you don't want.rename
an inherited member. This is useful if you inherit methods with identical names from two (or more) superclasses, and want to use both of them. This seems challenging to implement in R6.