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

Yet another special syntax #289

Closed
matehat opened this issue Mar 27, 2010 · 12 comments
Closed

Yet another special syntax #289

matehat opened this issue Mar 27, 2010 · 12 comments

Comments

@matehat
Copy link
Contributor

matehat commented Mar 27, 2010

Hi everyone,

after some really deep thoughts around the ideas on asynchronous programming with coffeescript, a more general idea popped in. We all know anonymous functions in coffeescript are ridiculously easy and short, thanks to javascript flexibility with closures. I'd like to go a step further with the way functions are passed around between functions.

Having a general purpose function that requires a callback as one of its argument is a common idiom in coffee/javascript. That function might operate on some data, or anything, then yields result to the callback. It might invoke it repetitively (for iterator-like behavior), or just once every now and then (for event-based behavior). When you read or write code using that pattern, you always get the clutter of an additional variable manipulated in the code. That variable is another thing to remember, or to look for when you read someone else's code.

Briefly, I think that the numerous situations where only one of the arguments is used as a callback warrants the idea of having a syntax that makes those situations more enjoyable, more readable. Since the callback can be used anywhere in a function body and since that function can contain any number of nested functions, a special indicator needs to occur somewhere around the definition itself.

I propose a combination of & and invoke. Here are some short examples :

Array::map: & -> 
  (l: or []).push(invoke i) for i in @
  l or []

Array::select: & -> 
  (l: or []).push(i) for i in @ when (invoke i)
  l or []

Array::reduce: (init) & ->
  (init: invoke init, i) for i in @
  init

As Tim-Smart pointed out, that's really nothing we can't already do, but I think it completely removes some of the clutter and makes the code simpler. I was going to propose yield at firrst, but StanAngeloff assured be it was everything but a good idea, since some js implementations were already using that special keyword.

What do you think?

@matehat
Copy link
Contributor Author

matehat commented Mar 27, 2010

Just so things are not confused altogether, the above still implies that one would call one of the above functions in the usual way :

[1, 2, 3, 4].map (x) -> x*x

I'm just proposing an alternate and cleaner way to manipulate the single passed callback from the within the function where it's used as an argument.

@weepy
Copy link

weepy commented Mar 27, 2010

I don't quite see how it's any clearer than the following standard way ?

Array::map: fn -> 
  (l: or []).push(fn i) for i in @
  l or []

@matehat
Copy link
Contributor Author

matehat commented Mar 27, 2010

In tiny functions like the one we took as an example, there isn't a big difference. But again, that's only about removing some unnecessary elements from the core behavior of a function. It "calls" an external function, and that is so common that it would be nice if the name of that external function was abstracted away.

Ruby does so in a fairly different way, but I think the idea is great. I don't think Coffeescript should be another Ruby, but we can still consider other ideas.

(BTW, you forgot parens around function arguments; that currently compiles into something you probably don't want :)

@matehat
Copy link
Contributor Author

matehat commented Mar 29, 2010

People should just calm down, the debate is getting out of hands :)

Seriously, here's an alternate for the 3rd of my examples :

Array::reduce: (init, &) ->
  (init: invoke init, i) for i in @
  init

I think it makes more sense

@weepy
Copy link

weepy commented Mar 29, 2010

What would the normal CS look like for this ?

@StanAngeloff
Copy link
Contributor

I think part of the problem is we still don't have a lengthy enough, real-word example of how this will be used. In the examples shown here, it's usually a one-liner and it isn't very clear what the added benefits of the alternative syntax are.

If you are doing a lot of Node.js programming this would be a welcomed addition. If you are the type of person who deals with Prototype, jQuery ick or anything like that, you'll probably never use it or use it rarely at best.

I sort of like the & syntax as it reminds me of C/PHP where it's used to pass in a reference to a pointer/variable. It sort of resembles the same, but in the context of callbacks.
WRT the invoke keyword, it's cool, but it doesn't feel right. I've done my fair share of .NET programming as well and .Invoke is quite common when dealing with reflection or threading. I don't have any better suggestions, so I might as well shut up.

EDIT: It might not be clear from my post, but I do like the idea in general.

@matehat
Copy link
Contributor Author

matehat commented Mar 29, 2010

That's the thing. In most of the situations where that pattern applies, even for non-aync contexts, functions are rather concise and not much logic is embedded inside a single function because that function delegates most of the work to the callback it receives as argument. The length is not in the function itself, for that reason. My proposition shows its strengths, for instance, in a module or an object that has a lot of those small functions where each would otherwise have its own callback variable.

I'll post an example of such a case in a bit. For the invoke keyword, knowing yield and return are taken, I just can't thing of another name. My thesaurus's completely depleted...

@weepy
Copy link

weepy commented Mar 29, 2010

call ?

@tim-smart
Copy link
Contributor

Making code simpler and making code shorter are two different things in my opinion.

do_it: (fn, value, cb) ->
  fn value, (result) ->
    cb result

is much less ambiguous than

do_it: (fn, value, &) ->
  fn value, (result) ->
    result

or using any other special keyword like yield etc. I really don't like the idea of covering up a callback function to make it feel more like a returning function, as all it does is bring un-necessary complexities to the language.

Creating the async functions isn't the problem, the problem is calling the async functions. I would first get the latter nailed before moving to the lesser problem.

@StanAngeloff
Copy link
Contributor

In order to get things moving and for the sake of the discussion and flying crazy ideas, why not combine everything so far to get:

myAsyncFunc: (arg1, &cb) ->
    result: fs.doIO arg1, defer
    return result

-->

var myAsyncFunc;
myAsyncFunc = function myAsyncFunc(arg1, cb) {
    var result;
    fs.doIO(arg1, function() {
        result = arguments[0];
        cb(result);
        return undefined;
    });
}
  • Callback is defined in the arguments list prefixed with an &
  • defer is used in place of the callback for async calls; no option to prefix the call -- it should be consistent
  • returns are converted to callback calls + return null/undefined as per gfxmonk's branch
  • for loops, the callback can be invoked using plain old cb(item)

Crazy simple, no ?

@matehat
Copy link
Contributor Author

matehat commented Mar 29, 2010

(as always, stan, good point..)

The idea might just not be that good after all. Anyway, I can still explain a bit further why that idea popped in my mind and why I thought about it. For those of you that have worked a bit with multilinear geometry or relativistic quantum fields, it might sound familiar. A particle (read function) is always associated with a set a coordinates or quantum numbers (read variables) that happen to describe its state. When one of these coordinates happens to not change with time, in such a way that some symmetry preserve its state in that dimension (read: some variable will always be a callback), we usually stop using that variable to describe the particle, to simplify equations. Indeed, the particle is not any simpler, its just expressed more simply.

Sorry, had to do that

@jashkenas
Copy link
Owner

matehat: I think your answer brings this back around to it's conclusion. I agree that it's a shortcut, but there's a bit too much new syntax for too little gain in expressiveness.

Relativistic quantum fields get the last word. Closing as a wontfix.

This issue was closed.
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

5 participants