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

Function stubs? #3093

Closed
00dani opened this issue Jul 29, 2013 · 10 comments
Closed

Function stubs? #3093

00dani opened this issue Jul 29, 2013 · 10 comments

Comments

@00dani
Copy link

00dani commented Jul 29, 2013

In MoonScript, it's possible to get a reference to an object method that remains bound to its original object. This feature is called a "function stub" and works like this:

higherOrderFunction obj\method
-- compiles to
return higherOrderFunction((function()
  local _base_0 = obj
  local _fn_0 = _base_0.method
  return function(...)
    return _fn_0(_base_0, ...)
  end
end)())

Given the way JavaScript's objects work and that Function::bind isn't reliably available on all browsers, a similar functionality in CoffeeScript would be valuable.

We don't use \ to denote method calls in CoffeeScript, so simply copying MoonScript's notation for the concept wouldn't work so well; perhaps reusing the bound-function symbol => in the contexts we use . would, like so?

higherOrderFunction obj=>method

Would compile to:

higherOrderFunction(function () {
  return obj.method.apply(obj, arguments);
});

The above CoffeeScript is currently a syntax error, so adding this use for => shouldn't break backwards-compat with anything. I'm open to other operator suggestions, of course.

@jashkenas
Copy link
Owner

We used to have an arbitrary-object binding operator in CoffeeScript, but it was removed because it wasn't generally terribly useful. In what real-world cases would this be? Explain how those uses are worth the conceptual complexity of the new syntax.

@00dani
Copy link
Author

00dani commented Jul 29, 2013

I think it's essentially only really applicable when passing a method into a higher-order function instead of calling the method directly. One (fairly common?) example of doing that from my personal experience is when using asynchronous methods with promises:

# with Function::bind (which assumes that Function::bind is available on this platform!):
Q.nfcall obj.method.bind(obj), argumentOne, argumentTwo
# with function stub:
Q.nfcall obj=>method, argumentOne, argumentTwo

@jashkenas
Copy link
Owner

In CoffeeScript, you'd normally define that method as a =>, bound method, and then you'd be good to go.

Q.nfcall obj.method, arg1, arg2

Anything else?

@00dani
Copy link
Author

00dani commented Jul 29, 2013

Well, that works if you assume all objects are being defined by you and in CoffeeScript, and not being received from external libraries. The latter is overwhelmingly more common in cases where you'd want to decorate the function, I suspect. (If you're defining the class directly, it can just use promises right off the bat and therefore doesn't even need Q.nfcall!)

@jashkenas
Copy link
Owner

I'm afraid you're going to have to think of a more compelling example, then.

CoffeeScript shouldn't be adding new syntax and language features that are only helpful when working with external code -- they should be rightly useful when working with CoffeeScript itself. In that sense, this proposal feels like a variant of the old bind operator, which I'd encourage you to loop up in the archives.

@00dani
Copy link
Author

00dani commented Jul 29, 2013

Function stub syntax would also be applicable to actual CoffeeScript-'native' objects in situations where the original method wasn't defined using =>; however, I imagine such methods wouldn't be particularly idiomatic code and can appreciate that adding features to support such design isn't really a worthwhile endeavour.

As for the question of external libraries, I'm not sure avoiding features that aid JavaScript interop is the best plan, since it's an obvious reality that CoffeeScript code must interoperate with some amount of JavaScript. Automatic binding of natively-defined methods with => goes a long way to "fixing" one of JavaScript's quirks. There's still no corresponding way to ensure binding for externally-defined methods, however, which introduces inconsistency in the handling of methods that otherwise seem essentially the same and means that that particular JS quirk still impinges on CoffeeScript development.

@connec
Copy link
Collaborator

connec commented Jul 29, 2013

Using => in class bodies has always felt like a bit of an 'anti-pattern' because it can lead to confusion due to the bound method effectively shadowing the prototype method. It also limits what can be done with prototype chaining etc. if some of your methods assume other methods will be bound when in fact they may not be, if the prototype is used elsewhere.

It feels like this kind of syntax offers far more flexibility and conceptual simplicity than bound instance methods.

The biggest use-case for me, mostly writing client-side code, is for writing event handlers e.g.:

class MyClass

  constructor: ->
    @on 'event1', => @handle_event1 arguments...
    @on 'event2', => @handle_event2 arguments...

  # or

  constructor: ->
    @on 'event1', @=>handle_event1
    @on 'event2', @=>handle_event2

  ...

Naturally, you could wrap that up in a little @callback method or something, or iterate through strings like in Backbone, but I find myself writing such helpers in a great number of classes across a wide range of projects.

I'm not sure about the syntax, but I like the idea.

@jashkenas
Copy link
Owner

@connec -- you should also check out the previous "bind" operator.

CoffeeScript's fat arrow is way less flexible ... but it turns out that in practice, if this is relevant to the function, then it's almost always the case that it needs to be bound lexically, like the rest of your variables, which is what the fat arrow does. If you write a function that wants a dynamic this argument, it's almost always better to pass it as a parameter to the function instead.

Fat arrows mean that you only have to bind once to the lexical this, and then you can forget about it -- as opposed to having to bind the function every time you reference or use it. Finally, in most modern JS platforms, .bind is always there if you need it.

@connec
Copy link
Collaborator

connec commented Jul 29, 2013

I understand your argument for 'lexicality', and that's definitely a way around the problem, but it still feels like such a common idiom, and supporting common, simple patterns with concise syntax seems like a nice idea.

Passing this around is certainly an option if you have control over the API, but that can feel like a lot of extra noise when it looks the context should be available anyway. For example, calling on 'event', obj, obj.method or on 'event', obj.method.bind(obj) isn't very DRY, and obfuscates the simplicity of what you're trying to do. Even on 'event', -> obj.method arguments... feels like writing far too much for what 'feels' like such a simple thing, especially with the prevalence of event-driven APIs.

The fat arrow also moves this consideration to the definition site, rather than the call site. You could call this a 'good thing™' but Javascript's semantics make it hard to believe that a.b will give you the function b bound to a, requiring you to look up the definition to decide if you're safe, of if you need to invoke one of the lengthy alternatives.

Having a syntax for this would make it much easier to see what was going on, and even if the pattern isn't used by everyone every day it's nice to have an explicit and unified (and concise) way of expressing 'I want to call this object's method in its context'.

@connec
Copy link
Collaborator

connec commented Jul 29, 2013

For context: #251 added the original binding application operator (<-) and #431 removed it.

I think a 'bound property access' is a bit more natural than a 'bound function application' though, even though the use-cases more-or-less overlap.

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

3 participants