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

Improve chaining syntax #1495

Closed
JulianBirch opened this Issue Jul 8, 2011 · 180 comments

Comments

Projects
None yet
@JulianBirch

JulianBirch commented Jul 8, 2011

Since chaining is a pretty common feature of Javascript libraries, how about a dedicated syntax for it?

e.g.

$("p.neat").addClass("ohmy").show("slow");

could be written

$("p.neat")..       # Note the "dot dot"
  addClass "ohmy"
  show "slow"

Alternatively, you could go with something insanely LISPy

->>
  $ "p.neat"
  addClass "ohmy"
  show "slow"

NB, the semantics I'm proposing would be equivalent to

a = $ "p.neat"
b = a.addClass "ohmy"
c = b.show "slow"
@michaelficarra

This comment has been minimized.

Show comment
Hide comment
@michaelficarra

michaelficarra Jul 8, 2011

Collaborator

I think this is a duplicate of #1251

Collaborator

michaelficarra commented Jul 8, 2011

I think this is a duplicate of #1251

@JulianBirch

This comment has been minimized.

Show comment
Hide comment
@JulianBirch

JulianBirch Jul 9, 2011

Yes and no. The problem is the same, but proposed solution is different. 1251 was rejected and closed because the syntax proposed ultimately didn't actually improve matters. I feel the above proposals do, in fact, produce a nicer syntax for "train wreck" expressions. It requires you to use separate lines and indentation, but if you wanted to do it on one line, you could still fall back to the existing syntax. I've realized the ".." syntax can be futher improved to

$ p.neat..
  addClass "ohmy"
  show "slow"

Which I feel looks a lot more like coffeescript than

$("p.neat").addClass("ohmy").show("slow")

JulianBirch commented Jul 9, 2011

Yes and no. The problem is the same, but proposed solution is different. 1251 was rejected and closed because the syntax proposed ultimately didn't actually improve matters. I feel the above proposals do, in fact, produce a nicer syntax for "train wreck" expressions. It requires you to use separate lines and indentation, but if you wanted to do it on one line, you could still fall back to the existing syntax. I've realized the ".." syntax can be futher improved to

$ p.neat..
  addClass "ohmy"
  show "slow"

Which I feel looks a lot more like coffeescript than

$("p.neat").addClass("ohmy").show("slow")
@satyr

This comment has been minimized.

Show comment
Hide comment
@satyr

satyr Jul 9, 2011

Collaborator

Also see #1407.

Collaborator

satyr commented Jul 9, 2011

Also see #1407.

@devongovett

This comment has been minimized.

Show comment
Hide comment
@devongovett

devongovett Jul 12, 2011

How about just putting the dots there as usual, but instead of this code:

$ "p.neat"
  .addClass "ohmy"
  .show "slow"

compiling to this JS as it does now:

$("p.neat".addClass("ohmy".show("slow")));

it would compile as expected to this JS:

$("p.neat").addClass("ohmy").show("slow");

This would essentially make the newline character mean something after function calls without parenthesis.

devongovett commented Jul 12, 2011

How about just putting the dots there as usual, but instead of this code:

$ "p.neat"
  .addClass "ohmy"
  .show "slow"

compiling to this JS as it does now:

$("p.neat".addClass("ohmy".show("slow")));

it would compile as expected to this JS:

$("p.neat").addClass("ohmy").show("slow");

This would essentially make the newline character mean something after function calls without parenthesis.

@erisdev

This comment has been minimized.

Show comment
Hide comment
@erisdev

erisdev Jul 12, 2011

@devongovett cute, but wouldn't that syntax make it harder to correctly parse this valid (and pretty common) construct?

lemmeTellYas 'all about some fruits'
    kiwi: 'real itchy-like'
    okra: 'all slimy inside'
    fig: 'full of little wasps'

erisdev commented Jul 12, 2011

@devongovett cute, but wouldn't that syntax make it harder to correctly parse this valid (and pretty common) construct?

lemmeTellYas 'all about some fruits'
    kiwi: 'real itchy-like'
    okra: 'all slimy inside'
    fig: 'full of little wasps'
@devongovett

This comment has been minimized.

Show comment
Hide comment
@devongovett

devongovett Jul 12, 2011

@erisdiscord no, I don't think so as the dots before the function names would be required and there aren't any colons after them. It's really the same as if you just removed parenthesis from around the arguments of each function.

devongovett commented Jul 12, 2011

@erisdiscord no, I don't think so as the dots before the function names would be required and there aren't any colons after them. It's really the same as if you just removed parenthesis from around the arguments of each function.

@JulianBirch

This comment has been minimized.

Show comment
Hide comment
@JulianBirch

JulianBirch Jul 15, 2011

Whilst my preference for as little punctuation as possible is marked, I can't see anything unambiguous with Devon's rule.

JulianBirch commented Jul 15, 2011

Whilst my preference for as little punctuation as possible is marked, I can't see anything unambiguous with Devon's rule.

@arbales

This comment has been minimized.

Show comment
Hide comment
@arbales

arbales Jul 16, 2011

+1 for @ devongovett's proposal — this might be the behavior I'd expect anyway.

arbales commented Jul 16, 2011

+1 for @ devongovett's proposal — this might be the behavior I'd expect anyway.

@TrevorBurnham

This comment has been minimized.

Show comment
Hide comment
@TrevorBurnham

TrevorBurnham Sep 21, 2011

Collaborator

Y'all should take a look at #1407. The proposal there would allow you to write

$ "p.neat"
  .addClass "ohmy"
  .show "slow"

without making newlines/indentation significant. In my view, it's the most elegant possible solution to this long-standing problem.

Collaborator

TrevorBurnham commented Sep 21, 2011

Y'all should take a look at #1407. The proposal there would allow you to write

$ "p.neat"
  .addClass "ohmy"
  .show "slow"

without making newlines/indentation significant. In my view, it's the most elegant possible solution to this long-standing problem.

@qrgnote

This comment has been minimized.

Show comment
Hide comment
@qrgnote

qrgnote Oct 15, 2011

A more explicit way perhaps could be

$ "p.neat" > addClass "ohmy" > show "slow"

or even with surrounding spaces

$ "p.neat" . addClass "ohmy" . show "slow"

currently writing all the () parenthesis is a pain.

I don't like the idea of using straight indentations for chaining. Indentations to me means "Sub/Below" but chaining is more like "Next"...

Something like

$ "p.neat"
.addClass "ohmy"
.show "slow"

would make more 'sense' but Jeremy already wrote that off here.

Maybe something like:

$ "p.neat" > 
  addClass "ohmy"
  show "slow"

could work.

I kinda like JulianBirch idea for .. as well...

$ "p.neat" .. addClass "ohmy" .. show "slow"

$ "p.neat" ..
  addClass "ohmy"
  show "slow"

qrgnote commented Oct 15, 2011

A more explicit way perhaps could be

$ "p.neat" > addClass "ohmy" > show "slow"

or even with surrounding spaces

$ "p.neat" . addClass "ohmy" . show "slow"

currently writing all the () parenthesis is a pain.

I don't like the idea of using straight indentations for chaining. Indentations to me means "Sub/Below" but chaining is more like "Next"...

Something like

$ "p.neat"
.addClass "ohmy"
.show "slow"

would make more 'sense' but Jeremy already wrote that off here.

Maybe something like:

$ "p.neat" > 
  addClass "ohmy"
  show "slow"

could work.

I kinda like JulianBirch idea for .. as well...

$ "p.neat" .. addClass "ohmy" .. show "slow"

$ "p.neat" ..
  addClass "ohmy"
  show "slow"
@tcr

This comment has been minimized.

Show comment
Hide comment
@tcr

tcr Oct 15, 2011

Just to throw another syntax in the ring, the ellipsis operator could be overloaded to mean "continue with result of last line", as such:

$ "p.neat"
...addClass "ohmy"
...show "slow"

You're not really saving punctuation but it is very simple to write, just as easy to read.

tcr commented Oct 15, 2011

