Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple inheritance #9

Closed
gaborcsardi opened this issue Aug 4, 2014 · 15 comments
Closed

Multiple inheritance #9

gaborcsardi opened this issue Aug 4, 2014 · 15 comments

Comments

@gaborcsardi
Copy link
Member

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 fancy print method. This is potentially useful for any class. So in a project, your class foo just inherits from pretty_printer, in addition to its normal inheritance hierarchy, and then foo has a nice print 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_plots, and also line_plots, but of course it should be possible to inherit from both of these to create a dynamic_line_plot. An alternative would be to have a dynamic_plot and a line_plot as members, but that is less expressive.

I personally like the implementation of multiple inheritance in the Eiffel language:

  • Members inherited twice, but having the same type are not duplicated. R of course has dynamic typing, so members with the same name would never be duplicated.
  • Methods inherited twice, but having the same code, are not duplicated.
  • If a member is inherited from two (or more) superclasses, then you can undefine the one(s) you don't want.
  • You can 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.
@wch
Copy link
Member

wch commented Aug 4, 2014

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/

@gaborcsardi
Copy link
Member Author

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

  1. It is probably not the thing to implement in R6, surely not now.
  2. It might not fit into R and/or at all. E.g. in which order would you list superclasses in the class attribute?

So I think I'll close this now, sorry for the noise.

@jdeboer
Copy link

jdeboer commented Apr 3, 2015

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:

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.

@jankowtf
Copy link

@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:

Context

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.

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

@jankowtf
Copy link

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 inheritand implement.

Probably tons of reasons why this is a bad idea ;-) But for now, it gets the job done.

@jdeboer
Copy link

jdeboer commented Feb 19, 2016

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.

@petermeissner
Copy link

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

  • ther is a general extension of the R6 class,
  • than a class that does implement the core data structure and core methods,
  • than some modular, optional and independent additions
  • and in the end one class that is faced by the user
                               | - 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?

@lrnv
Copy link

lrnv commented Aug 7, 2017

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.

@nhamilton1980
Copy link

nhamilton1980 commented Oct 15, 2017

So how do you propose the diamond problem is handled?

@lrnv
Copy link

lrnv commented Oct 15, 2017

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

@nhamilton1980
Copy link

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.

@lrnv
Copy link

lrnv commented Oct 15, 2017

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.

@nhamilton1980
Copy link

nhamilton1980 commented Oct 15, 2017

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

@lrnv
Copy link

lrnv commented Oct 15, 2017

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.

@nhamilton1980
Copy link

Yeah you would probably lose first syntax. I am still trying to decide whether that is a good or a bad thing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants