Improve chaining syntax #1495

Open
JulianBirch opened this Issue Jul 8, 2011 · 179 comments

Projects

None yet
@JulianBirch

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"
Collaborator

I think this is a duplicate of #1251

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")
Collaborator
satyr commented Jul 9, 2011

Also see #1407.

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

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

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

arbales commented Jul 16, 2011

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

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.

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

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

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

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.

$ "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 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 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 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)

$('#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 :)

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

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

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.

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?

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();
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.

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

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 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 commented Nov 4, 2011
{bar} = require 'foo'

is the same as

bar = require('foo').bar

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

showell commented Nov 24, 2011

Does this proposal boil down to this?:

"Any statement starting with a dot implicitly operates on the value from its indentation parent."

If so, I'm -1. In cases where the indentation parent is far away, I think this will cause readability concerns. Also, I think many readers would expect the implicit object to come from the prior line.

I've only skimmed this thread, but it seems like nobody has put forward a concise explanation of what the rule would be. I understand that it's still a work in progress.

erisdev commented Nov 24, 2011

@showell one could also argue against the indented if or for syntax using that same argument. It's not up to CoffeeScript to prevent bad programmers writing hard to read code.

showell commented Nov 24, 2011

@erisdiscord Are you saying that CoffeeScript's design decisions don't impact code readability? Nobody with any sense of nuance would suggest that my arguments apply to if/for. That's just silly.

showell commented Nov 24, 2011

The code below could have multiple interpretations:

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

The endPath() fragment could be acting on "brush", or it could be acting on the value returned from "ellipse". Obviously, only one interpretation would be valid under this proposal, but I think CS newcomers could reasonably expect either interpretation.

I'm all in favor of constructs that lead to terseness, but not if they introduce ambiguity.

erisdev commented Nov 24, 2011

@showell I'm not saying CoffeeScript's design decisions don't impact code readability, but I think your argument is weak unless I'm misunderstanding it.

Vagueness and contrivance aside, how is either of these examples any harder to read than the other? In both cases, we end up with statements far from their indentation parent and readability is affected about the same IMO.

$ 'article'
  .applySomeStyle()
  .find 'h1'
    .makeNeatHeading()
    .addToTOC()
  .find 'p'
    .extractPullQuotes()

if entity?
  entity.updatePosition()
  unless 0 <= entity.x < width
    entity.x += width
    doSomething()
  unless 0 <= entity.y < height
    entity.y += height
    doSomething()

I think my sense of nuance is in working order, thanks.

erisdev commented Nov 24, 2011

@showell .endPath() could apply to the return value of ellipse, sure, but under this proposal it wouldn't. There's no ambiguity because the meaning is well defined: a statement beginning with a dot applies to the result of its indentation parent. I would expect someone to learn the basics of the language if they intend to maintain code written in it.

This proposal is consistent with implicit object literals, BTW, where there's no confusion about what object andSoOn belongs to:

parentObject:
  childA:
    someGrandChild
    anotherGrandChild
  childB:
    yetAnotherGrandChild
    andSoOn

But this argument isn't the same as arguing that the distance from its indentation parent would affect readability, which is the point that I was addressing.

showell commented Nov 24, 2011

@erisdiscord Yep, I'm making two different arguments. My first argument is that .endPath applies to an object that is five lines above the statement, which means the reader has to keep more context in his head to understand what should be dead-simple code. My second argument is that the rules of the language wouldn't be obvious to a newcomer.

erisdev commented Nov 24, 2011

@showell I think we have a fundamental disagreement on the issue, so I'm gonna let this be my last comment on the matter. I don't think you're wrong per se, but I don't think you're right either. ;D

It's a potentially very useful bit of syntax sugar that definitely has potential for abuse, but I feel like the potential for convenience outweighs the potential for bad code.

Sorry for repeatedly editing my comments, meanwhile; I just keep noticing things that I left out or could have phrased better. I think I'm happy with what I've written now. C:

showell commented Nov 24, 2011

@erisdiscord asks how these snippets compare in terms of readability:

$ 'article'
  .applySomeStyle()
  .find 'h1'
    .makeNeatHeading()
    .addToTOC()
  .find 'p'
    .extractPullQuotes()

if entity?
  entity.updatePosition()
  unless 0 <= entity.x < width
    entity.x += width
    doSomething()
  unless 0 <= entity.y < height
    entity.y += height
    doSomething()

Vagueness and contrivance aside, the second example is more readable, because every individual statement in the second example explicitly refers to all the objects it's invoking/referencing (e.g. "entity", "width", "height", "doSomething"). I'm not oblivious to the fact that you still need to read up in the code to know whether the statement even executes, but that doesn't undermine my argument.

showell commented Nov 24, 2011

@erisdiscord I think your examples will help clarify the debate. Obviously, it's ultimately a judgment call, as I can't see how there's any clear "right" or "wrong" answer. No prob on the edits, I think my answers still make sense in context.

Collaborator

@showell has made some very convincing arguments. I agree with everything he's said. -1.

Collaborator

I agree that the drawbacks are real, but I also feel that they're outweighed by the benefits. I love the way that this proposal reduces both repetition and, in many cases, temporary variable declarations. Any surprising behaviors that result should be easy to diagnose by compiling to JS.

"Any statement starting with a dot implicitly operates on the value from its indentation parent.”

Yes. This code:

$ 'article'
  .applySomeStyle()
  .find 'h1'
    .makeNeatHeading()
    .addToTOC()
  .find 'p'
    .extractPullQuotes()

Is already legal CS, we are simply discussing what it means, not whether it should be legal. As for it being “far from its indentation parent,” that’s the beauty of indentation. It’s ‘far' in chars and lines, but visually, it’s easy to see.

The structure of the indentation exactly maps to the structure of the expression, and the eye easily finds the indentation parent at a glance. That’s exactly why people indent outlines.

erisdev commented Nov 28, 2011

Yes, I suppose I would like to add one last thing on the subject of indentation: if you use tabs and your editor has a setting that shows them, it's incredibly easy to trace back to the parent.

visible tabs

It might even be possible to alter the CoffeeScript mode for TextMate or any number of other editors to stripe your indentation, whether spaces or tabs, provided the syntax highlighter can know what constitutes a single level of indentation.

poswald commented Nov 29, 2011

I like the ragenwald proposal for this. I think the strongest argument is the comment he made on hacker news but didn't paste here:

http://news.ycombinator.com/item?id=3175028

My suggestion is that "chaining" method calls is a syntax issue and not a function issue, and 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.
pop() is a great example. Why shouldn't pop return the receiver? The first example makes sense, you ought to be able to chain calls to pop(). Then again, why shouldn't pop() return what it pops? You ought to be able to pop something and use it, just like the second example.

Baking chaining into what functions return forces the function author to choose On behalf of the function user. I do not blame people for doing this in a language lacking thenproper syntax, but given how much people do this, it seems worth considering for people writing new langauges or new syntaxes for existing languages.

I like that his proposal allows the caller to decide if they want the object (cascade) or the returned value from the function (chain) for the next line of code. This frees the function to return a value instead of the object.

donpark commented Nov 30, 2011

+1 @raganwald's proposal. I'm not perturbed by semantic difference from javascript. CoffeeScript is not javascript anyway.

mcmire commented Nov 30, 2011

+1 @raganwald's proposal as well. It's true that it's different from the current indentation behavior but then again, a lot of things in CoffeeScript are different than JavaScript. I rarely chain methods together but when I do, the methods in the chain already return the object in question (since otherwise chaining would be impossible), and so this:

foo
  .bar()
  .baz()

and this:

foo
  .bar()
    .baz()

would behave exactly the same and not be surprising to fellow developers. So the only case in which this new whitespace feature would be used, probably, would be when chaining jQuery selector methods, and there I can definitely see the use case for it.

showell commented Nov 30, 2011

FWIW the idea of an implicit object receivers being linked to the parent of the block has precedence in Visual Basic:

With testObject
    .Height = 100
    .Text = "Hello, World"
    .ForeColor = System.Drawing.Color.Green
    .Font = New System.Drawing.Font(.Font, _
        System.Drawing.FontStyle.Bold)
End With

My main problem with VB isn't the clumsy With/End With. I wouldn't even like VB if it were indentation-based:

testObject
    .Height = 100
    .Text = "Hello, World"
    .ForeColor = System.Drawing.Color.Green
    .Font = New System.Drawing.Font(.Font, _
        System.Drawing.FontStyle.Bold)

I just don't like implicit receivers. If you need to pop three items off an array, just keep it simple:

array.pop()
array.pop()
array.pop()

The syntactic repetition mirrors the semantic repetition.

It seems like there are a few use cases for the fluent style:

  1. You are setting a bunch of attributes on a DOM element. IMHO that should be the job of your stylesheets. Set the class within CS, then specify all the particulars of that style within CSS.
  2. You are repeating the same operation on object multiple times. IMHO that is the job of a loop.
  3. You are setting a bunch of properties on an object. IMHO that is either a symptom of a heavy language like Java or overly coarse objects.
Collaborator
satyr commented Nov 30, 2011

So with @raganwald's proposal, https://github.com/jashkenas/coffee-script/blob/1.1.3/examples/underscore.coffee#L604 would have to be

str.replace(/\r/g, '\\r')
     .replace(/\n/g, '\\n')
       .replace(/\t/g, '\\t')

?

@satyr: If those methods chain rather than cascade, yes.

Or this would work

str.
  replace(/\r/g, '\\r').
  replace(/\n/g, '\\n').
  replace(/\t/g, '\\t')

Trailing dots behaviour would be unchanged by the proposal.

Owner

Just to keep tabs on what TC39's @dherman is doing with this:

https://gist.github.com/1414956

quangv commented Dec 1, 2011

who's confused? Raise their hand...

quangv commented Dec 2, 2011

@alessioalex +1

oh “monocle-mustache” will kill chaining? Boo... I thought we wanted to improve chaining... not kill it ....

:/ seems kinda crazy, unless CoffeeScript have a different syntax for chaining... but that might be confusing...

erisdev commented Dec 2, 2011

@quangv @alessioalex it's not gonna kill chaining. As the proposal stands, you'll still be able to chain methods on a single line or by using indentation.

anObject.foo().bar().baz()

anObject
  .foo()
    .bar()
      .baz()

It's also very likely that ending a line with the dot operator will still continue that line as before rather than triggering the new cascading syntax.

anObject.
  foo().
  bar().
  baz()

Mind, I don't recommend writing it this way if this proposal is adopted because it could be confusing, but eh. C:

Of course, if the methods foo, bar and baz are just returning this anyway, as is typically the case with jQuery, then you can continue to write it the same way as long as you're mindful of indentation.

Collaborator

I don't want to see code like this:

v = obj
  .foo().
  bar.
  baz()
  .qux

Ever. I would lose my mind. It should be exactly equivalent to v = obj.foo().bar.baz().qux. Assuming I understand this proposal fully, I would have to figure out that the programmer meant obj.foo().bar.baz(); v = obj.qux, which would be better if it was just written like that in the first place.

edit: typo

Since nobody else has asked, an open question is, “With @raganwald’s Significant Whitespace suggestion, what does the following return?”

(...)
  .bar()
  .blitz()

Is it equivalent to:

(function () {
  var _ref1 = (...);
  _ref1.foo();
  return _ref1.bar();
})()

Or does it have Kestrel/_.tap semantics:

(function () {
  var _ref1 = (...);
  _ref1.foo();
  _ref1.bar();
  return _ref1;
})()

The second interpretation is nice for initialization code, very similar to object initializer blocks in ActiveRecord:

myNewThingummy = new Thingummy(...)
  .doSomeSetup()
  .someProperty = someValue
  .anotherProperty = anotherValue
  .finishSettingUp()

Like it? Hate it?

Collaborator

@raganwald: As you could probably tell from my above comment, I was assuming the first interpretation. And I would probably prefer that, though it's irrelevant since I still haven't bought into the syntax yet.

@michaelficarra, I would be bewildered, perplexed, and discombobulated by code such as:

v = obj
  .foo().
  bar.
   baz()
  .qux

However, now that you mention it, if I want to write:

v = obj
  .foo()
    .bar
      .baz()
        .qux

And mean v =obj.foo().bar.baz.qux(), then the really significant indentation thing should not have Kestrel/_.tap semantics, otherwise it would resolve to obj.

mcmire commented Dec 4, 2011

@raganwald, that is a good point. I don't know what people would want... probably more often that not people would not want tap semantics, but I think there is a case for it. It sounds your proposal is something that would need to be tested out for a while to see if it really works in the wild.

quangv commented Dec 5, 2011

anyway to make

x 'cat' .dog

=

x('cat').dog

?

just wanted to make sure...


update: @michaelficarra thanks.

Collaborator

@quangv: see #1407

I like @raganwald's proposal, I really do. The idea that method invocations respect indentation the same way that hashtables do is rather elegant. (My personal answer to @raganwald's question is that the latter behaviour is probably the sensible thing to do.)

