Skip to content
This repository

Multiple inheritance? Mixins? #452

Closed
TrevorBurnham opened this Issue June 22, 2010 · 33 comments
Trevor Burnham
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?

Andre

Yes, mixins - I'm missing them too.

Jeremy Ashkenas
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?

Trevor Burnham
Collaborator

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.

Tim Cameron Ryan
tcr commented June 23, 2010
class A extends Foo merge Bar, Baz

?

Jeremy Ashkenas
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.

Stan Angeloff

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.

Jeremy Ashkenas
Owner

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

Henry Greville

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

+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

+1

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

class Button extends Control implements Options, Events

+1

Hajime Morrita

+1

Jeremy Ashkenas
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.

Hajime Morrita

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

Ricardo Tomasi

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);
Henry Greville

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

Peter Hollows

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

Clark Van Oyen

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
Deleted user

The content you are editing has changed. Reload the page and try again.

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

Sending Request…

Attach images by dragging & dropping or selecting them. Octocat-spinner-32 Uploading your images… Unfortunately, we don't support that file type. Try again with a PNG, GIF, or JPG. Yowza, that's a big file. Try again with an image file smaller than 10MB. This browser doesn't support image attachments. We recommend updating to the latest Internet Explorer, Google Chrome, or Firefox. Something went really wrong, and we can't process that image. Try again.

Tomas Sandven

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

Thomas Eng

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.

Nami-Doc
Collaborator

satyr/coco#144

Also, this is 2 years old.

Thomas Eng

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

Nami-Doc
Collaborator

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

Octavio Turra

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
Mario Uher

Hey folks, I got Mixins in CoffeeScript ;)

Andrey Popp

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

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
yi commented June 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

Nami-Doc
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 :).)

Nami-Doc Nami-Doc reopened this June 12, 2013
Nami-Doc Nami-Doc closed this June 12, 2013
Yi
yi commented June 13, 2013

understand, thank you :smile:

printercu

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.