paren-free call expressions
I'm working on a block-as-better-function proposal, essentially an alternative to arrow-function-syntax even though unlike the latter, the former has new semantics.
One of the insights from Allen and Mark, which is kind of obvious to Rubyists, is that blocks as lightweight functions usable for building control abstractions really need paren-free calls to be as light as in Smalltalk, therefore actually usable compared to writing loops or even old-fashioned function expressions.
Sure, block-lambdas are good as lighter functions too, but they're not enough, and the TCP wins were considered losses in that context. This is because a function can escape from a downward flow and later be invoked, then attempt to return from the activation of its static parent function after that function's activation has been deallocated.
A block may not escape. Indeed Ruby's simplest downward-funarg block protocol involves caller passing a block via a paren-free call, and the receiving method not declaring the formal, rather just calling it via Ruby's yield. No escape.
Anyway, I'm going to include paren-free call syntax in the block-lambda proposal, not factor it out. It probably could be, but it's not as simple as you show. In particular,
foo = Foo.new
in Harmony should continue to get the 'new' property from Foo and assign it to foo. It should not implicitly call Foo.new().
On 18.05.2011 20:16, Brendan Eich wrote:
I'm working on a block-as-better-function proposal, essentially an alternative to arrow-function-syntax even though unlike the latter, the former has new semantics.
One of the insights from Allen and Mark, which is kind of obvious to Rubyists, is that blocks as lightweight functions usable for building control abstractions really need paren-free calls to be as light as in Smalltalk, therefore actually usable compared to writing loops or even old-fashioned function expressions.
Yeah, right.
Sure, block-lambdas are good as lighter functions too, but they're not enough, and the TCP wins were considered losses in that context.
Sorry, TCP? What's that?
This is because a function can escape from a downward flow and later be invoked, then attempt to return from the activation of its static parent function after that function's activation has been deallocated.
Yeah, a casual "bidirectional" (up/down) closure.
A block may not escape. Indeed Ruby's simplest downward-funarg block protocol involves caller passing a block via a paren-free call, and the receiving method not declaring the formal, rather just calling it via Ruby's yield. No escape.
Just only if the receiving method doesn't define the block explicitly via the last &-argument. It's exactly the explicit block which is transformed to the Proc-object, and already this object can be returned upward. Though, there's no much sense in it, since the block is created lexically in the caller context and all caller's bindings are already closured in it anyway (will it be returned back or not -- no matter).
Anyway, I'm going to include paren-free call syntax in the block-lambda proposal, not factor it out. It probably could be, but it's not as simple as you show. In particular,
foo = Foo.new
in Harmony should continue to get the 'new' property from Foo and assign it to foo. It should not implicitly call Foo.new().
Oh, of course. The same as in CoffeeScript. It will be a simple property
reading, not calling. In Coffee we have to used () for calling a
function without parameters. However in Ruby as is said there are no
simple properties, all public stuff are only methods. foo.bar
-- is
calling a method. foo.bar = 10
-- is calling the method bar=
, it's
actually exactly parens-free mode of foo.bar=(10).
So in JS/Coffee I do not expect Foo.new behave as a method. But Foo.new {x: 10} -- yes, and it's very convenient for declarative DSL.
P.S.: so in proposal it will look like:
[1, 2, 3].map {|x| x * x}
not like this:
[1, 2, 3].map({|x| x * x});
right? (just asking to include to the following talk)
Dmitry.
I'm new to this list but I was writing some notes about paren-free calls this morning and thought they might be relevant.
The trouble with paren-free calls, as implemented in CoffeeScript and others:
- they overload the space character, making it mean something different in only a small fraction of cases.
- they break down whenever calls are nested or part of another expression (requiring a fall-back to explicit parentheses).
- they also make certain other things impossible to write, like named function declarations (blah -> becomes blah(->)).
The first point is major, in my opinion. If the goal is to have an argument list opener that doesn't require an explicit close, then some other character(s) would be a better solution than forcing people to read possible double-meanings into every space they see.
U+0020 is consistently high up in the top 5 most-repeated characters in a JavaScript program, so overloading its meaning for a minor cosmetic effect is not justifiable IMO. And the writability improvement of making list closure optional can be achieved by other means, if it's considered that important.
,
On Thu, May 19, 2011 at 2:45 AM, David Griffiths <dxgriffiths at gmail.com> wrote:
- they overload the space character, making it mean something different in only a small fraction of cases. [...]
The first point is major, in my opinion. If the goal is to have an argument list opener that doesn't require an explicit close, then some other character(s) would be a better solution than forcing people to read possible double-meanings into every space they see.
U+0020 is consistently high up in the top 5 most-repeated characters in a JavaScript program, so overloading its meaning for a minor cosmetic effect is not justifiable IMO.
Could you clarify this objection? As I understand it, even with paren-free call syntax, spaces would still have just one purpose: to separate tokens.
Many, many languages (including Perl, Ruby, VB, Scala, Smalltalk, Logo, and of course all the ML-like languages, including Haskell and F#) do without parentheses for calls in some circumstances at least. Do you use any of these languages? I quite like some of them.
Sure,
My rationale is that people reading code are not the same as parsers :) If a space is sometimes just a space, and sometimes it is effectively an implied (, then the comprehension difficulty is increased.
If you could measure it with an eye-tracking system, I suspect that savings like these are actually a net cost in additional eye fixations and reading time... especially in cases where the () can only be omitted sometimes and not others.
To quote someone else, it's an "absurd geek tweak" that can only really serve to make the overall JS codebase less accessible.
That's not to say it shouldn't be supported by altJS compilers and languages that showcase extreme minimalism for the novelty value, but in a global standard?
To answer your other question though, yes, I do and I get the value. I just think in this case it's unwise.
On 21/05/2011, at 4:26 AM, Jason Orendorff wrote:
On Thu, May 19, 2011 at 2:45 AM, David Griffiths <dxgriffiths at gmail.com> wrote:
- they overload the space character, making it mean something different in only a small fraction of cases. [...]
The first point is major, in my opinion. If the goal is to have an argument list opener that doesn't require an explicit close, then some other character(s) would be a better solution than forcing people to read possible double-meanings into every space they see.
U+0020 is consistently high up in the top 5 most-repeated characters in a JavaScript program, so overloading its meaning for a minor cosmetic effect is not justifiable IMO.
Could you clarify this objection? As I understand it, even with paren-free call syntax, spaces would still have just one purpose: to separate tokens.
Many, many languages (including Perl, Ruby, VB, Scala, Smalltalk, Logo, and of course all the ML-like languages, including Haskell and F#) do without parentheses for calls in some circumstances at least. Do you use any of these languages? I quite like some of them.
On Thu, 26 May 2011 07:15:31 +0200, David Griffiths
<dxgriffiths at gmail.com> wrote:
Many, many languages (including Perl, Ruby, VB, Scala, Smalltalk, Logo, and of course all the ML-like languages, including Haskell and F#) do without parentheses for calls in some circumstances at least. Do you use any of these languages? I quite like some of them.
The functional languages (ML-like or Haskell-like) typicallly have curried
function
definitions. I.e., if you define a function (ML-syntax, from memory :)
let fun foo x y z = x * y * z
in foo 2 3 4
end;
the function foo isn't actually taking three arguments, it's a curried
function of type
int -> int -> int -> int
taking one argument and returning a new function.
You can also make one argument a product type, so: let fun bar (x, y) z = x * y * z in bar (2, 3) 4 end; defines a function of type (int * int) -> int -> int.
The point is that parentheses are not part of the call syntax at all. It's
not that you can omit them in some
circumstances. You can omit them in all circumstances, because all
functions take only one argument. That argument
might need to be a tuble, and you use parentheses to create tuples, but
they don't need to be part of the call, e.g.
let fun bar (x, y) z = x * y * z
val tup = (2,3)
in bar tup 4
end;
ECMAScript doesn't curry its function declarations. A function does take
all its arguments at once (and not as a
tuple value), and since we can even supply too few or too many arguments
when calling a function, trying to
mimic SML will only bring pain.
Much as I like SML and Haskell, their function application syntax
(literally "exp ::= exp_1 exp_2" [1]) just won't
work for ECMAScript. It's pretty in its simplicity, but ECMAScript is long
past simplicity on this point.
/L
[1] First match for "SML grammar":
www.mpi-sws.org/~rossberg/sml.html
ECMAScript doesn't curry its function declarations. A function does take all its arguments at once (and not as a tuple value), and since we can even supply too few or too many arguments when calling a function, trying to mimic SML will only bring pain.
I thought so, too, for a long time. But it turns out that ECMAScript already supports currying, in definitions and applications, including the usual application by juxtaposition syntax (curried definitions are more awkward).
What is confusing the issue is that each ES function takes a single argument, but that argument is a variable-length array in disguise, not a fixed-size tuple. Also, the syntax for curried function definitions is so awkward that programmers prefer to pack their arguments into a single argument array (uncurried).
Current practice:
// curried function addC(x) { return function (y) { return x+y; }; } log( addC (1) (2) );
// uncurried function add (x,y) { return x+y; } log( add (1,2) );
// the arguments list really wants to be an array function addA () { return arguments[0]+arguments[1]; } var args = [1,2]; log( addA.apply (null,args) );
Part of the Harmony proposals is to make this last point clearer, by introducing destructuring of formal parameters, spreads for arrays and rest parameters
// the arguments list really wants to be an array function addH (...args) { return args[0]+args[1]; } let args = [1,2]; log( addH (...args) );
This makes it fairly obvious that the argument "list", which is commonly used as an arguments array, can also be seen as an array argument with non-standard syntax.
If we look at the addC example again, we already have paren-free calls in ES, it is just that ES forces us to wrap every argument -even numeric literals- in an array, written in parens instead of brackets.
The ES way towards fully paren-free call syntax seems to go via:
(a) making curried definitions simpler
(b) not having to wrap each argument as an array,
(in the same step, we would start using [..] instead
of (..) if we really mean to pass an array)
That does not mean that this will be trivial to accomplish within the constraints of backwards compatibility and ES grammar. My own attempt is still pretty awkward
Flattening syntactic tail nests (paren-free, continued)
https://mail.mozilla.org/pipermail/es-discuss/2011-April/013616.html
Control structure proxies (aka monads, cps, and their relatives)
https://mail.mozilla.org/pipermail/es-discuss/2011-May/014674.html
but if more hands, eyes, and minds could join the attempt, I do not think ES is beyond hope here!-)
Much as I like SML and Haskell, their function application syntax (literally "exp ::= exp_1 exp_2" [1]) just won't work for ECMAScript. It's pretty in its simplicity, but ECMAScript is long past simplicity on this point.
ES can't afford to lose support for its vast and variable installed code base, but that does not imply that simplicity cannot be offered as an option. The need is there, so big indeed that real world JS programmers introduce preprocessors to make their work easier. Some of them would jump at the opportunity to drop their preprocessing (perhaps not the preprocessor authors;-).
Claus
Parens-free call expression allow to provide some actions /decoratively/. From this, the consequence of easy and elegant DSLs (domain specific languages). For example:
class Account { attributes "customer", "cart" }
Thus, call to
attribute
function looks like not the imperative call, but like a declaration that instances of Account class has those attributes.Ruby uses exactly this approach of its attr_accessor, private, etc. "keywords" which in fact are just functions placed on the very basic class:
class Foo attr_accessor :x, :y end
foo = Foo.new
foo.x = 10 foo.y = 20
which in fact is just a syntactic sugar (not at grammar, but at application level via simple
attr_accessor
function) for the:class Foo
def x end
def x=(value) end
def y end
def y=(value) end
end
That is, create getter and setter for every needed property (yes, in Ruby all public properties are accessors). Thus, this function does it behind the scene: attr_accessor(:x, :y), however being called /without/ parens, it elegantly looks like a declarative keyword. Another example as is said,
private
function which also without parens looks like an operator.Besides, If -> will be accepted, in many cases (being passed as
callback) they will much more elegantly look without parens of call expressions.
So what do you think? If the idea is good, may it bring some problems with parsers and grammar?
P.S.: some examples of declarative DSLs: jashkenas/coffee-script/wiki/[Extensibility]-Writing-DSLs
Dmitry.