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? Mixins? #452

Closed
TrevorBurnham opened this issue Jun 22, 2010 · 36 comments
Closed

Multiple inheritance? Mixins? #452

TrevorBurnham opened this issue Jun 22, 2010 · 36 comments

Comments

@TrevorBurnham
Copy link
Collaborator

I'm loving the extends keyword, but in the course of developing a complex project, I really wish I could give a class functionality from multiple sources. Obviously I could do this by extending the prototype manually, but it feels like a hack to mix the extends syntax with JavaScript-style prototype manipulation.

For my purposes, it would be great to be able to say

class A extends Foo, Bar

but I realize that multiple inheritance is a huge headache, so Ruby-style mixins or Scala-style traits would probably be a better solution. The Scala syntax would be especially nice for CoffeeScript:

class A extends Foo with Bar

Obviously with is already a keyword, but its meaning in this syntax is unambiguous.

Of course, this is something that a standard library could do, but it would be much clearer to have something like the above syntax rather than something like

class A extends foo
   # properties...

lib.mixin A, bar

What does everyone else think?

@ghost
Copy link

ghost commented Jun 22, 2010

Yes, mixins - I'm missing them too.

@jashkenas
Copy link
Owner

So. JavaScript inheritance of properties through prototypes is only for single inheritance. You can have chains pointing back as long as you like, but there's only one prototype per-object.

Any "mixin" functionality would be limited to what can be accomplished at runtime through helper functions, as mentioned above. It would be a blind copy of properties onto the object or prototype, not a true inclusion, where subsequent changes to the module would affect the mixed-in objects. For this reason, I don't think that it's in the domain of CoffeeScript. Is there a concrete proposal for what you'd like it to compile into?

@TrevorBurnham
Copy link
Collaborator Author

I would like

class A extends Foo with Bar, Baz

to copy the properties from the Bar and Baz prototypes over to A. Naturally, I wouldn't be able to use super if I overrode a Bar or Baz function, because that could be ambiguous. It would still be quite useful, though, for adding certain kinds of functionality to multiple classes. It's syntactic sugar, plus folks coming from Ruby probably expect some kind of mixin functionality in the language.

@tcr
Copy link

tcr commented Jun 23, 2010

class A extends Foo merge Bar, Baz

?

@jashkenas
Copy link
Owner

Closing this one as a wontfix, here's why:

The idea with CoffeeScript classes is to expose the common pattern of use of JavaScript's prototype chains in a convenient way -- not to mimic classes from Ruby or another language. If you'd like to include methods from an external module into a class you have a number of options: you can delegate to the class, manually copy methods over, or use a helper function like Prototype's extend.

It's an orthogonal issue to classes ... you should equally well be able to "mixin" functions into a vanilla object, which is something that an extend function does.

@StanAngeloff
Copy link
Contributor

Mootools solves this using an Implements property. A Class in Mootools can extend only one other, but can implement as many as required. A common example is:

Button: new Class {
  Extends: Control,
  Implements: [Options, Events]
}
cancel: new Button()
cancel.addEvent 'click', ( -> false)

This is very powerful and I use it quite a lot in my code. Options and Events are so common that every UI class must have them.

I personally think Coffee should have a second think about allowing this kind of construct.

class Button extends Control implements Options, Events

From the docs:

Extends

The Class that this class will extend. i.e. proper inheritance

Implements

Implements is similar to Extends, except that it adopts properties from one or more other classes without inheritance. Useful when implementing a default set of properties in multiple Classes.

@jashkenas
Copy link
Owner

We can reopen it if someone cooks up a patch they'd like to propose.

@hen-x
Copy link

hen-x commented Jun 24, 2010

If we had inline anonymous classes, it would be possible to implement mixins in user code while keeping them at the top of the class definition:

implementing: (mixins..., classReference) ->
    for mixin in mixins
        for key, value of mixin::
            (classReference::)[key]: value
    classReference

Button: implementing Options, Events, class 
    doSomething: ->

cancel: new Button()
cancel.addEvent 'click', ( -> false)

...although a class defined this way could not have a subclass containing super calls, since it would not be bound to a name at compile-time.

@koops
Copy link

koops commented Aug 3, 2010

+1 for some kind of mixin functionality. It seems awkward and un-coffee to create a class and then copy functions to the prototype after the definition. That functionality feels like it belongs in the class definition.

@dfreire
Copy link

