jashkenas / coffeescript

Unfancy JavaScript
https://coffeescript.org/
MIT License
16.52k stars 1.98k forks source link

Method Overloading #531

Closed devongovett closed 14 years ago

devongovett commented 14 years ago

A useful addition to CoffeeScript's class implementation would be method overloading. This would make working with functions that accept a variable number of arguments much easier. For example:

class Users
  find: (id) ->
    # find user by id

  find: (first, last) ->
    # find user by first and last name

could be written instead of:

class Users
  find: (one, two) ->
    switch arguments.length
      when 1
        id: one
        # find user by id
      when 2
        first: one
        last: two
        # find user by first and last name

which is much more code, and is more complex. John Resig experimented with Method Overloading, and has a good post on how it can be implemented in JavaScript (very simple, actually). Here is the function.

// addMethod - By John Resig (MIT Licensed)
function addMethod(object, name, fn){
    var old = object[ name ];
    object[ name ] = function(){
        if ( fn.length == arguments.length )
            return fn.apply( this, arguments );
        else if ( typeof old == 'function' )
            return old.apply( this, arguments );
    };
}

This method would only need to be run when the CoffeeScript compiler detects that there are two functions with the same name and with a different number of arguments.

Caveats

  1. Moving objects from one object to another at runtime: which version of the method is copied? Answer: all of them. Just like if you wrote the function using a switch on arguments.length (see above), all versions of the method would be copied. This fits in with Resig's implementation.
  2. Functions with a variable number of arguments using splats - Not sure how to fix this problem. If you have a suggestion, please post it in the comments. The only thing I can think of right now is that these methods would not be overloadable. If you tried to overload them, either a compile-time error would be thrown or they would just get overwritten like they are currently.

I think that Method Overloading could be a very useful feature to add to CoffeeScript. It would require no syntax changes whatsoever, it is easy to explain to people, and it is very convenient. What do you think?

TrevorBurnham commented 14 years ago

It would be a bit confusing if this were only on classes and not objects, but of course CoffeeScript has no way of detecting when two classes that share the same name are bound to any old object...

Right now classes act a lot like objects, e.g. fields defined outside of a function have no scope (see issue 499). I'd like to see classes be more like classes in other languages, so I give +1 to this proposal.

Edit: Actually, on second thought, how does this work with extends? And is it possible for the overhead to only be incurred when the feature is used, rather than every function on every class being a wrapper?

weepy commented 14 years ago

from John's post :

Obviously, there's some pretty big caveats when using this:

The overloading only works for different numbers of arguments - it doesn't differentiate based on type, argument names, or anything else. (ECMAScript 4/JavaScript 2, however, will have this ability - called Multimethods - I'm quite excited.)
All methods will some function call overhead. Thus, you'll want to take that into consideration in high performance situations.
devongovett commented 14 years ago

@TrevorBurnham yes, the overhead would only be necessary when the compiler detects that there are two methods of the same name with a different number of arguments. It works with extends the same way it would work if you wrote it using the switch on arguments length (e.g. above) - all versions of the function essentially act as one, so all of them would be copied over.

StanAngeloff commented 14 years ago

It's not a bad idea, however I reckon the issue lies when you start defining your classes in several places or simply extend them:

class NewUsers
  find: (id) ->
    # find new users by id

class NamedUsers extends NewUsers
  find: (first, last) ->
    # find users by first and last name

(new NamedUsers).find(10)  # should be by first: 10 and not by id: 10

One solution would be to iterate over the properties of the parent prototype and if two functions have the same name -- merge them into one (unexpected behaviour). It gets complicated when you start having methods with the same signature in several child classes or when you use super to call a parent method which is overloaded.

evilpie commented 14 years ago

I think this should be implemented with some library, but not within the core language.

devongovett commented 14 years ago