Just to throw another syntax in the ring, the ellipsis operator could be overloaded to mean "continue with result of last line", as such:

$ "p.neat"
...addClass "ohmy"
...show "slow"

You're not really saving punctuation but it is very simple to write, just as easy to read.

@JulianBirch

This comment has been minimized.

Show comment
Hide comment
@JulianBirch

JulianBirch Oct 17, 2011

As qrgnote points out, jashkenas doesn't like the leading dots proposal. Satyr does, obviously, since it works in Coco. Personally, I think* that when writing normal javascript, it's more common to be writing ")." or "})." at the start of a line than just ".", which makes me favour changing the behaviour.

I don't think using "greater than" works, because it's impossible to tell what you meant by ">" anymore. Which actually pretty much leaves us with the ".." proposal.

*I remember than when Python introduced their inline if statement, they designed it on the basis of analysing a large code base (their own) to see what was the more common idiom. I did a quick scan of jQuery edge for ^\s+[.] which found only one case in which the "dot on newline implicitly closes a bracket" would not have been the intended behaviour.

For reference, the line was

            if (rvalidchars.test(data.replace(rvalidescape, "@")
        .replace(rvalidtokens, "]")
        .replace(rvalidbraces, ""))) {

JulianBirch commented Oct 17, 2011

As qrgnote points out, jashkenas doesn't like the leading dots proposal. Satyr does, obviously, since it works in Coco. Personally, I think* that when writing normal javascript, it's more common to be writing ")." or "})." at the start of a line than just ".", which makes me favour changing the behaviour.

I don't think using "greater than" works, because it's impossible to tell what you meant by ">" anymore. Which actually pretty much leaves us with the ".." proposal.

*I remember than when Python introduced their inline if statement, they designed it on the basis of analysing a large code base (their own) to see what was the more common idiom. I did a quick scan of jQuery edge for ^\s+[.] which found only one case in which the "dot on newline implicitly closes a bracket" would not have been the intended behaviour.

For reference, the line was

            if (rvalidchars.test(data.replace(rvalidescape, "@")
        .replace(rvalidtokens, "]")
        .replace(rvalidbraces, ""))) {
@qrgnote

This comment has been minimized.

Show comment
Hide comment
@qrgnote

qrgnote Oct 25, 2011

It'll be great if it can work inline as well... because going back and adding (&) is annoying.

check = require "validator" ... check

could return

check = require("validator").check

Edit:

Actually in practice entering ... is weird inline... keeps me thinking of "to be continued", maybe > or . (spaces) would be best.

check = require "validator" . check
check = require "validator" > check

qrgnote commented Oct 25, 2011

It'll be great if it can work inline as well... because going back and adding (&) is annoying.

check = require "validator" ... check

could return

check = require("validator").check

Edit:

Actually in practice entering ... is weird inline... keeps me thinking of "to be continued", maybe > or . (spaces) would be best.

check = require "validator" . check
check = require "validator" > check
@patrys

This comment has been minimized.

Show comment
Hide comment
@patrys

patrys Oct 26, 2011

Why fix something that is not broken? Introducing new meaning to existing symbols and operators is risky as at some point the JavaScript itself could decide to overload them. And chaining calls is not a common idiom in either CS or JS. It's just something that one popular client-side library happens to use.

Ruby is fine without chained call support. JS and CoffeScript offer you working solutions that require you to be verbose. If you need something else, you can probably roll your own solution without modifying the language, see this Ruby example for an idea of what to add to jQuery's prototype:

http://stackoverflow.com/questions/4099409/ruby-how-to-chain-multiple-method-calls-together-with-send/4099562#4099562

patrys commented Oct 26, 2011

Why fix something that is not broken? Introducing new meaning to existing symbols and operators is risky as at some point the JavaScript itself could decide to overload them. And chaining calls is not a common idiom in either CS or JS. It's just something that one popular client-side library happens to use.

Ruby is fine without chained call support. JS and CoffeScript offer you working solutions that require you to be verbose. If you need something else, you can probably roll your own solution without modifying the language, see this Ruby example for an idea of what to add to jQuery's prototype:

http://stackoverflow.com/questions/4099409/ruby-how-to-chain-multiple-method-calls-together-with-send/4099562#4099562

@JulianBirch

This comment has been minimized.

Show comment
Hide comment
@JulianBirch

JulianBirch Oct 26, 2011

Underscore isn't the only popular library that uses method chaining. jQuery uses it as well. The reason I suggested the ".." syntax is because that's the syntax Clojure has for this, so it's useful in Java as well.

JulianBirch commented Oct 26, 2011

Underscore isn't the only popular library that uses method chaining. jQuery uses it as well. The reason I suggested the ".." syntax is because that's the syntax Clojure has for this, so it's useful in Java as well.

@patrys

This comment has been minimized.

Show comment
Hide comment
@patrys

patrys Oct 26, 2011

@JulianBirch, I agree but the idiom is still not part of the language philosophy per se and reusing operators that are already part of the language comes with the risk of them suddenly gaining a meaning in a future version of JS.

patrys commented Oct 26, 2011

@JulianBirch, I agree but the idiom is still not part of the language philosophy per se and reusing operators that are already part of the language comes with the risk of them suddenly gaining a meaning in a future version of JS.

@JulianBirch

This comment has been minimized.

Show comment
Hide comment
@JulianBirch

JulianBirch Oct 26, 2011

Certainly, and tbh I think this has come down to a "won't change". I do find thrush operators useful, however, and it's a pity CoffeeScript doesn't have one.

JulianBirch commented Oct 26, 2011

Certainly, and tbh I think this has come down to a "won't change". I do find thrush operators useful, however, and it's a pity CoffeeScript doesn't have one.

@banacorn

This comment has been minimized.

Show comment
Hide comment
@banacorn

banacorn Oct 30, 2011

$ "p.neat"
  .addClass "ohmy"
  .show "slow"

this is exactly what I'm looking and expecting for.
parenthesis is really a pain when doing chaining

banacorn commented Oct 30, 2011

$ "p.neat"
  .addClass "ohmy"
  .show "slow"

this is exactly what I'm looking and expecting for.
parenthesis is really a pain when doing chaining

@qrgnote

This comment has been minimized.

Show comment
Hide comment
@qrgnote

qrgnote Oct 30, 2011

yes, CoffeeScript does not support chaining.

You can't use CoffeeScript to chain, you have to resort to JavaScript.

CoffeeScript does such a great job of relieving parenthesesitis, that when I have to return to JavaScript awk!!! Especially when I have to "go back" to add parenthesis.

it's not easy to add parentheses backwards

my-module = require './mymod'
# awk! have to go to back and add parentheses
my-module = require('./mymod').method

It seems that not supporting chaining is an oversight of CoffeeScript. Chaining isn't just used by jQuery like the previous example shows, it's pervasive in CommonJS modules loading, and what's more Node, then that? Let alone a lot libraries like Underscore supports "chaining" as well. Like mongodb/mongoskin

posts = require('mongoskin').db('localhost:27017/blog').collections('posts')

jQuery and all the custom libraries built off of jQuery is important to support native imho. Parentheses sucks. I don't want to write JavaScript, I want to write CoffeeScript.

Question, why was "parentheses" made optional on function/method calls in the first place? That same rationale should be why chaining should be supported without parentheses.

chain 'uh'
chain('uh').chain "why does"
chain('uh').chain("why does").chain "my method calls"
chain('uh').chain("why does").chain("my method calls").chain "sometimes requries"
chain('uh').chain("why does").chain("my method calls").chain("sometimes requries").chain "parentheses"

Simple, CoffeeScript doesn't support chaining.

qrgnote commented Oct 30, 2011

yes, CoffeeScript does not support chaining.

You can't use CoffeeScript to chain, you have to resort to JavaScript.

CoffeeScript does such a great job of relieving parenthesesitis, that when I have to return to JavaScript awk!!! Especially when I have to "go back" to add parenthesis.

it's not easy to add parentheses backwards

my-module = require './mymod'
# awk! have to go to back and add parentheses
my-module = require('./mymod').method

It seems that not supporting chaining is an oversight of CoffeeScript. Chaining isn't just used by jQuery like the previous example shows, it's pervasive in CommonJS modules loading, and what's more Node, then that? Let alone a lot libraries like Underscore supports "chaining" as well. Like mongodb/mongoskin

posts = require('mongoskin').db('localhost:27017/blog').collections('posts')

jQuery and all the custom libraries built off of jQuery is important to support native imho. Parentheses sucks. I don't want to write JavaScript, I want to write CoffeeScript.

Question, why was "parentheses" made optional on function/method calls in the first place? That same rationale should be why chaining should be supported without parentheses.

chain 'uh'
chain('uh').chain "why does"
chain('uh').chain("why does").chain "my method calls"
chain('uh').chain("why does").chain("my method calls").chain "sometimes requries"
chain('uh').chain("why does").chain("my method calls").chain("sometimes requries").chain "parentheses"

Simple, CoffeeScript doesn't support chaining.

@patrys

This comment has been minimized.

Show comment
Hide comment
@patrys

patrys Oct 30, 2011

Consider me insane but I find foo = require('bar').baz much shorter and way more readable than:

foo = require 'bar'
    .baz

If parentheses are your number one problem in programming then let me congratulate you for having nothing else to worry about :)

patrys commented Oct 30, 2011

Consider me insane but I find foo = require('bar').baz much shorter and way more readable than:

foo = require 'bar'
    .baz

If parentheses are your number one problem in programming then let me congratulate you for having nothing else to worry about :)

@erisdev

This comment has been minimized.

Show comment
Hide comment
@erisdev

erisdev Oct 30, 2011

yes, CoffeeScript does not support chaining.

Wait, what?

You can't use CoffeeScript to chain, you have to resort to JavaScript.

Huh? This looks like CoffeeScript to me: $('.awesome').css(color: randomAwesomeColor()).appendTo('.gr8-stuff')

While I agree that a syntax that allows us to leave off parentheses would be the ant's pants, the status quo really isn't as bad as you make it sound. We've been using parentheses for years in other languages. C:

(If you're allergic to parentheses, you'll want to be extra careful and avoid exposure to Lisp or S-expressions)

erisdev commented Oct 30, 2011

yes, CoffeeScript does not support chaining.

Wait, what?

You can't use CoffeeScript to chain, you have to resort to JavaScript.

Huh? This looks like CoffeeScript to me: $('.awesome').css(color: randomAwesomeColor()).appendTo('.gr8-stuff')

While I agree that a syntax that allows us to leave off parentheses would be the ant's pants, the status quo really isn't as bad as you make it sound. We've been using parentheses for years in other languages. C:

(If you're allergic to parentheses, you'll want to be extra careful and avoid exposure to Lisp or S-expressions)

@banacorn

This comment has been minimized.

Show comment
Hide comment
@banacorn

banacorn Oct 30, 2011

$('#blah')
    .bar('foo')
    .bar('foo')
    .bar( ->
        fn()
    ).bar 'foo'
$ '#blah'
    .bar 'foo'
    .bar 'foo'
    .bar ->
        fn()
    .bar 'foo'

I think the latter would make my day better :)

banacorn commented Oct 30, 2011

$('#blah')
    .bar('foo')
    .bar('foo')
    .bar( ->
        fn()
    ).bar 'foo'
$ '#blah'
    .bar 'foo'
    .bar 'foo'
    .bar ->
        fn()
    .bar 'foo'

I think the latter would make my day better :)

@jashkenas

This comment has been minimized.

Show comment
Hide comment
@jashkenas
Owner

jashkenas commented Oct 30, 2011

@raganwald

This comment has been minimized.

Show comment
Hide comment
@raganwald

raganwald Oct 30, 2011

The substance of my comment onHN:

I would use indentation to discriminate between the cases:

list
  .concat(other)
    .map((x) -> x * x)
      .filter((x) -> x % 2 is 0)
        .reverse()

For pipelining, and:

brush
  .startPath()
  .moveTo(10, 10)
  .stroke("red")
  .fill("blue")
  .ellipse(50, 50)
  .endPath()

For what we are calling “chaining.” I use this now as a personal coding style when writing jQuery stuff with JQuery Combinators:

added
    .addClass('atari last_liberty_is_' + ids_of_added_liberties[0])
    .find('#'+ids_of_added_liberties[0])
        .when(doesnt_have_liberties)
            .removeClass('playable_'+added_colour)
            .addClass('no_liberties');

The first “addClass” and “find” are both sent to “added,” “when” is sent to the result of “find”, “removeClass” and the second “addClass” are both sent to the result of “when.” It feels to me very much like how we indent scope and syntactic blocks like “if” or “unless."

raganwald commented Oct 30, 2011

The substance of my comment onHN:

I would use indentation to discriminate between the cases:

list
  .concat(other)
    .map((x) -> x * x)
      .filter((x) -> x % 2 is 0)
        .reverse()

For pipelining, and:

brush
  .startPath()
  .moveTo(10, 10)
  .stroke("red")
  .fill("blue")
  .ellipse(50, 50)
  .endPath()

For what we are calling “chaining.” I use this now as a personal coding style when writing jQuery stuff with JQuery Combinators:

added
    .addClass('atari last_liberty_is_' + ids_of_added_liberties[0])
    .find('#'+ids_of_added_liberties[0])
        .when(doesnt_have_liberties)
            .removeClass('playable_'+added_colour)
            .addClass('no_liberties');

The first “addClass” and “find” are both sent to “added,” “when” is sent to the result of “find”, “removeClass” and the second “addClass” are both sent to the result of “when.” It feels to me very much like how we indent scope and syntactic blocks like “if” or “unless."

@erisdev

This comment has been minimized.

Show comment
Hide comment
@erisdev

erisdev Oct 30, 2011

@raganwald 👍 That is precisely how I feel it should be as well, if CoffeeScript is going to have such a syntax. Lately I tend to write my chained method calls on the same indentation level as the object

brush
.startPath()
.moveTo(10, 10)
…

but I think that indenting it your way makes more sense and is ultimately more consistent.

erisdev commented Oct 30, 2011

@raganwald 👍 That is precisely how I feel it should be as well, if CoffeeScript is going to have such a syntax. Lately I tend to write my chained method calls on the same indentation level as the object

brush
.startPath()
.moveTo(10, 10)
…

but I think that indenting it your way makes more sense and is ultimately more consistent.

@qrgnote

This comment has been minimized.

Show comment
Hide comment
@qrgnote

qrgnote Oct 30, 2011

Consider me insane but I find foo = require('bar').baz much shorter and way more readable than:

 foo = require 'bar'
  .baz

yes, but a single line chaining syntax would be nice.

foo = require 'bar' . baz
# or
foo = require 'bar' > baz
foo = require 'bar' .. baz
foo = require 'bar' ... baz

but @patrys point about future-proofing coffee-script is a good point...
we just have to decide on a syntax.

my vote right now goes for .. or both...

brush 
  ..moveTo 10, 10
  ..stroke "red"
  ..fill "blue"
  ..ellipse 50, 50

brush ...
  .moveTo 10, 10
  .stroke "red"
  .fill "blue"
  .ellipse 50, 50

qrgnote commented Oct 30, 2011

Consider me insane but I find foo = require('bar').baz much shorter and way more readable than:

 foo = require 'bar'
  .baz

yes, but a single line chaining syntax would be nice.

foo = require 'bar' . baz
# or
foo = require 'bar' > baz
foo = require 'bar' .. baz
foo = require 'bar' ... baz

but @patrys point about future-proofing coffee-script is a good point...
we just have to decide on a syntax.

my vote right now goes for .. or both...

brush 
  ..moveTo 10, 10
  ..stroke "red"
  ..fill "blue"
  ..ellipse 50, 50

brush ...
  .moveTo 10, 10
  .stroke "red"
  .fill "blue"
  .ellipse 50, 50
@raganwald

This comment has been minimized.

Show comment
Hide comment
@raganwald

raganwald Oct 30, 2011

FWIW, Smalltalk implemented chaining in its syntax and was indentation agnostic, so you could write

foo
  bar;
  bash: something;
  thenBlitz.

or:

foo bar; bash: something; thenBlitz.

If you want to go that way, I would avoid .. as a potential readability and accidental mistype liability for the same reasons that = and == often trip people up in if statements. JM2C.

Unfortunately, the semantics I would like in an operator conflict with Coffeescript’s goal of compiling to readable JS that corresponds closely to the semantics of the original source. Instead of an operator that means “send and return the receiver,” I would like an operator that means “send to the previous receiver,” which is exactly what the ; in Smalltalk means. So instead of:

foo
  ..bar()
  ..bash(something)
  .thenBlitz()

I would have suggested:

foo
  .bar()
  &.bash(something)
  &.thenBlitz()

Meaning “send bar to foo, and send bash(something) to foo, and send thenBlitz to foo.” I fear that the compiled code would be confusing, but like the way it reads. It describes to me what the code is doing, not making me think about the implementation of returning the receiver and then sending a message to the result.

Like .., you could still use it on one line:

 foo.bar()&.bash(something)&.thenBlitz()

raganwald commented Oct 30, 2011

FWIW, Smalltalk implemented chaining in its syntax and was indentation agnostic, so you could write

foo
  bar;
  bash: something;
  thenBlitz.

or:

foo bar; bash: something; thenBlitz.

If you want to go that way, I would avoid .. as a potential readability and accidental mistype liability for the same reasons that = and == often trip people up in if statements. JM2C.

Unfortunately, the semantics I would like in an operator conflict with Coffeescript’s goal of compiling to readable JS that corresponds closely to the semantics of the original source. Instead of an operator that means “send and return the receiver,” I would like an operator that means “send to the previous receiver,” which is exactly what the ; in Smalltalk means. So instead of:

foo
  ..bar()
  ..bash(something)
  .thenBlitz()

I would have suggested:

foo
  .bar()
  &.bash(something)
  &.thenBlitz()

Meaning “send bar to foo, and send bash(something) to foo, and send thenBlitz to foo.” I fear that the compiled code would be confusing, but like the way it reads. It describes to me what the code is doing, not making me think about the implementation of returning the receiver and then sending a message to the result.

Like .., you could still use it on one line:

 foo.bar()&.bash(something)&.thenBlitz()
@tcr

This comment has been minimized.

Show comment
Hide comment
@tcr

tcr Oct 30, 2011

@raganwald +1. This seems like an intuitive, semantic distinction between chaining vs continuation of previous lines.

Moved comments about chaining parentheses omission to #1407.

tcr commented Oct 30, 2011

@raganwald +1. This seems like an intuitive, semantic distinction between chaining vs continuation of previous lines.

Moved comments about chaining parentheses omission to #1407.

@tcr

This comment has been minimized.

Show comment
Hide comment
@tcr

tcr Oct 30, 2011

Just to recap, it looks like we're discussing three problems:

  1. Multi-line chaining vs. pipelining syntax (sort of a with-like construct) using indentation or perhaps a new operator:

    $('body')
        .html('<h1>hey</h1>')
        .find('h1')
            .addClass('cool')
    
  2. Permitting multi-line paren-free chaining syntax:

    $ 'body'
        .html '<h1>hey</h1>'
        .addClass 'cool'
    
  3. A single-line chaining syntax using perhaps a new operator a la require 'foo' .. baz

tcr commented Oct 30, 2011

Just to recap, it looks like we're discussing three problems:

  1. Multi-line chaining vs. pipelining syntax (sort of a with-like construct) using indentation or perhaps a new operator:

    $('body')
        .html('<h1>hey</h1>')
        .find('h1')
            .addClass('cool')
    
  2. Permitting multi-line paren-free chaining syntax:

    $ 'body'
        .html '<h1>hey</h1>'
        .addClass 'cool'
    
  3. A single-line chaining syntax using perhaps a new operator a la require 'foo' .. baz

@satyr

This comment has been minimized.

Show comment
Hide comment
@satyr

satyr Oct 30, 2011

Collaborator

@raganwald: Instead of an operator that means “send and return the receiver,” I would like an operator that means “send to the previous receiver,” which is exactly what the ; in Smalltalk means.

A separate proposal. See #1431.

Collaborator

satyr commented Oct 30, 2011

@raganwald: Instead of an operator that means “send and return the receiver,” I would like an operator that means “send to the previous receiver,” which is exactly what the ; in Smalltalk means.

A separate proposal. See #1431.

@TrevorBurnham

This comment has been minimized.

Show comment
Hide comment
@TrevorBurnham

TrevorBurnham Oct 30, 2011

Collaborator

Thanks, @timcameronryan. Let me add that there is a separate open issue for that second problem, #1407.

So to clarify, @raganwald: Are you proposing a chaining syntax where function results are cached as needed so that you could write, for instance,

$('#todos')
  .addClass('rad')
  .children()
    .addClass('tubular')
  .parent()
    .addClass('dudetastic')

and have it be equivalent to

_ref = $('#todos');
_ref.addClass('rad');
_ref.children().addClass('selected');
_ref.parent().addClass('active');

? Or are you just suggesting a stylistic convention?

Collaborator

TrevorBurnham commented Oct 30, 2011

Thanks, @timcameronryan. Let me add that there is a separate open issue for that second problem, #1407.

So to clarify, @raganwald: Are you proposing a chaining syntax where function results are cached as needed so that you could write, for instance,

$('#todos')
  .addClass('rad')
  .children()
    .addClass('tubular')
  .parent()
    .addClass('dudetastic')

and have it be equivalent to

_ref = $('#todos');
_ref.addClass('rad');
_ref.children().addClass('selected');
_ref.parent().addClass('active');

? Or are you just suggesting a stylistic convention?

@raganwald

This comment has been minimized.

Show comment
Hide comment
@raganwald

raganwald Oct 31, 2011

If my dreams came true, then:

$('#todos')
  .addClass('rad')
  .children()
    .addClass('tubular')
  .parent()
    .addClass('dudetastic’)

Would compile to something like:

_ref1 = $('#todos');
_ref1.addClass('rad')
_ref2 = ref1.children();
_ref2.addClass('tubular');
_ref3 = ref1.parent();
_ref3.addClass('dudetastic’);

The obvious optimization is to take advantage of the fact that some of these ’nodes’ are degenerate and do not have more than one child:

_ref = $('#todos');
_ref.addClass('rad’);
_ref.children().addClass('tubular');
_ref.parent().addClass('dudetastic’);

This is exactly what @TrevorBurnham is asking. Presuming we have degenerate node optimization, this code:

list
  .concat(other)
    .map((x) -> x * x)
      .filter((x) -> x % 2 is 0)
        .reverse()

Compiles to itself:

list
  .concat(other)
    .map(function (x) { return x * x; })
      .filter(function (x) { return x % 2 === 0; })
        .reverse();

raganwald commented Oct 31, 2011

If my dreams came true, then:

$('#todos')
  .addClass('rad')
  .children()
    .addClass('tubular')
  .parent()
    .addClass('dudetastic’)

Would compile to something like:

_ref1 = $('#todos');
_ref1.addClass('rad')
_ref2 = ref1.children();
_ref2.addClass('tubular');
_ref3 = ref1.parent();
_ref3.addClass('dudetastic’);

The obvious optimization is to take advantage of the fact that some of these ’nodes’ are degenerate and do not have more than one child:

_ref = $('#todos');
_ref.addClass('rad’);
_ref.children().addClass('tubular');
_ref.parent().addClass('dudetastic’);

This is exactly what @TrevorBurnham is asking. Presuming we have degenerate node optimization, this code:

list
  .concat(other)
    .map((x) -> x * x)
      .filter((x) -> x % 2 is 0)
        .reverse()

Compiles to itself:

list
  .concat(other)
    .map(function (x) { return x * x; })
      .filter(function (x) { return x % 2 === 0; })
        .reverse();
@davidchambers

This comment has been minimized.

Show comment
Hide comment
@davidchambers

davidchambers Oct 31, 2011

Contributor

Trevor's example:

$('#todos')
  .addClass('rad')
  .children()
    .addClass('tubular')
  .parent()
    .addClass('dudetastic')

This is both valid CoffeeScript and valid JavaScript. While I see a great deal of merit in the proposal to make whitespace significant in such cases, the fact that a piece of code can be valid in both languages but mean different things is a cause for concern.

Contributor

davidchambers commented Oct 31, 2011

Trevor's example:

$('#todos')
  .addClass('rad')
  .children()
    .addClass('tubular')
  .parent()
    .addClass('dudetastic')

This is both valid CoffeeScript and valid JavaScript. While I see a great deal of merit in the proposal to make whitespace significant in such cases, the fact that a piece of code can be valid in both languages but mean different things is a cause for concern.

@JulianBirch

This comment has been minimized.

Show comment
Hide comment
@JulianBirch

JulianBirch Oct 31, 2011

Personally, I don't have much of an issue with flat indentation in either of @raganwald's examples. I don't do indentation like that when programming in Clojure, and I don't believe anyone else does either. It's well understood that each line (form) of -> operates on the result of the last one. If the last function happens to return its own parameter, that's fine, but not a cause for a different indentation strategy. Take a look a clj-webdriver for an extended exercise in using -> as a chaining syntax.

To clarify, what I was trying to set out to do at the start was propose a syntax that made chaining APIs easier to use than CoffeeScript, not provide a way of doing chaining-like things on things that didn't already support them.

If you just wanted to simplify the case in which the chain always returned the same object, VB6's with statement would do the job:

with $ 'p.next'
    .addClass "ohmy"
    .show()

I imagine most JS programmers are pretty allergic to anything containing the word "with", however...

Oh, and if I wanted to extract variables from a require, I'd have written

{bar} = require 'foo'

In the first place. :)

JulianBirch commented Oct 31, 2011

Personally, I don't have much of an issue with flat indentation in either of @raganwald's examples. I don't do indentation like that when programming in Clojure, and I don't believe anyone else does either. It's well understood that each line (form) of -> operates on the result of the last one. If the last function happens to return its own parameter, that's fine, but not a cause for a different indentation strategy. Take a look a clj-webdriver for an extended exercise in using -> as a chaining syntax.

To clarify, what I was trying to set out to do at the start was propose a syntax that made chaining APIs easier to use than CoffeeScript, not provide a way of doing chaining-like things on things that didn't already support them.

If you just wanted to simplify the case in which the chain always returned the same object, VB6's with statement would do the job:

with $ 'p.next'
    .addClass "ohmy"
    .show()

I imagine most JS programmers are pretty allergic to anything containing the word "with", however...

Oh, and if I wanted to extract variables from a require, I'd have written

{bar} = require 'foo'

In the first place. :)

@yuchi

This comment has been minimized.

Show comment
Hide comment
@yuchi

yuchi Oct 31, 2011

Actually the concept behind with does not work well with standard chained code, while useful for other purposes.

# pseudo code
with $ 'div'
  .find 'span'
  .addClass 'span-element' # following `with` concept this should be applied to `$ 'div'`

should hypothetically yeld to:

var _ref = $('div');
_ref.find('span');
_ref.addClass('span-element');

and not:

var _ref = $('div');
_ref=_ref.find('span');
_ref=_ref.addClass('span-element');

yuchi commented Oct 31, 2011

Actually the concept behind with does not work well with standard chained code, while useful for other purposes.

# pseudo code
with $ 'div'
  .find 'span'
  .addClass 'span-element' # following `with` concept this should be applied to `$ 'div'`

should hypothetically yeld to:

var _ref = $('div');
_ref.find('span');
_ref.addClass('span-element');

and not:

var _ref = $('div');
_ref=_ref.find('span');
_ref=_ref.addClass('span-element');
@patrys

This comment has been minimized.

Show comment
Hide comment
@patrys

patrys Oct 31, 2011

What about:

with $ 'div'
  with @find 'span'
    @addClass 'span-element'

Not that it would make the code any shorter than JS.

patrys commented Oct 31, 2011

What about:

with $ 'div'
  with @find 'span'
    @addClass 'span-element'

Not that it would make the code any shorter than JS.

@raganwald

This comment has been minimized.

Show comment
Hide comment
@raganwald

raganwald Oct 31, 2011

I like @davidchambers’s observation and agree that we should be concerned that:

a = [1, [2]]
a
  .pop()
  .pop()

and:

a = [1, [2]]
a
  .pop()
    .pop()

Are both valid JS with identical semantics. If Coffeescript is to have different semantics, it ought to be a big win for developers.

Tangentially, I find it surprising that Coffeescript is a language with “significant whitespace” but both of the above are valid Coffeescript and both compile to exactly the same code. If they don’t compile to different JS, I would expect Coffeescript to tell me that one of the two is illegal.

One of the benefits of a significant whitespace language is to use whitespace to save on things like braces and end statements. The other is to enforce a single consistent indentation across all code. Allowing both forms discards this second benefit.

I’m just ruminating here, I am not agitating for change. I’m flattered that you folks found my suggestion interesting enough to take the time to consider the ramifications, and I look forward to seeing how chaining might evolve into Coffeescript.

raganwald commented Oct 31, 2011

I like @davidchambers’s observation and agree that we should be concerned that:

a = [1, [2]]
a
  .pop()
  .pop()

and:

a = [1, [2]]
a
  .pop()
    .pop()

Are both valid JS with identical semantics. If Coffeescript is to have different semantics, it ought to be a big win for developers.

Tangentially, I find it surprising that Coffeescript is a language with “significant whitespace” but both of the above are valid Coffeescript and both compile to exactly the same code. If they don’t compile to different JS, I would expect Coffeescript to tell me that one of the two is illegal.

One of the benefits of a significant whitespace language is to use whitespace to save on things like braces and end statements. The other is to enforce a single consistent indentation across all code. Allowing both forms discards this second benefit.

I’m just ruminating here, I am not agitating for change. I’m flattered that you folks found my suggestion interesting enough to take the time to consider the ramifications, and I look forward to seeing how chaining might evolve into Coffeescript.

@alexkg

This comment has been minimized.

Show comment
Hide comment
@alexkg

alexkg Nov 3, 2011

I like the raganwald era syntax.

Just wondering if it might support assignment:

Cuz = extends Bro, ( args... ) ->
  ...

::doStuff = ( args ) ->
  ...

.doThings = ->
  ...

Might go some way towards #1632, at least for class definition...

alexkg commented Nov 3, 2011

I like the raganwald era syntax.

Just wondering if it might support assignment:

Cuz = extends Bro, ( args... ) ->
  ...

::doStuff = ( args ) ->
  ...

.doThings = ->
  ...

Might go some way towards #1632, at least for class definition...

@quangv

This comment has been minimized.

Show comment
Hide comment
@quangv

quangv Nov 4, 2011

{bar} = require 'foo'

is the same as

bar = require('foo').bar

@JulianBirch cool didn't realize that, thanks. :)

quangv commented Nov 4, 2011

{bar} = require 'foo'

is the same as

bar = require('foo').bar

@JulianBirch cool didn't realize that, thanks. :)

@donabrams

This comment has been minimized.

Show comment
Hide comment
@donabrams

donabrams Aug 15, 2012

After playing with this a bit more I didn't find it very coffeescripty. So I found a nice keyword to use, send. Single line example:

app = require 'express' send createServer()

and multiline example:

$ "div" send
  chain "hide", "slow" send
    addClass "done"
    find "span" send
      addClass "done"
  chain "show", "slow" send
    removeClass "done"

Possible trailing if/unless syntax:

fratboy send(f)
  say 'another beer!' unless f.hasBeer
  when bar_is_closed if f.atBar()
    send
      removeClass 'atBar'
      goToFratHouse()
  getBuddies().each (b) ->
    b.doSomethingAndSayNoHomo f if drunk

and the troublesome map/reduce example

list.concat other
  send map (x) -> 
    x * x
  send filter (x) -> 
    x % 2 is 0
  send reverse()

donabrams commented Aug 15, 2012

After playing with this a bit more I didn't find it very coffeescripty. So I found a nice keyword to use, send. Single line example:

app = require 'express' send createServer()

and multiline example:

$ "div" send
  chain "hide", "slow" send
    addClass "done"
    find "span" send
      addClass "done"
  chain "show", "slow" send
    removeClass "done"

Possible trailing if/unless syntax:

fratboy send(f)
  say 'another beer!' unless f.hasBeer
  when bar_is_closed if f.atBar()
    send
      removeClass 'atBar'
      goToFratHouse()
  getBuddies().each (b) ->
    b.doSomethingAndSayNoHomo f if drunk

and the troublesome map/reduce example

list.concat other
  send map (x) -> 
    x * x
  send filter (x) -> 
    x % 2 is 0
  send reverse()
@nfour

This comment has been minimized.

Show comment
Hide comment
@nfour

nfour Oct 18, 2012

And what about handling expected function call scenarios like

parser = require('parser')(config, do('something')(something))

as

parser = require 'parser' config, do 'something' something

or

useStruct( buildStruct( parseStruct( '1=2&3=4' ) ) )

as

useStruct buildStruct parseStruct '1=2&3=4'

or

useStruct
        buildStruct
                parseStruct '1=2&3=4'

I originally expected the last example with function calls indented to work. Is there an easier way without parenthesis to do this, currently?

nfour commented Oct 18, 2012

And what about handling expected function call scenarios like

parser = require('parser')(config, do('something')(something))

as

parser = require 'parser' config, do 'something' something

or

useStruct( buildStruct( parseStruct( '1=2&3=4' ) ) )

as

useStruct buildStruct parseStruct '1=2&3=4'

or

useStruct
        buildStruct
                parseStruct '1=2&3=4'

I originally expected the last example with function calls indented to work. Is there an easier way without parenthesis to do this, currently?

@mcmire

This comment has been minimized.

Show comment
Hide comment
@mcmire

mcmire Oct 18, 2012

@TurtlePie Yup, you can add backslashes to the end of each line, thusly:

useStruct \
  buildStruct \
    parseStruct \
     '1=2&3=4'

mcmire commented Oct 18, 2012

@TurtlePie Yup, you can add backslashes to the end of each line, thusly:

useStruct \
  buildStruct \
    parseStruct \
     '1=2&3=4'
@jiyinyiyong

This comment has been minimized.

Show comment
Hide comment
@jiyinyiyong

jiyinyiyong Mar 10, 2013

Contributor

Want it!

var rework = require('rework');

var css = rework('string of css here')
  .vendors(['-webkit-', '-moz-'])
  .use(rework.prefixValue('linear-gradient'))
  .use(rework.prefixValue('radial-gradient'))
  .use(rework.prefixValue('transform'))
  .use(rework.vars())
  .use(rework.colors())
  .toString()

I do want to write it like this as LiveScript does:

rework = require 'rework'

css = rework('string of css here')
  .vendors ['-webkit-', '-moz-']
  .use rework.prefixValue('linear-gradient')
  .use rework.prefixValue('radial-gradient')
  .use rework.prefixValue('transform')
  .use rework.vars()
  .use rework.colors()
  .toString()

Hacking it with @ maybe easy... but... Isn't it nicer to write directly in CoffeeScript?

Contributor

jiyinyiyong commented Mar 10, 2013

Want it!

var rework = require('rework');

var css = rework('string of css here')
  .vendors(['-webkit-', '-moz-'])
  .use(rework.prefixValue('linear-gradient'))
  .use(rework.prefixValue('radial-gradient'))
  .use(rework.prefixValue('transform'))
  .use(rework.vars())
  .use(rework.colors())
  .toString()

I do want to write it like this as LiveScript does:

rework = require 'rework'

css = rework('string of css here')
  .vendors ['-webkit-', '-moz-']
  .use rework.prefixValue('linear-gradient')
  .use rework.prefixValue('radial-gradient')
  .use rework.prefixValue('transform')
  .use rework.vars()
  .use rework.colors()
  .toString()

Hacking it with @ maybe easy... but... Isn't it nicer to write directly in CoffeeScript?

@vendethiel

This comment has been minimized.

Show comment
Hide comment
@vendethiel

vendethiel Mar 10, 2013

Collaborator
rework = require 'rework'

css = rework 'string of css here'
  .vendors <[-webkit- -moz-]>
  .use rework.prefixValue 'linear-gradient'
  .use rework.prefixValue 'radial-gradient'
  .use rework.prefixValue 'transform'
  .use rework.vars!
  .use rework.colors!
  .toString!

and that's only coco

Collaborator

vendethiel commented Mar 10, 2013

rework = require 'rework'

css = rework 'string of css here'
  .vendors <[-webkit- -moz-]>
  .use rework.prefixValue 'linear-gradient'
  .use rework.prefixValue 'radial-gradient'
  .use rework.prefixValue 'transform'
  .use rework.vars!
  .use rework.colors!
  .toString!

and that's only coco

@jiyinyiyong

This comment has been minimized.

Show comment
Hide comment
@jiyinyiyong

jiyinyiyong Mar 10, 2013

Contributor

@Nami-Doc And LiveScript is a fork of Coco.

Contributor

jiyinyiyong commented Mar 10, 2013

@Nami-Doc And LiveScript is a fork of Coco.

@vendethiel

This comment has been minimized.

Show comment
Hide comment
@vendethiel

vendethiel Mar 10, 2013

Collaborator

@jiyinyiyong hence the "only", just meant that ls had even more sugar (ie require!).

Collaborator

vendethiel commented Mar 10, 2013

@jiyinyiyong hence the "only", just meant that ls had even more sugar (ie require!).

@jiyinyiyong

This comment has been minimized.

Show comment
Hide comment
@jiyinyiyong

jiyinyiyong Mar 10, 2013

Contributor

@Nami-Doc ..How I hope there is a Chinese translation... I think now I get it.
Coco's syntax ifs just fine in solving this.

Contributor

jiyinyiyong commented Mar 10, 2013

@Nami-Doc ..How I hope there is a Chinese translation... I think now I get it.
Coco's syntax ifs just fine in solving this.

@rev22

This comment has been minimized.

Show comment
Hide comment
@rev22

rev22 Jul 1, 2013

I think the clearest solution is chaining method calls on the same indentation level:

    new Thing
    .add foo ...
    .add bar ...

equivalent to:

    (new Thing).add(foo ...).add(bar ...)

rev22 commented Jul 1, 2013

I think the clearest solution is chaining method calls on the same indentation level:

    new Thing
    .add foo ...
    .add bar ...

equivalent to:

    (new Thing).add(foo ...).add(bar ...)
@00dani

This comment has been minimized.

Show comment
Hide comment
@00dani

00dani Aug 13, 2013

I think Coco's approach of:

firstCall argument, argument
  .method arg
  .otherMethod otherArg

is sufficient for most libraries that use chaining, as well as (I think) quite intuitively looking like what it means:

firstCall(argument, argument).method(arg).otherMethod(otherArg);

00dani commented Aug 13, 2013

I think Coco's approach of:

firstCall argument, argument
  .method arg
  .otherMethod otherArg

is sufficient for most libraries that use chaining, as well as (I think) quite intuitively looking like what it means:

firstCall(argument, argument).method(arg).otherMethod(otherArg);
@erisdev

This comment has been minimized.

Show comment
Hide comment
@erisdev

erisdev Aug 13, 2013

@00Davo with the indentation as it is, that looks to me more like

var x = firstCall(argument, argument);
x.method(arg);
x.otherMethod(otherArg);

which is arguably more useful since it works identically for common use cases like jQuery, Q, &c. and the behaviour is even more useful for cases where you might want to call multiple methods on the result of one function call.

erisdev commented Aug 13, 2013

@00Davo with the indentation as it is, that looks to me more like

var x = firstCall(argument, argument);
x.method(arg);
x.otherMethod(otherArg);

which is arguably more useful since it works identically for common use cases like jQuery, Q, &c. and the behaviour is even more useful for cases where you might want to call multiple methods on the result of one function call.

@00dani

This comment has been minimized.

Show comment
Hide comment
@00dani

00dani Aug 13, 2013

@erisdiscord

I'd personally think unindented chains

firstCall arg, arg
.method arg
.otherMethod arg

would have that meaning, with indented chains having the meaning I suggested.

So perhaps it's not quite as clear as I thought. Hmm.

00dani commented Aug 13, 2013

@erisdiscord

I'd personally think unindented chains

firstCall arg, arg
.method arg
.otherMethod arg

would have that meaning, with indented chains having the meaning I suggested.

So perhaps it's not quite as clear as I thought. Hmm.

@erisdev

This comment has been minimized.

Show comment
Hide comment
@erisdev

erisdev Aug 13, 2013

@00Davo see, to me, if you want meaningful indentation in method chaining, it'd have to look like this, which can get messy with long chains:

firstCall arg, arg
  .method arg
    .otherMethod arg

I think this is part of the reason it hasn't happened yet. (I'm going to bed now; it's 2am)

erisdev commented Aug 13, 2013

@00Davo see, to me, if you want meaningful indentation in method chaining, it'd have to look like this, which can get messy with long chains:

firstCall arg, arg
  .method arg
    .otherMethod arg

I think this is part of the reason it hasn't happened yet. (I'm going to bed now; it's 2am)

@00dani

This comment has been minimized.

Show comment
Hide comment
@00dani

00dani Aug 13, 2013

Well, making "same-level" indented chains work like Smalltalk cascading actually doesn't mean they'll work right with Q or with jQuery. For instance:

# jQuery
$ 'selector'
  .filter predicate
  .hide()
# Q
readFile path
  .then YAML.parse
  .then processReadYamlObject
  .nodeify callback

Having the chaining syntax compile to equivalently chained methods in JavaScript is, I think, preferable for reasons like those. (Also, libraries like jQuery are explicitly built to be chainable. JavaScript itself doesn't have cascading syntax, so fewer libs are built with that use in mind.)

Hmm. What about actually using ; to indicate a cascade, as in Smalltalk? First-blush try at doing that:

readFile path
  .then YAML.parse
  .then processReadYamlObject
  .nodeify callback
readFile path
  ;then YAML.parse
  ;then processReadYamlObject
  ;nodeify callback

Compiles to:

readFile(path).then(YAML.parse).then(processReadYamlObject).nodeify(callback);
_ref = readFile(path);
_ref.then(YAML.parse);
_ref.then(processReadYamlObject);
_ref.nodeify(callback);

00dani commented Aug 13, 2013

Well, making "same-level" indented chains work like Smalltalk cascading actually doesn't mean they'll work right with Q or with jQuery. For instance:

# jQuery
$ 'selector'
  .filter predicate
  .hide()
# Q
readFile path
  .then YAML.parse
  .then processReadYamlObject
  .nodeify callback

Having the chaining syntax compile to equivalently chained methods in JavaScript is, I think, preferable for reasons like those. (Also, libraries like jQuery are explicitly built to be chainable. JavaScript itself doesn't have cascading syntax, so fewer libs are built with that use in mind.)

Hmm. What about actually using ; to indicate a cascade, as in Smalltalk? First-blush try at doing that:

readFile path
  .then YAML.parse
  .then processReadYamlObject
  .nodeify callback
readFile path
  ;then YAML.parse
  ;then processReadYamlObject
  ;nodeify callback

Compiles to:

readFile(path).then(YAML.parse).then(processReadYamlObject).nodeify(callback);
_ref = readFile(path);
_ref.then(YAML.parse);
_ref.then(processReadYamlObject);
_ref.nodeify(callback);
@Soviut

This comment has been minimized.

Show comment
Hide comment
@Soviut

Soviut Sep 5, 2013

in LESS they use & to indicate continuation of a selector since the dot is already used for selecting css classes, for example:

a.button {
    &:hover { }
}

Compiles to:

a.button:hover {
}

I propose we replace the dot syntax for chained calls with & and consider it a metaphor for "and then, and then, and then".

$ 'a.button'
    &css 'color', 'red'
    &show 'slow'

(It literally would be &then when used with a promise).

readFile path
    &then YAML.parse()
    &then processReadYamlObject()

The only other similar syntax I think makes sense is the ^ which could be a metaphore for "I belong to whatever is above me at this indentation level". In the same case, it would replace the dot at the beginning of the lines.

$ 'a.button'
    ^css 'color', 'red'
    ^show 'slow'

Soviut commented Sep 5, 2013

in LESS they use & to indicate continuation of a selector since the dot is already used for selecting css classes, for example:

a.button {
    &:hover { }
}

Compiles to:

a.button:hover {
}

I propose we replace the dot syntax for chained calls with & and consider it a metaphor for "and then, and then, and then".

$ 'a.button'
    &css 'color', 'red'
    &show 'slow'

(It literally would be &then when used with a promise).

readFile path
    &then YAML.parse()
    &then processReadYamlObject()

The only other similar syntax I think makes sense is the ^ which could be a metaphore for "I belong to whatever is above me at this indentation level". In the same case, it would replace the dot at the beginning of the lines.

$ 'a.button'
    ^css 'color', 'red'
    ^show 'slow'
@danschumann

This comment has been minimized.

Show comment
Hide comment
@danschumann

danschumann Sep 19, 2013


PROPOSAL: Periods(following a space) terminate the most recent parenthesis.


Parenthesis, or the lack thereof, are one of the best things about coffeescript.
Parenthesis in any situation inclines the programmer to add code in 2 places, as opposed to 1.

changing
(figure a)

$('.selector').
   .append 'appends 1'

to become
(figure b)

 $('.selector').
   .append('appends 1')
   .append 'appends 2'

In figure b, two lines are identical but look differently. You must replace a space with a (, and then insert at the end.

Proposed syntax:
(figure c)

$('.selector').
   .append 'appends 1' .
   .append 'appends 2'

figure c has both lines looking similarly. Only a single Insertion is needed with no replacements.
Example results: fewer keystrokes producing more symmetrical, readable code


CURRENTLY HOW IT WORKS AND HOW IT COULD WORK( The poor example)

Keep as much the same as possible. Currently, while it is smelly, figure 1 is possible
figure 1:

$('.selector').append $('<div>')
  .append('div contents')
  .click ->
    console.log 'div function'

Keeping figure 1 and adding functionality that would not break old code is essential to not pissing off the user base.

figure 2: (Currently produces syntax errors.)

$ '.selector' ..append $ '<div>' .
  .append 'div contents' .
  .click ->
    console.log 'div function'

Ideally, both compile to
figure 3:

$('.selector').append(
  $('<div>')
    .append('div contents')
    .click(function(){ return console.log('div function'); })
)

danschumann commented Sep 19, 2013


PROPOSAL: Periods(following a space) terminate the most recent parenthesis.


Parenthesis, or the lack thereof, are one of the best things about coffeescript.
Parenthesis in any situation inclines the programmer to add code in 2 places, as opposed to 1.

changing
(figure a)

$('.selector').
   .append 'appends 1'

to become
(figure b)

 $('.selector').
   .append('appends 1')
   .append 'appends 2'

In figure b, two lines are identical but look differently. You must replace a space with a (, and then insert at the end.

Proposed syntax:
(figure c)

$('.selector').
   .append 'appends 1' .
   .append 'appends 2'

figure c has both lines looking similarly. Only a single Insertion is needed with no replacements.
Example results: fewer keystrokes producing more symmetrical, readable code


CURRENTLY HOW IT WORKS AND HOW IT COULD WORK( The poor example)

Keep as much the same as possible. Currently, while it is smelly, figure 1 is possible
figure 1:

$('.selector').append $('<div>')
  .append('div contents')
  .click ->
    console.log 'div function'

Keeping figure 1 and adding functionality that would not break old code is essential to not pissing off the user base.

figure 2: (Currently produces syntax errors.)

$ '.selector' ..append $ '<div>' .
  .append 'div contents' .
  .click ->
    console.log 'div function'

Ideally, both compile to
figure 3:

$('.selector').append(
  $('<div>')
    .append('div contents')
    .click(function(){ return console.log('div function'); })
)
@dashed

This comment has been minimized.

Show comment
Hide comment
@dashed

dashed Dec 4, 2013

Shouldn't this issue be closed because of 563f14b?

dashed commented Dec 4, 2013

Shouldn't this issue be closed because of 563f14b?

@vendethiel

This comment has been minimized.

Show comment
Hide comment
@vendethiel

vendethiel Dec 4, 2013

Collaborator

No, this discussion is about cascades.

Collaborator

vendethiel commented Dec 4, 2013

No, this discussion is about cascades.

@xixixao

This comment has been minimized.

Show comment
Hide comment
@xixixao

xixixao Dec 6, 2013

Contributor

We should decide whether "we got what we wanted".

Contributor

xixixao commented Dec 6, 2013

We should decide whether "we got what we wanted".

@vendethiel

This comment has been minimized.

Show comment
Hide comment
@vendethiel

vendethiel Dec 6, 2013

Collaborator

I'd say yes, for now at least :).

Collaborator

vendethiel commented Dec 6, 2013

I'd say yes, for now at least :).

@JulianBirch

This comment has been minimized.

Show comment
Hide comment
@JulianBirch

JulianBirch Jan 27, 2014

As OP, I'd say it looks closed.

JulianBirch commented Jan 27, 2014

As OP, I'd say it looks closed.

@blixt

This comment has been minimized.

Show comment
Hide comment
@blixt

blixt Sep 20, 2014

The other issues mentioning cascades seem to have been closed due to being a duplicate for this. So I guess this task now has to be considered the canonical request for cascades, rather than just simply chaining as it was originally intended for (or we create a new issue for cascading and don't close it as a duplicate of this).

Here's an example of cascading in Dart: http://news.dartlang.org/2012/02/method-cascades-in-dart-posted-by-gilad.html

I think this would be really helpful because some APIs simply don't allow for the existing chaining, Promise being the most obvious one:

myPromise
  .then ->
    someOtherPromiseOp()
  .catch ->
    console.error 'oh no, this error might have happened in someOtherPromiseOp, not in myPromise!'

This would solve the above:

myPromise
  ..then ->
    someOtherPromiseOp()
  ..catch ->
    console.error 'we know that myPromise threw, not someOtherPromiseOp'

blixt commented Sep 20, 2014

The other issues mentioning cascades seem to have been closed due to being a duplicate for this. So I guess this task now has to be considered the canonical request for cascades, rather than just simply chaining as it was originally intended for (or we create a new issue for cascading and don't close it as a duplicate of this).

Here's an example of cascading in Dart: http://news.dartlang.org/2012/02/method-cascades-in-dart-posted-by-gilad.html

I think this would be really helpful because some APIs simply don't allow for the existing chaining, Promise being the most obvious one:

myPromise
  .then ->
    someOtherPromiseOp()
  .catch ->
    console.error 'oh no, this error might have happened in someOtherPromiseOp, not in myPromise!'

This would solve the above:

myPromise
  ..then ->
    someOtherPromiseOp()
  ..catch ->
    console.error 'we know that myPromise threw, not someOtherPromiseOp'
@usergenic

This comment has been minimized.

Show comment
Hide comment
@usergenic

usergenic Jul 23, 2015

Nested cascades in CoffeeScript would be a snap with the .. syntax, but I could see a single . prefix syntax could work along with the indent to promote the same idea.

document.queryAll "#mypanel TABLE.firstCol"
  .classes.remove "firstCol"
    .style
      console.log("current background is: ", .background)
      .background = "red"
      .border = "2px solid black"
  .nodes.add(new Element.html "<spam>This cell is now red</span>")

usergenic commented Jul 23, 2015

Nested cascades in CoffeeScript would be a snap with the .. syntax, but I could see a single . prefix syntax could work along with the indent to promote the same idea.

document.queryAll "#mypanel TABLE.firstCol"
  .classes.remove "firstCol"
    .style
      console.log("current background is: ", .background)
      .background = "red"
      .border = "2px solid black"
  .nodes.add(new Element.html "<spam>This cell is now red</span>")
@1j01

This comment has been minimized.

Show comment
Hide comment
@1j01

1j01 Jan 5, 2016

@brendan .classes.remove(...).style? Also, new Element.html means new (Element.html)
I guess you're used to dart with .classes.remove etc.

document.querySelector "#mypanel table tr"
  .classList.remove "first-row"
  .style
    console.log "current background is: ", .background
    .background = "red"
    .border = "2px solid black"
  .appendChild new Element
    .innerHTML = "<span>This cell is now red</span>"

I'm not sure how I feel about this. The . after console.log is ambiguous unless you will no longer be able to do . on a new line without indenting as you can now.

At any rate, I can't disagree with notion that "writing functions to return a certain thing just to cater to how you like to write programs is hacking around a missing language feature"

1j01 commented Jan 5, 2016

@brendan .classes.remove(...).style? Also, new Element.html means new (Element.html)
I guess you're used to dart with .classes.remove etc.

document.querySelector "#mypanel table tr"
  .classList.remove "first-row"
  .style
    console.log "current background is: ", .background
    .background = "red"
    .border = "2px solid black"
  .appendChild new Element
    .innerHTML = "<span>This cell is now red</span>"

I'm not sure how I feel about this. The . after console.log is ambiguous unless you will no longer be able to do . on a new line without indenting as you can now.

At any rate, I can't disagree with notion that "writing functions to return a certain thing just to cater to how you like to write programs is hacking around a missing language feature"

@usergenic

This comment has been minimized.

Show comment
Hide comment
@usergenic

usergenic Jan 5, 2016

Didn't realize that CoffeeScript would interpret

x
  .y
  .z

as x.y.z.

I guess I'd amend the example you posted to be:

document.querySelector "#mypanel table tr"
  ..classList.remove "first-row"  # querySelector result's classList
  ..style # querySelector result's style
    console.log "current background is: ", ..background # style's background
    ..background = "red" # style's background
    ..border = "2px solid black" # style's border
  ..appendChild new Element # querySelector result's appendChild
    ..innerHTML = "<span>This cell is now red</span>" # appendChild result's innerHTML

usergenic commented Jan 5, 2016

Didn't realize that CoffeeScript would interpret

x
  .y
  .z

as x.y.z.

I guess I'd amend the example you posted to be:

document.querySelector "#mypanel table tr"
  ..classList.remove "first-row"  # querySelector result's classList
  ..style # querySelector result's style
    console.log "current background is: ", ..background # style's background
    ..background = "red" # style's background
    ..border = "2px solid black" # style's border
  ..appendChild new Element # querySelector result's appendChild
    ..innerHTML = "<span>This cell is now red</span>" # appendChild result's innerHTML
@stugol

This comment has been minimized.

Show comment
Hide comment
@stugol

stugol Mar 14, 2016

Is this likely to be implemented soon?

stugol commented Mar 14, 2016

Is this likely to be implemented soon?

@GeoffreyBooth

This comment has been minimized.

Show comment
Hide comment
@GeoffreyBooth

GeoffreyBooth Apr 26, 2017

Collaborator

I’m not sure what is even still being debated on this thread. If someone wants to improve chaining from where it stands in v2, please open a new issue with a specific proposal.

Collaborator

GeoffreyBooth commented Apr 26, 2017

I’m not sure what is even still being debated on this thread. If someone wants to improve chaining from where it stands in v2, please open a new issue with a specific proposal.

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