dfreire commented Aug 6, 2010

+1

@Goutte
Copy link

Goutte commented Feb 25, 2011

class Options
  options: {}
  setOptions: (o) ->
    for own key, value of o
      @options[key] = value
    this

class Button extends Control implements Options, Events

+1

@omo
Copy link

omo commented Feb 26, 2011

+1

@jashkenas
Copy link
Owner

Sorry, folks, but +1s aren't terribly helpful without a specific implementation strategy you'd like to propose. Multiple inheritance simply does not exist in JavaScript -- you have a single __proto__ property, which points at a single parent object.

@omo
Copy link

omo commented Feb 26, 2011

@jashkenas Thanks you for you response and I'm sorry for my noisy comment...
Simple function copying like Backbone.Events is a good starting point for me.
For name conflicts, I prefer just throwing error to avoid accidental override.

@ricardobeat
Copy link
Contributor

You can have a longer prototype chain, or am I doing something stupid?

x = function(){};
x.prototype = { x: 1 };
y = function(){};
y.prototype = new x();
y.prototype.y = 2;
z = function(){ this.z = 3; };
z.prototype = new y();
z.prototype.z = 3;
a = function(){};
a.prototype = new z();
a.prototype.a = 4;

console.log(new a)

x.prototype.b = 5;

console.log(new a);

@hen-x
Copy link

hen-x commented Apr 6, 2011

@ricardobeat: Yes, an object can have a long prototype chain -- but each object in the chain will inherit from another object in the chain. If you instead have 2 existing objects, a and b, which don't inherit from each other, you cannot make object c inherit from both of them.

@captainpete
Copy link

As a workaround, you can copy functions into a class on initialize with jQuery to get snapshot mixins using something like the following:

initialize: ->
  super
  jQuery.extend this, YourHelper.prototype

Not without limitation, but lets you "choose composition over inheritance".

UPDATE: Read the mixin section on http://arcturo.github.com/library/coffeescript/03_classes.html, previous link was broken, thanks @countable :)

@countable
Copy link

hm, that link on Mixins in the last post is dead now, and this issue is closed but it comes up first in Google for "coffeescript mixins", so incase anyone else ends up here, I'll note the Spine framework has support which you can easily steal as an alternative to the @captainpete workaround.

class Module
  @include: (obj) ->
    throw('include(obj) requires obj') unless obj
    for key, value of obj when key not in moduleKeywords
      @::[key] = value

    included = obj.included
    included.apply(this) if included
    @

and then to use it

helper = 
    my_helper_fn: -> 'i'm helping'

class MyClass extends Module
     @include helper

@ghost
Copy link

ghost commented Jan 28, 2012

Function::use = (argv...) ->
  for cl in argv
    for key, value of cl::
      @::[key]=value
  @

class A
  a: -> 
class B 
  b: -> 
class C
  @use A,B
  c: ->

i = new C
console.log i

try

@Hubro
Copy link

Hubro commented Jan 30, 2012

I agree that one shouldn't mess with the inheritance chain, but I do think that coffee-script should provide the functionality that Spine does, namely including other classes. Let extends do what it does, but also let me @include EventEmitter :-)

I hope you'll consider this. Thanks

@tengla
Copy link

tengla commented Sep 29, 2012

I totally agree with @jashkenas . Coffeescript should not, and not ever, mimic the (useful?) constructs of other languages. Some seem to confuse the intention of Coffescript with libraries such as Prototype, MooTools, jQuery, Spine et.al. Go write a library, or something.

@vendethiel
Copy link
Collaborator

satyr/coco#144

Also, this is 2 years old.

@tengla
Copy link

tengla commented Sep 29, 2012

Yes @Nami-Doc, but the issue still holds relevance, don't you think? I stumbled upon this thread today, investigating multiple inheritance in coffescript and javascript. It seems to me the only way to achieve multiple inheritance in Javascript is to be rather explicit ...

@vendethiel
Copy link
Collaborator

Yes, that's why I linked coco's solution.

@tengla
Copy link

tengla commented Sep 29, 2012

ty @Nami-Doc .

@octavioturra
Copy link

For me, it's not about other language constructs, but the Object Oriented paradigm, reusability and a way to code. Yes, we can live without it, but, something like Interfaces, that just have methods, or just signatures could be useful.

Why not just put methods of "interface C" into a "class B" that extends "class A" and, if it isn't implemented, throws an exception?

 for(var m in implemented){
     myClass[m] = function(){
          throw new Error("Method " + m + " has not been implemented");
     };
 }

Then, we will have

 class B extends A implements C, D
      csMethod : ->
             [...]
      dsMethod : ->
             [...]

Or just make a mixin modificator, making new objects of C and D inside B's creator function and mixes its methods into B

  class B extends A mixin C, D

@ream88
Copy link

ream88 commented Jan 13, 2013

Hey folks, I got Mixins in CoffeeScript ;)

@andreypopp
Copy link

Mixins still could be useful for specifying behaviours which do not require managing their own "state" — in the context of Backbone I can think of a ViewBehaviour which can provide some methods for View for turning el into "contenteditable" element.

I still do think that mixins should be implemented by mangling a prototype chain, so they easily can be stacked up into complex behaviours. I propose the following implementation as a draft:

class Base
  @with: (mixins...) ->
    for mixin in mixins

      cur = mixin
      protos = loop
        proto = Object.getPrototypeOf(cur)
        break if proto == Object.prototype
        cur = proto

      protos.reverse()
      protos.push(mixin)
      protos.push(this.prototype)

      newProto = Object.getPrototypeOf(this.prototype)

      for proto in protos
        newProto = extend Object.create(newProto), {constructor: newProto.constructor}, proto

      this.__super__ = Object.getPrototypeOf(newProto)
      this.prototype = newProto

# example

Mixin =
  a: ->
    console.log 'Mixin'

class A extends Base
  @with Mixin

  a: ->
    console.log 'A'
    super

a = new A
a.a()

# prints 'A'
# prints 'Mixin'

So basically what I think could be useful as a language support — include or with keyword instead of static method call (for backward compatibility) and corresponding super references inside non-methods.

class B extends A with Mixin

@jamesonquinn
Copy link

I support closing this bug. Here's how I get mixins:

mixOf = (base, mixins...) ->
  class Mixed extends base
  for mixin in mixins by -1
    for name, method of mixin::
      Mixed::[name] = method
  Mixed

...

class A extends mixOf Foo, Bar

Yes, it makes the prototype chain longer and calling Foo methods from A is microscopically slower, but it has almost exactly the semantics I'd expect¹, and it's 6 simple lines. In fact, it's hard for me to imagine what I'd want any of the above-proposed syntactic sugar to compile into, if not this.

¹ I mean:

  • yes: "super" from A methods
  • yes: no python-style method-resolution-order magic from "super" in Bar methods (that would be multiple inheritance, not mixins)
  • no: methods that Bar inherits from a superclass (technically, mixins should have this; but pragmatically, it's good that this doesn't, because it discourages mixins as anything but simple one-offs.)
  • no: changes to mixin are not reflected in A (again, technically bad but pragmatically good)

@yi
Copy link

yi commented Jun 12, 2013

I found Ruby's acts_as pattern is really handy when doing Ruby on Rails work. I were long to bring it to JS. Inspired by your comments, I've put a npm module to provide a similar "composition over inheritance" way in coffee. And it can also check who-looks-like-who for duck typing
fyi: https://npmjs.org/package/acts-as

@vendethiel
Copy link
Collaborator

I think you should post that on the mailing list/irc, that possibly can interest some people. (I'd like to reduce noise in old and closed issue, however :).)

@yi
Copy link

yi commented Jun 14, 2013

understand, thank you 😄

@printercu
Copy link

You can take a look at CoffeeClasskit.

I made it with an eye on Ruby's modules system. It uses Object.defineProperty, __proto__ and other modern features, so it wouldn't work in old browsers. But works fine in node.

@darky
Copy link

darky commented Jul 4, 2014

For some may be interesting this variant: https://gist.github.com/darrrk/75d6a6a4d431e7182888

@GabrielRatener
Copy link
Contributor

I think as ES6 and proxies are coming, this should be reconsidered.

@arximboldi
Copy link

Hi!

Just wanted to bring awareness to a little module I made, which implements multiple inheritance: https://github.com/arximboldi/heterarchy

The interesting thing about this implementation is that, unlike the mixin-based approaches discussed here so far, it supports cooperative multiple inheritance, in a way similar to how Python does it. It has some drawbacks, so I'm unsure it's appropriate for the core language, but it should help pythonistas to import their beloved patterns.

Cheers!

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

No branches or pull requests