4 ms·
I applaud people for trying to find clean ways to add chaining to langauges like JS, but feel we are working at the wrong level of abstraction. Chaining is a sy
by raganwald 15y ago
I applaud people for trying to find clean ways to add chaining to langauges like JS, but feel we are working at the wrong level of abstraction. Chaining is a syntax and grammer problem, not a semantic problem. Smalltalk solved this by making chaining part of the syntax. if you write:
foo
:bar;
:bash something;
:thenBlitz.
The semi-colons tell the interpreter to chain the method calls to the original receiver. That way you can read the sntax and know chaining is involved without needing to know whether those methods were written to be chained. The return value of a method is its semantics, and should not be chosen to try to implement syntax.
Summary: This problem should be solved by the parser, not by the function author.
p.s. paging @jashkenas!
p.p.s. Did you ever take that test yourself? Yes, my comments apply doubly to libraries like #andand.
- jashkenas 15y agoReporting for duty... A dedicated syntax for chaining has been discussed several times for CoffeeScript, with different flavors: https://github.com/jashkenas/coffee-script/issues/1495 https://github.com/jashkenas/coffee-script/issues/1495 https://github.com/jashkenas/coffee-script/issues/1291 https://github.com/jashkenas/coffee-script/issues/1291 https://github.com/jashkenas/coffee-script/issues/281 https://github.com/jashkenas/coffee-script/issues/281 https://github.com/jashkenas/coffee-script/issues/1431 https://github.com/jashkenas/coffee-script/issues/1431 Personally, I'm a bit iffy on the subject, because although the demonstrated usefulness of the idiom is evident in libraries like jQuery, chaining-as-a-language-feature is inherently focused on stringing together a sequence of imperative side effects, where it can be nicer to use expressions for their values. For example, where the returned value is a part of the next step of the computation, chaining comes naturally: list .concat(other) .map((x) -> x * x) .filter((x) -> x % 2 is 0) .reverse() And where you need built-in language chaining to do the job for you, it's almost always because you're ignoring the return value, and using each method for its side effects: brush .startPath() .moveTo(10, 10) .stroke("red") .fill("blue") .ellipse(50, 50) .endPath() So the enforced imperativeness of chaining syntax makes it feel a bit off. Still, if you were designing such a syntax from scratch, and not following Smalltalk's lead, what would you think it should look like?
- raganwald 15y agoI 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."
- jashkenas 15y agoWow -- that's really taking significant whitespace to a whole 'nother level. Very pretty.
- jussir 15y agoLooks very nice! Though the discrimination between intending or not is done based on whether the previous action mutates the object (no new intend) or creates or returns different object (new intend), right? This is something that can't be known on the language level unless there's a specific syntax for it, which doesn't seem to be possible for Javascript based languages (e.g. https://github.com/jashkenas/coffee-script/issues/1738 https://github.com/jashkenas/coffee-script/issues/1738).
- raganwald 15y agoMy point is that if I owned the parser, I would ignore what the function returns and send the message to the previous receiver when multiple message inocations are indented in parallel. Here's an example using functions that both return a value and have a side-effect: ['foo', ['bar']] .pop() .pop() Would return 'foo' if I owned the parser, while: ['foo', ['bar']] .pop() .pop() ...would return 'bar'. You can infer from his what happens to the niial array in terms of side effects. My suggestion is that "chaining" method calls is a syntax issue and not a function issue, and that writing functions tonreturn 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.
- satyr 15y agoI believe you mean: foo bar; bash: something; thenBlitz. http://www.inf.ufsc.br/poo/smalltalk/ibm/tutorial/chap3.html#3.70 http://www.inf.ufsc.br/poo/smalltalk/ibm/tutorial/chap3.html...
- raganwald 15y agoyes I do!