However, more generally, I'd regard the motivation behind the proposal as being twofold (YMMV)

  • to enable a more point-free style than is currently possible
  • to reduce the number of times parens are required

I'm rather concerned that the underscore list comprehension example of @raganwald's looks rather inelegant. Maybe we need a way of specifying that we should be using the value of the previous line rather than the previous indentation.

To rewrite one of @raganald's examples:

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

could become

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

(Pipe was chosen to indicate "pipelining", but the exact syntax doesn't matter that much to me.)

yuchi commented Dec 5, 2011

Super small idea on @JulianBirch pipe proposal: using : instead of pipes, it resembles the dot notation but it's vertically continued.

list
  :sort comparable
  :reverse()
  :push 'some', 'other', 'element'
alexkg commented Dec 5, 2011

The more I think about it, the more I like it.

I think the @raganwald proposal is cleaner than any of the alternatives (even mustache oriented). It even allows accessors like :: to modify prototype properties.

It seems a little unclear when used with functions:

Thing = ->
    @createdAt = new Date()

    ::toString -> "I am a thing."

But maybe we can write instead:

Thing = ->
    @createdAt = new Date()

::toString -> "I am a thing."

I.e. no indentation necessary for cascading access at root level.

A suggestion for handling return value:

Suppose the default is tap semantics, in this case root:

root
    .foo
        .bar
            .baz

To return an intermediate value, add return as per cascade, in this case, the object returned by foo:

root
    .foo
        .bar
            .baz
        return

I realise this is a little loose, just a suggestion.

Anyway, really like this proposal, hope it goes through more-or-less as-is.

quangv commented Dec 5, 2011

@raganwald +1, I like it.

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

These 3 still relevant?

@alexkg:

It seems a little unclear when used with functions:

Thing = ->
    @createdAt = new Date()

    ::toString -> "I am a thing.”

I agree this is a little unclear. That being said, with my proposal it is not ambiguous. If we adopt this reasoning:

foo
.bar # <- Never refers to “foo,” it either refers to a parent or it’s a syntax error

Then we know that the following is unambiguous:

foo = ->
   gronk
   .bar

It is always:

foo = ( () -> gronk ).bar

I agree it is unsightly, I would probably find another way to express my program. A similar thing happens with

foo = bar.map (x) ->
    gronk(x * x)
    .reduce (a, b) ->
        a + grundle(b)

Which I would rewrite as:

foo = bar
    .map (x) -> gronk(x * x)
        .reduce (a, b) ->  a + grundle(b)

I suspect that chaining multi-line functions looks better with the current lack of significant whitespace, but chaining and cascading one-line method invocations looks better with my proposal.

@JulianBirch:

Using a “pipe” or | as an infix to achieve Thrush semantics is a great idea, and I think it has been seen in the wild in other languages that permit operator overriding. I also seem to remember the possibility that it has been used for function composition, as in:

foo = (x) ->
    # ...
bar = (x) ->
    # ...
fubar = foo | bar

Maybe piping functions deserves a separate proposal/discussion for Coffeescript? If so, please ping me so I can participate in the discussion.

p.s. I would have sent this as a message, but since you haven’t included an email address in your profile, Github won’t let random drive-by people unilaterally stuff your inbox with their drivel...

yuchi commented Dec 5, 2011

(| is used in kaffeine as an alternative function call style, similar to bash's pipe, where arguments precede the function)

Wouldn't | conflict with the bitwise OR operator?

I also think this discussion is getting way too far away from JavaScript. CoffeeScript has always been "just JavaScript" expressed slightly differently, and usually things that are too far away from JS are not excepted. I am starting to think that all of these weird new syntaxes are getting a little too far fetched. I personally think CoffeeScript should keep the "just JavaScript" feeling, and support chaining the same way as in my proposal above, which would basically change the output of this CoffeeScript:

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

from this:

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

to this:

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

and be equivalent to this CoffeeScript with parentheses:

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

Anything more than that I think is going too far away from the "just JS" feel that the language has always had.

Collaborator
satyr commented Dec 5, 2011

Which I would rewrite as:

foo = bar
    .map (x) ->
        gronk(x * x)
    .reduce (a, b) ->
        a + grundle(b)

Rewrite in current CoffeeScript you mean? That .reduce would be sent to bar with your proposal, wasting .map.

i.e. I think CoffeeScript should retain the semantics of JS but not the syntax. @raganwald’s proposal goes too far away from the semantics of JS in my opinion. I also don't like the look of all those indentations just to chain stuff together, but perhaps that's just me.

I think CoffeeScript should retain the semantics of JS but not the syntax. @raganwald’s proposal goes too far away from the semantics of JS in my opinion

Two points. First, I agree that it introduces new semantics. However... Doesn’t that also apply to any and all discussion about introducing cascading syntax? In other words, if significant whitespace is a bad idea because it introduces new semantics to JS, aren’t we also against introducing .. and ->> and everything else suggested here?’’

Second, it’s obvious that people want this, given how much work is being done to back them into JS through fluent interfaces. If Coffeescript doesn’t introduce cascading message syntax, people will find another way to cascade messages. So, I suggest we accept that cascades are a part of Javascript, and all we are discussing is how Coffeescript programs should represent them.

And of course, I’m ok if the answer is, “We feel that the best way for Coffeescript programs to represent cascades is the way Javascript represents them, by library authors greenspunning the feature into the semantics of their functions."

quangv commented Dec 5, 2011

@devongovett what do you mean the 'the semantics of JS' ?

raganwald's proposal, what's the difference between these ?

root
    .foo
root
.foo
root()
.foo
root
    .foo
# => root.foo
root
.foo
# => ERROR
root()
.foo
# => ERROR

@raganwald yes I am against all of the other cascading proposals as well. When I look at a piece of code I should be able to understand what it does immediately. These proposals break what I assume should happen when I read the code.

Chaining already works fine in JS. Libraries like jQuery have shown that. Chaining already works in CoffeeScript the same way as it does in JS as well, if you use parentheses around your function calls. I think all we need is to extend that such that one could chain non-parenthesised function calls as well as in my example, or do nothing at all. I'm not really sure there is a problem beyond that. In fact, I'd be fine with doing nothing and leaving it as it currently is -- if you want to use chaining, you have to use parentheses to be clear. All of this new syntax (some of it breaking the beauty of CoffeeScript) and breaking the JS-like semantics that the language has is not something I think CoffeeScript should do.

Contributor

if you want to use chaining, you have to use parentheses to be clear

The more pertinent point is that if you want to use chaining, the methods you're invoking must return an appropriate value.

Sure, jQuery does this and it works beautifully. But @raganwald's argument is that JavaScript libraries shouldn't have to be written in this way in order to be used in this way.

Yup and I disagree. If I'm a library author, I want control over how my API will be used. It really isn't that hard to add return this to the functions you want to be chainable. Really it isn't.

@devongovett

It really isn't that hard to add return this to the functions you want to be chainable. Really it isn't.

No, it isn’t that hard except that CoffeeScript and JavaScript only allow functions to return one thing, so when you decide that .pop() should be cascadable, you are also deciding that it can’t be pipelined as in undoStack.pop().undoIt(). Likewise, on the surface it’s easy to say that current code does what we expect by sending each method to the return value of the whitespace-agnostic previous method invocation, but that hides the deeper reality that some methods return their receiver and some don’t, forcing the reader to look up what the method does to understand what the code does.

It’s easy to write return this, harder to read it when you are trying to understand a block of code at a glance. It doesn’t feel hard to us as API authors because we know our own libraries by heart. But we should have some empathy for the people who read the code our users write. A syntax for cascading messages (mine or any other) makes it easier for code readers and writers.

I suggest it’s easier for API authors too: In choosing to return this, we are mixing two separate concerns: What our functions do in a domain-specifc way, and how they should be used in a syntactic way. I am loathe to invoke clichés as if they are immutable tenets of wisdom, but I urge you to consider the possibility that this is a "separation of concerns” issue and that the answer is—as usual—that separating concerns is desirable.

(JM2C, I like CoffeeScript and I’m sure it will continue to be a fine language no matter how things turn out with respect to this discussion)

OK, I disagree. pop() should do what the API author intended and nothing else. It might be a little nicer to be able to chain those calls, but that isn't how the underlying code works, which is misleading. undoStack.pop().undoIt() looks like it is calling undoIt() on the result of the pop() call, which it sounds like you are saying it wouldn't be. That is too much magic for my taste and it changes the semantics of JS too much.

First, we probably agree on a great deal. But to clarify, I agree with you! When I write:

undoStack.pop().undoIt()

I am absolutely saying “pop the top command object off the undo stack and send the undoIt message to it." Same as if I write:

undoStack
    .pop()
        .undoIt()

But I also want to sometimes write:

dealCard = (shoe) ->
    shoe
        .pop()
        .pop() 
        .pop() # burn three cards
        .pop() # and return the fourth

I think I understand your feeling that this second (contrived) possibility is not worth what you feel are the drawbacks.

  1. Having to indent every line just to simulate chaining as it is now is not very pretty.
  2. There should not be a difference between shoe.pop().pop().pop().pop() and
shoe
  .pop()
  .pop()
  .pop()
  .pop()

I should be able to format my code however I like and have it function the same way.

showell commented Dec 5, 2011

My problem with this proposal is that there's already a dead simple way in CS to apply operations to the same variable over and over again:

dealCard = (shoe) ->
    shoe.pop()
    shoe.pop() 
    shoe.pop() # burn three cards
    shoe.pop() # and return the fourth

I'm all for the DRY principle, but I would use DRY to drive out deeper abstractions, e.g. popMany.

I know the "shoe" example is contrived; a slightly more real-world example might be useful for this whole discussion, something that goes beyond simple styling of widgets, which is already easy in JS/CS.

Contributor

Bringing it up again since it hasn't been aswered: how would one write this so that reduce is chained to map's return value?

foo = bar
  .map (x) ->
      gronk(x * x)
  .reduce (a, b) ->
      a + grundle(b)

@erisdiscord but only optionally... I can write it on a single line if I want. if someCondition then doOneThing() else doAnother() This proposal would change the meaning of chaining on a single line vs on multiple lines which is confusing. I realize it has been done before in other languages, but that does not mean it is a good fit for CoffeeScript.

foo = bar
    .map (x) -> gronk(x * x)
        .reduce (a, b) -> a + grundle(b)

Or (not my favourite):

foo = bar
     .map (x) ->
         gronk(x * x)
        .reduce (a, b) ->
            a + grundle(b)

Which makes me wonder about a proposal above (I hope I understand it correctly):

foo = bar
    ^.map (x) ->
        gronk(x * x)
    ^.reduce (a, b) ->
        a + grundle(b)

None of those are desirable in terms of syntax to me.

showell commented Dec 5, 2011

@raganwald, You mean I made my point in both Ernest and Earnest, right? ;)

I agree that the tree-like forms are more compelling examples for your proposal. I'll ask a thought question that's mostly orthogonal to your proposal. Why can't libraries like jQuery traverse the trees for us?

First, some background:

When you're in the browser, you're naturally dealing with lots of tree-like structures, which ultimately all relate back to the DOM. The DOM itself is a tree, and that tree-like pattern gets reflected in the code you write. When you start manipulating the DOM, you usually end up having some pretty rote code that just traverses a small part of the tree to add styles, update values, etc. Whether or not you're using a fluent library and/or whether or not you are using a programming language that has With-like syntax, the code that you're writing ultimately has an underlying tree structure. To put it another way, even if you introduce a bunch of locals to "flatten" out your code, and even if there is no logical branching like you'd have in an if/else statement, your code is still influenced by a tree in some sense. I think most people are on the same page with regard to that.

Correct me if I'm wrong, but I think some of the positive sentiment for this proposal comes from a reasonable aesthetic--when you're traversing a tree, you expect the layout of the code to reflect that tree on some level? For example, if I apply a style to a UL, then a bunch of LIs within that UL, it seems reasonable to indent, or it seems reasonable to have an implied context.

Back to the question: why don't libraries just traverse data structures for us?

Instead of this:

document.query '#myTable'
  .queryAll '.firstColumn'
    .style
      .background = 'red'
      .border = '2px solid black'
    .text = 'first column'
  .queryAll '.lastColumn'
    .style.background = blue
    .text = 'last column'

I'd love to write this:

document.query('#myTable').update
  firstColumn:
    style:
      background: 'red'
      border:'2px solid black'
    text: 'first column'
  lastColumn:
    style:
      background: 'blue'
      text: 'last column'

I agree with your conjecture about earnest/ernest and share your aesthetic with respect to code that resembles what it consumes or produces.

Update: I also don’t want to overburden this discussion with my proposal. Once people fully understand it, I’m pleased to sit back and let everyone debate it. To summarize, the big questions are:

  1. Should there be a way to cascade messages orthogonal to whether the underlying libraries were written “fluently?”
  2. If so, should it work for one-liners, multi-liners, or both?
  3. Given YN, NY, or YY to question 2, are any of the proposals a win overall for CoffeeScript?

So, I would like to take a step back from advocation and simply answer technical questions (if any) from here on in. I respect that there are other worthy proposals and reasonable dissenting views, and I hope they are all given equal consideration.

erisdev commented Dec 5, 2011

@showell I think this is something you and I agree on: it would indeed be nice if jQuery would traverse data structures for us. But how would you propose creating a generalised form that distinguishes further subqueries from arguments or nested function calls? I ask because I'm tempted to hack together a plugin. C:

showell commented Dec 5, 2011

@erisdiscord In the example we've been using, I would mostly sidestep your question by suggesting that we separate content from styling.

Then the code becomes fairly straightforward again:

myTable = document.query '#myTable'

myTable.style
  '.firstColumn':
    background: 'red'
    border:'2px solid black'
  '.lastColumn':
    background: 'blue'

myTable.text
  '.firstColumn': 'first column'
  '.lastColumn': 'last column'

Now, instead of having a single long block of code with two responsibilities, you have two shorter blocks of code that each have a single responsibility (or are at least closer to SRP).

By splitting out styling from presentation, you drive out the underlying smell of this code, which is that your external CSS isn't pulling much weight. This might be a perfectly legit judgment call for the code in question, but by breaking the code into smaller pieces, I think you have more flexibility in the future.

(I concede that having jQuery deal with arbitrary objects makes it work a little harder. It would basically have to know when it's close to the leaves to distinguish selectors from key/value pairs.)

patrys commented Dec 5, 2011

Guys, please make sure that once you grow old of CS and I will need to maintain the code you write I will still be able to guess what the code does. You can call me old-fashioned but I strongly believe in what B. Kernighan said: "Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." I still thing that having two libraries make you use foo.bar().baz() is still no excuse for the language to include an explicit chaining syntax. You can still write it the way you'd write it any other language:

a = foo.bar()
a.baz()
quangv commented Dec 6, 2011

@patrys
""Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it."

nice quote...

so Chaining and Cascading seems like different features... I say we support Chaining first, and discuss Cascading after.

quangv commented Dec 6, 2011

What do you guys think about this, for multi-line & single-line chaining support.

x y
.z
# => x(y).z
x y .z
# => x(y).z

No. It's unclear. Just write the parenthesis. Really. It isn't that hard.

ratbeard commented Dec 6, 2011

I agree with patrys. I like an el.addClass('active').show() as much as the next guy, but when I need to debug a 10 line jquery chained expression with multiple stack levels from find, filter, end, etc., to be able to step through the code sanely in a debugger and inspect variables, I'll often break it up into multiple expressions / local variable assignments.

I haven't had the problem that libraries which allow you to add chaining onto existing API's solve. The little bit of elegance they might add to a few lines in an application seem outweighed by the complexity and reduced debug-ibility they introduce. Adding this to the language itself to avoid a few () doesn't seem like a good idea to me.

Contributor

+1 with @patrys and @ratbeard.

erisdev commented Dec 6, 2011

@patrys @ratbeard actually, I'd argue that the cascading syntax would make debugging jQuery and the like easier. Consider:

someObject
  .doSomething()
  .doAnotherThing()
  .doMoreThings()

With cascading, this would translate into something much more debuggable than typical jQuery-style chaining-as-cascading. It would compile to something equivalent to this:

someObject.doSomething();
someObject.doAnotherThing();
someObject.doMoreThings();

With source mapping (or however it's called, and it is still planned IIRC), we could pretty easily pinpoint the call that's causing the error, since each cascaded call would be a separate statement on a separate line.

quangv commented Dec 6, 2011

@devongovett, @ratbeard, @clutchski you don't want CS to have specific chaining support? Or are you just against this syntax.

If you don't want CS to support chaining, then I would argue the syntax won't break any existing code, or at least have a work around. For old behavior single-line

x y .z
# chaining => x(y).z

x y.z
# old behavior => x(y.z)

For multi-line, we run into the issue of not being able to support old-behavior... :/ okay so new proposed multi-line chaining support

x y
  .z  # chaining => x(y).z

x y
.z   # old behavior => x(y.z)

Does anyone use the old/current behavior ? Who writes x .y to mean x.y ?

I think if we were to support chaining, then it'll be hard luck to find a more simplistic syntax than [white-space][dot]


Shoot, I forgot that @jashkenas wrote off [dot][blah]

This has come up before, and I'm still of the opinion that it's an abuse of indentation. Leading dot accessors should attach to the final value of the previous line, and not cause the previous line to be implicitly wrapped in parentheses.
-jashkenas #944

I would argue, with all due respect, :) about 'cause the previous line to be implicitly wrapped in parentheses.'
that the the previous line is already wrapped in parentheses... the following .blah is just appended onto the end of the previous compiled line.

The current compilation:

x y
.z
# => x(y.z)

I think it's even more suggestive. Here, the leading dot suggests that the previous line is not finished compiling.

I can't think of any other CoffeeScript syntax, except for multiline-strings, heredocs, & block comments that the next un-indented code impacts or adds onto the previous line.

I think, to extend your point jashkenas, 'Leading dot accessors should attach to the final _compiled_ value of the previous line'

No I am not against it... CS already supports chaining and it works just fine. As a syntax, x .y is just too confusing and I have no idea what it is supposed to do when I first look at it... x().y is oh so much clearer. Oh, I'm calling a function. And plus, CoffeeScript function calls with no arguments require the parens anyway. I think we should just stick with what we have at this point.

quangv commented Dec 6, 2011

@devongovett
CS already supports chaining

It doesn't actively support chaining, as in, it doesn't have specific grammar targeted to make code chaining easier. It merely passively supports chaining, or I would argue just barely tolerates it.

For x .y, I think we can look at it like (x).y and compile it tox.y =p

That isn't chaining... x.y is reading the property y from the object x. If adding chaining grammar to the language was what you meant by chaining, then yes, I am against adding it for all the reasons I've listed in this thread.

quangv commented Dec 6, 2011

@devongovett lol, yes to compile to x().y i think writing x().y is fine... there's no annoying back-track-coding that occurs...

@ghost
ghost commented Dec 7, 2011

Also see: #1897

I’m sure most of you have seen this, but for the record:

Katy is a library that adds chaining semantics to CoffeeScript and JavaScript programs using combinators instead of syntax.

weepy commented Dec 13, 2011

With regard to the pipeline / chaining examples, why not swap around the nature of the indents ? I.e :

  1. Same indent is a chain
  2. Further indent will work on the last top level element.
array
  .pop()
  .sendTo(friend)
     .pop().sendTo(friend)

->

array
  .pop()
  .sendTo(friend)
array.pop().sendTo(friend)

This would allow most compatibility with other Coffee. I'm not certain if either version is more or less intuitive ?

@ghost
ghost commented Dec 13, 2011

Can't get the benefit of the above suggestion.

Would this be something?

array
  .pop()
    .sendTo(friend1)
    .sendTo(friend2)
  .shift()
    .sendTo(friend3)
    .sendTo(friend4)

->

array.pop().sendTo(friend1);
array.pop().sendTo(friend2)
array.shift().sendTo(friend3)
array.shift().sendTo(friend4)
Contributor
aseemk commented May 13, 2012

I'm way late to this party, but really interesting points all around.

In general, I would definitely love to be able to chain over newlines for jQuery. That's indeed issue #1407.

I feel less strongly about cascading personally. I can see the value for sure, and @raganwald makes an excellent point that it makes it clearer to the reader what's going on, but it does worry me a lot that this would be the first time (I think, correct me if I'm wrong) code that looks the exact same in JS would mean something different.

It also worries me that existing CoffeeScript code -- including the compiler itself -- would silently break. i.e. the compiler wouldn't be able to know the intent of the code. But CoffeeScript 1.3 did recently introduce a similarly breaking change with do (foo=bar) ->, so maybe this is acceptable.

All in all though, I'd certainly love if we didn't hold back addressing chaining simply because we couldn't agree on cascading.

Contributor
aseemk commented May 13, 2012

It's also interesting that this is one time where JS will actually have easier syntax to immediately understand when reading:

http://blog.mozilla.org/dherman/2011/12/01/now-thats-a-nice-stache/

Allows chaining and cascading beautifully. But I agree that the curly brace isn't CoffeeScript-y. Tough dilemma.

Collaborator

this would be the first time (I think, correct me if I'm wrong) code that looks the exact same in JS would mean something different

no, a in b, a ? b : c

There's probably plenty more.

Contributor
aseemk commented May 13, 2012

Great points -- and great examples of how those differences can trip people up. To this day I get bitten by in vs. of -- and it's always hard to debug. =)

arbales commented May 13, 2012

What makes it hard to debug?

On May 13, 2012, at 1:27 PM, Aseem Kishorereply@reply.github.com wrote:

Great points -- and great examples of how those differences can trip people up. To this day I get bitten by in vs. of -- and it's always hard to debug. =)


Reply to this email directly or view it on GitHub:
#1495 (comment)

Contributor
aseemk commented May 13, 2012

for key in obj isn't a syntax error, and it in itself doesn't throw any runtime error -- it always works -- so errors surface somewhere else in the code.

I'm not complaining at this point, just commenting that every time I make this mistake, it's quite an ordeal to get back to that line as the source of the error.

mcmire commented May 13, 2012

I would still love to see this happen. Forget the jQuery argument because I think that horse is dead or dying, but what about cases like #2083, where all you really want is to be able to drop the parens on an indented one-liner? (Basically, changing it so that a new line that starts with a dot does not apply to the preceding token, but the entire line.)

Does anyone disagree with this (besides Jeremy)?

quangv commented May 15, 2012
$ 'body' .html 'HelloWorld'

I'd love to learn Jison to see how easy this would be to implement.

arbales commented May 15, 2012

@quangv I think you'll find that challenging beyond learning Jison – that could compile to any of…

$('body').html('HelloWorld')
$('body'.html)('HelloWorld')
$('body'.html('HelloWorld'))
...

@quangv you might want to do it at the rewriter stage, 'if the current token is a dot and the last token is .spaced, close open calls'. Saves you a lot of trouble ;)

mcmire commented May 15, 2012

If you really want to do it, see how coco does it, because it can do this already I believe.

quangv commented May 15, 2012

@arbales yeh they'll have to be some editorial decisions on how it compiles.

$ 'body' .html 'hello'  # => $('body').html('hello');  # new chaining support.
$ 'body'.html 'hello'  # => $('body'.html('hello'));  # like it currently does.

Sorry I just don't see the down-side of having chaining support, unless it really fudge something up in the compiler, or makes compilation slow or something. I doubt many people use spaces in between there object's dot notations. And multi-line chaining support / alternatives have already been discussed.

Is it just cause it's ambiguous? I wonder what else in CS is just as, if not more ambiguous.

I don't have a ruby background, things like cubes = (math.cube num for num in list) still gives me a headache.

Idk what's the harm? Is it just personal preference? If it really goes against the "Philosophy of CS" than I think it's Philosophy should be well articulated and the wiki, to make it clear to all, what is and is not acceptable CS syntax/functionality/scope.

Is it just cause:

chains are dumb

I just wanted to point out Backbone, another brilliant project of Jeremy's and no doubt contributed and advocated by the same community, has built in support for jQuery, the View's $ and $el... I know these were recent additions but, since jQuery popularized chaining, and Backbone, a family member of CS imho :) supports it... it wouldn't be so outrageous if CoffeeScript has better support for chaining...

@renekooi Thanks, you don't make it sound hard at all to support something like this. So I guess it's just a question of should we.

Owner

One pretty nice approach. I just learned that Dart offers a .. operator for cascades.

expression
  ..method1()
  ..method2()
  ..method3()
enyo commented Jun 28, 2012

@jashkenas So does this mean then that I could do:

expression
  ..method1()
  ..method2()
    ..a()
    ..b()

?

mcmire commented Jun 28, 2012

@enyo Yeah, here's a post on it: http://news.dartlang.org/2012/02/method-cascades-in-dart-posted-by-gilad.html. Not only can you make method calls on subsequent lines, but you can also set properties, which looks goofy but is kind of an interesting approach. But, it hasn't been implemented in Dart yet -- looks like the details are still being worked out.

expression
  ..method1()
  ..method2()
  ..method3()

Looks nice when you indent it to show what’s going on, but consider expression..method1().method2()..method3(). Did you catch that method3() is sent to the return value of method2()? No, which is why we use indentation to make our code clear.

So if we’re going to use indentation to make our code clear, I ask why we don’t just let the CoffeeScript compiler infer what we want from the indentation instead of from the use of .. :-)

Or, pick another syntax that is equally clear whether used inline or with indentation. .. is a little two subtle to my eyes.

erisdev commented Jun 29, 2012

.. is a little two subtle

@raganwald was that intentional? I spat coffee.

I agree, though; it's a little too subtle and unobvious. 👎

poswald commented Jun 29, 2012

How about an elipsis then:

expression
…method1()
…method2()
…method3()

:-D In all seriousness, I kind of like the double dot notation. I think from reading some of the examples in that dart posting, it's pretty clear what is going on. Regardless of the character or indentation used, I think the key point that the caller decides is a very useful feature and somebody should make a call on it one way or the other.

erisdev commented Jun 29, 2012

@poswald it would be awesome if modern programming languages (Perl 6 notwithstanding) were free to adopt Unicode operators in earnest, but, well, yeah, you already know. C:

@satyr satyr referenced this issue in satyr/coco Jun 29, 2012
Closed

Smalltalk-like cascading #72

Contributor

Did you catch that method3() is sent to the return value of method2()? - @raganwald

Yes, but that's a weak argument. Things like

((expression.method1()).method2()).method3()

can look just as confusing, but don't warrant not using parentheses.

The double dot idea looks nice, I'm surprised it never came up in multiple issues and year-long debates. The first concern would be conflicting with the float+property bug in JS (1..method), but that already causes a parse error in CoffeeScript, so it's clean.

Regardless of the syntax, I hope the solution doesn't use a _ref variable to hold the parent expression and compiles to clean, indented, chained calls - otherwise every project using jQuery will have the ugliest js output.

@ricardobeat:

Things like ((expression.method1()).method2()).method3() can look just as confusing, but don't warrant not using parentheses.

That isn’t isomorphic to my argument. Here’s the parallel argument I’l construct as a straw man:

Things like if foo { bar(); bash() } can look just as confusing, but don’t warrant not using braces.

To which I would reply: yes they do look confusing, which is why programmers have discovered that the best policy is to use indentation to signal the code’s structure like this:

if foo {
  bar();
  baz()
}

And that leads us to saying “As long as we’re using indentation, let’s make it significant,” which leads us to:

if foo
  bar()
  baz()

My argument with respect to chaining (or “K Combinator”) syntax is that we end up in the same place: We apply the syntax to communicate our intention to the transpiler and then we and indent it to communicate our intention to other humans. Why not skip the intermediate step and use indentation to communicate our intention to both parties?

I don’t think that indentation is the only way to go, but I submit that if we're using indentation in non-trivial examples, it’s a syntax smell: A great syntax wouldn’t need indentation.

Getting back to parentheses, maybe there’s a chaining syntax that doesn’t look like a method call, maybe it looks more like parentheses. Our eyes have been trained to parse parentheses on a single line, there might be something interesting there.

e.g. expression{.method1().method2().method3()} vs. expression{.method1()}.method2(){.method3()}

I think parentheses are different than infix operators when you want to use them on a single line or when spanning lines, because we have accumulated a lot of practice matching them in our heads and when they’re too difficult to match in our heads we can use our existing tools to match them up for us.

enyo commented Jun 29, 2012

@raganwald I think that indentation is the cleanest and most beautiful solution, but it would completely break backwards compatibility. The .. syntax would not.

I would also like to say that I don't think that parentheses like expression{.method1().method2()} are the way to go. I hate it when I start writing a line and I have to go back to the beginning to add an opening parenthesis... it completely interrupts my flow when programming.

I think that indentation is the cleanest and most beautiful solution, but it would completely break backwards compatibility

I agree with this.

I hate it when I start writing a line and I have to go back to the beginning to add an opening parenthesis

I’m unclear on this. In my hypothetical parenthesis syntax, let’s start typing:

expression
expression{
expression{.message()
expression{.message()}

Whereas with ..:

expression
expression..
expression..message()

The parenthesis adds an extra character to close the K Combinator, but I don’t see you needing to backtrack.

p.s. Thanks, @enyo, “Beautiful but impractical” is one of the nicest things anyone can say about an idea.

enyo commented Jun 29, 2012

With paranthesis I have to know that I'm going to apply more than one method on the object before I start writing the first method call. If I just think of it after writing the first method, I have to backtrack.

expression
  .method1()
    # I realize here I want to apply something on the return value on .method1(),
    # so I simply add it here
    .submethod1()
  # Now I just decide where to continue...

So I don't have to know the exact method chain I will need to build beforehand.

Are you comparing parentheses to dot-dot or parentheses to indentation? Because if you’re comparing parentheses to dot-dot, you have the same problem of needing to back track. Indentation makes backtracking go away with method calls just as it does with logical branches:

if foo
  baz()
  # I realize here I want to do something if foo is truthy
  bash()
# Now I decide whether the next thing I want to do is if foo is truthy or all the time
enyo commented Jun 29, 2012

I was comparing it to indentation. I know that indentation is not the best way to go, but it would be great to find a solution that doesn't need backtracking.

Contributor

How about only allowing .. to start a line (i.e. using it inline would be a compile error)? Single line usage doesn't seem to offer anything worth the trouble, and parentheses already cover that.

Original Proposal: indent + '.x' = send x to parent

This allows for easy chaining WITHOUT library support.

Example 1:

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'

However, it can get ugly, confusing and unreadable:

Example 2:

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

And more importantly, this is valid in javascript but would have a different meaning in CS. Unacceptable.

My Proposal:
A) indent + '.x' = send x to parent
B) function + args + '.x' = send x to result of function (args)

