jashkenas/coffeescript

String after implicit object

Opened this issue · 26 comments

Shouldn't this:

Hm.label(for: 'username', 'Username')

get compiled into:

      Hm.label({
        "for": 'username'
        },
        'Username'
      );

instead of:

      Hm.label({
        "for": 'username',
        'Username': 'Username'
      });

??

Perhaps it would be a good idea if compact literal expansions (like {a, b, c}{a:a, b:b, c:c}) only worked inside of explicit braces, and not inside of implicit object literals. Otherwise, there is no way to "break out" of an implicit literal on a single line; individual values in an argument list, like in this example, will just be interpreted as keys.

Actually, while this is baffling behavior at first glance, I think it's OK.

Consider that in Ruby, implicit hashes are allowed only for the last argument precisely because humans would have a hard time seeing that y is the third argument in the function call

foo(x, :a => b, :c => d, y)

So, if you use a hash as an earlier argument, you need to use curly braces. It seems wise to me, as a stylistic matter, to use curly braces for a hash that isn't the last argument to a function. Given the stylistic convention, scenarios like the above would be unlikely for two reasons: First, the caller would use curly braces around the hash; and second, the person who wrote the function would have made the hash the second argument rather than the first.

Also consider syntax highlighting. If for: 'username', 'Username' were all in the "hash" color, or if for: and 'Username' were both in the "key" color, a programmer trying to pass 'Username' as an argument would immediately realize their mistake and add curly braces.

So, I don't strenuously object to sethaurus' proposal, but I don't think it's necessary, either.

TrevorBurnham: That makes sense. Thank you.

Thanks for reporting this ... it's not too hard of a fix -- just one more check to make, while searching for the end of the implicit object. Here's the patch:

http://github.com/jashkenas/coffee-script/commit/474c372b17cd6ebd5eb6c26ef7f6cb5769c881bf

Closing the ticket.

Cool. Thank you, jashkenas!

Sorry, devtime, had to revert this commit for the following reason:

http://github.com/jashkenas/coffee-script/issues/#issue/631

Ok, jashkenas. No biggie. Thanks for letting me know.

To prevent this, how about making object shorthand syntax a little more explicit:
{:a, :b} # {a:a, b:b}
Then we can omit braces in these cases as well:
f :k1, :k2, o # f({k1:k1, k2:k2}, o)
:puts = require 'sys' # {puts:puts} = require('sys')

satyr: I like it!

jashkenas: will you consider reopening this issue? or should we create a new one for satyr's proposal? Thanks.

I like it as well. Nice suggestion.

I like the way it is now. TrevorBurnham's comment on how ruby does it, also applying for pythons **kwargs, is tried and true, and makes perfect sense.

To solve it, you can already be expicit:
Hm.label({for: 'username'}, 'Username')
Compiles to:
Hm.label({
"for": 'username'
}, 'Username');
As expected. You can even leave out the parenthesis:
Hm.label {for: 'username'}, 'Username'

I like satyr's proposal. How often would you really want to do fn key: value, 'key2' ? What's the point of having a string key with the same string value?

On a side note, why is fn key: value, a + b not valid but fn key: value, (a + b) is compiling. There is too much magic happening around implicit objects.

How often would you really want to do fn key: value, 'key2' ?

StanAngeloff: I agree with you -- it's much more common for me to need a string after a hash than needing a "string key with the same string value".

func(xx: zz, 'yy')

Also, the current behavior is counterintuitive. Most developers who'll come by the sort of code above for the first time will think that the second parameter is a string.

If it can be done, wtihout requring explicit brackets great.

I spent a long time trying to solve this last night, and got mostly nowhere. If anyone has some brilliant ideas for parsing it, I'd love to hear them. This is the crux of the issue.

call key: val, arg

call key: val, key2: call2 arg1, arg2, arg3

In a perfect world would compile into (I think):

call({key: val}, arg);

call({key: val, key2: call2(arg1, arg2, arg3)});

The trick is that when we're rewriting the token stream and inserting implicit parentheses and braces, the braces go first. I can't think of a rule that would correctly distinguish the two parses above, and would know that the implicit braces are supposed to end early for the first bit of code. Something where the brace and paren rules mutually invoke each other might do the trick...

It can get a bit harder: multiple inline objects. Like:
call key: val, key2: call2 arg1, okey: oval, okey2: oval2, notakeyorarg
Being compiled into:
call({key: val, key2: call2(arg1, {okey: oval, okey2: oval2}, notakeyorarg)});

I haven't found a way to make this case parsable, in general. I'm afraid we'll have to accept it as a known limitation of implicit objects (which, for the record, are still more flexible than in Ruby, for example).

Closing this ticket as frozen for the time being, and if someone comes up with a fix, it'll be warmly welcomed.

A patch to fix this issue is now on master, which now compiles the original test case for this issue correctly. If you have a minute to give master a try, and see if it's working for your use cases, please do.

#1061

jashkenas,

Wow! This is good news! I'll be trying this out later today. I'm afraid I haven't been keeping up with the commits lately -- Is the current code on master as stable (or more) as 1.0 by now? Would you say it's pretty safe to start compiling my code with it? Or should I be using some branch?

master is the future 1.1 ... 1.0-stable is the future 1.0.1 -- this particular commit is on master, because it's highly likely that it's not fully baked yet. That's why I'm asking you to kick the tires a bit ;)

Sure. Will do. :)

#1061

Some observations:

  • f a:b, c is valid, but [a:b, c] isn't.
  • f a:b, c+1 still doesn't work.
  • f a:b, g c still doesn't work.

yep the fix is just a hack to prevent unexpected results.
all three cases result at least in an error message.

Those are serious failures, though. Is the hack more generalizable?

Looks like the discussion is happening on #1061 ... let's keep it over there.