@evilpie I disagree. I think that the reason people don't take advantage of method overloading very much in JavaScript (using a helper library function) is that the extra code that they have to write in order to use it is too great. I think the only way that people will take advantage of it is if it is part of the language itself (with no extra code for them to write).

evilpie commented 14 years ago

I dont think that this feature is really necessary and will be used often. You can already do something simliar by using the unpacking mechanisme. find: (parameters) -> {name, last, id}: if parameters? then paramters else {} if id alert "hey #$id" if name and last alert "hello $name $last" if name alert "hai $name"

devongovett commented 14 years ago

@StanAngeloff that's a tricky one! Since those methods are essentially different methods of the same name, I think that when one class extended another class with a method of the same name but a different number of arguments, the two would need to be merged (using Resig's method). This makes the behavior exactly the same as if you had two methods of different names - both would be available on the child class. So if one of the methods was on the superclass and one was on the child class, both would be available on the child class. This makes sense if you think of the methods as they would be if they had different names. Calling super shouldn't work in this case because if the methods had different names, there would be no super method. That's how I think about it anyway. :-)

devongovett commented 14 years ago

@evilpie again, I disagree. I would use this feature all the time. While your solution is interesting, there are some problems with it that method overloading would solve. First of all, I now have to pass an object to your method instead of just my arguments, which is ok, but is more typing for the user of my function - not so good for API design. This example is very similar to the one I used (above) with the switch on arguments.length, which I described the downsides of (up there). I think that this feature would get used by a lot of people, especially those trying to write APIs in CoffeeScript.

jashkenas commented 14 years ago

devongovett: I'm fairly skeptical of how the implementation of this would work, and how flexible it would be. Mind pulling together a patch for master that we can take a look at?

devongovett commented 14 years ago

jashkenas: I'll see what I can do. I haven't ever really done any work on the compiler before, so I'll need to familiarize myself with that before I can produce anything of value. Any suggestions on how to get started?

jashkenas commented 14 years ago

Ah, well, no worries on integration into the compiler, then. How about just a good test-case example of the situations in which you'd use overloading in coffee, and write out what the JavaScript you'd like it to compile into would look like.

trans commented 14 years ago

If you really want to do this --and I think it would be a great feature personally, then there are two good approaches.

The first is simply adding something to the language to make parameter switching very easy.

find: (*) ->
  (id) ->
    ...
  (first, last) ->
    ...

The syntax no doubt needs adjustment. But you get the idea. This suffers none of the extends issues. It is simply a syntax sugar to make the parameter switching easy.

The other approach is full-on overloading support, and to do that requires introducing a way to mark methods as "new defs" vs. "re-defs".

class NewUsers
  find: (id) ->
    ...
  find: (first, last) ->
    ...

class NamedUsers extends NewUsers
  find: (first, last, middle) ->
    ...

In this case it would be considered a "re-def" and both definitions would be over-ridden in the superclass. Calling super would of course utilize the method signature. But if we did say,

class NamedUsers extends NewUsers
  find: *(first, last, middle) ->
    ...

Just to make up some notation, then it would add the method as a third possible signature rather than over-ride. Of course if two signatures are identical it over-rides regardless.

jashkenas commented 14 years ago

This ticket was being left open to provide devongovett an opportunity to prove the idea with a patch -- but it's been a while now, so I'm going to close it as a wontfix. We can re-open it if someone else has a proposed implementation.

My general opinion about method overloading in dynamically typed languages stands ... it's like pattern-matching dispatch, and can't be done very well in such a static fashion because you don't have types on any of your arguments. Dispatching based solely on the number of arguments is crude at best.

thejh commented 13 years ago

How about this syntax or something similar? https://gist.github.com/1089339 I hope that it doesn't introduce parser conflicts...

juliankrispel commented 10 years ago

if anybody seeks this kind of functionality, have a look at multimethod.js, looks promising.

zackarychapple commented 9 years ago

Was really hoping to be able to do this... Any hope of resurrections?