This would work even if the library didn't support chaining natively, which is immensely powerful:

Example 3:

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

This removes the need for typing .end().end() and all that nonsense.

On the downside, someone mentioned this as a confusing situation:

Example 4:

a() .b something .c yay \.d()

Which in javascript would be:

a().b(something.c(yay).d())

But I think this is acceptable. Especially considering this common use case:

Example 5:

foo = require 'bar' \.baz 

Another use of this syntax which was not discussed: use of if and unless in chaining:

Example 6:

fratboy
  \.say 'another beer!' unless \.hasBeer
  \.when bar_is_closed if \.atBar()
    .removeClass 'atBar'
    .goToFratHouse()

Which in javascript would be:

if (!fratboy.hasBeer) {
  fratboy.say('another beer!');
}
if (fratboy.atBar()) {
  fratboy.when(bar_is_closed)
    .removeClass('atBar')
    .goToFratHouse();
}

Which is a big deal and the reason I read the entire thread of Issue #1495. However, this requires changing the rules of the trailing if clause to terminate at the end of the line. Example: instead of

a() if b()
  .c()

being intepreted as

if (b().c()) {
  a();
}

it should instead be interpreted as

if (b()) {
  a().c();
}

Which is NOT backwards compatible and thus pretty scary, but is very useful. I'll call this Proposal C since it differs from A and B.

After thinking about this more, Example 6 wouldn't be interpreted as intended. It would be interpreted as:

if (!fratboy.hasBeer) {
  fratboy.say('another beer!');
}
if (fratboy.atBar()) {
  fratboy.when(bar_is_closed)
    .removeClass('atBar'.goToFratHouse());
}

I should amend example 6:

fratboy
  \.say 'another beer!' unless \.hasBeer
  \.when bar_is_closed if \.atBar()
    \.removeClass 'atBar'
    \.goToFratHouse()

Which in javascript would be:

if (!fratboy.hasBeer) {
  fratboy.say('another beer!');
}
if (fratboy.atBar()) {
  var _parent = fratboy.when(bar_is_closed);
  _parent.removeClass('atBar');
  _parent.goToFratHouse();
}

This isn't as desirable for library supported chaining, but it's super readable and I'd still use it over typing a ton more parens. It also has the added bonus of forcing you to be explicit when chaining functions don't return the same object they are sent.

mcmire commented Jul 30, 2012

@donabrams Your approach is pretty interesting. It's different behavior than what we've had previously because it seems like the rule from the parser perspective is "if i see a \. then look at the parent scope and replace \. with <parent>. -- regardless of where that \. appears on the line". It looks a lot like a kind of with construct, except predicated on indentation and with explicit syntax. It could be confusing in that I don't think that I've seen this exact language feature before, but it also is pretty powerful too. I also like how you've thought about trailing conditionals.

That said, it may be a bit too powerful. Your syntax is rather succinct, which is cool for this exact use case, but I feel that people will want to do more with it that the syntax doesn't support. Any time you add a new language feature that fundamentally changes how people write their code, you have to be careful. It's one of those things that will have to be tested out in production code for a while. That's fine, but if \. was only allowed at the start of a line to mean "prepend the parent scope to the start of this line as opposed to joining this line to the previous one", I think that would go over easier and not shake things up so much.

Also, you mentioned earlier a single-line equivalent, but based on the above, I don't think it makes sense here. For instance, going back to one of your previous examples, I don't think the following makes sense:

a() .b something .c yay \.d()

Because there is no parent scope, there is no way to meaningfully replace \.

So that might be a problem. Now that I think about it again, though, the behavior is different from multi-line anyway, and so I feel like there should be a separate syntax for this (or, no syntax at all, as it's still not very readable).

I didn't like the single line version as much either (Proposal B). Contrived example:

playerId = createPlayer 'joe', 200, 'password', 21230 \.id
#versus
playerId = createPlayer('joe', 200, 'password', 21230).id

The latter is easier to read and not much harder to program, so why rock the boat?

Proposals A and C I am much more in want of. The alternatives are very wordy and harder to read.

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 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 commented Oct 18, 2012

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

useStruct \
  buildStruct \
    parseStruct \
     '1=2&3=4'
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?

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

Contributor

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

Collaborator

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

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.

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

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 commented Dec 4, 2013

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

Collaborator

No, this discussion is about cascades.

Contributor
xixixao commented Dec 6, 2013

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

Collaborator

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

As OP, I'd say it looks closed.

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'

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

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 commented Mar 14, 2016

Is this likely to be implemented soon?

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