breuleux / earl-grey

Programming language compiling to JavaScript
http://breuleux.github.io/earl-grey/
MIT License
464 stars 6 forks source link

Parentheses #3

Open ghost opened 9 years ago

ghost commented 9 years ago

Parentheses are often syntax noise. For example, if we have a silly function like this [1]:

foo(a, b, c) = print '{a} - {b} - {c}'

we can call the function like this [2]:

foo(1, 2, 3)

we can't omit parentheses (like in ruby or coffeescript) [3]

foo 1, 2, 3

I understand the reason. In Earl Grey we could code something like tihs: [4]

p = {1, 2, 3}
foo p

However [3] is the most readable piece of code. So I suggest some syntax sugar like this:

foo! 1, 2, 3

It's just an idea. What do you think about it? Thank you for Earl Grey.

breuleux commented 9 years ago

Personally I find [2] more readable than [3], because the brackets isolate the function name better. It may be a matter of habit, but I do find for instance that [2] makes skimming easier: if I want to know what operations a piece of code is doing, I can spot parentheses and check what's to their left, whereas blanks are more ambiguous. Furthermore, in Ruby/CoffeeScript, expressions like g(f 1, 2, 3) look confusing. At a glance it looks like the commas separate g's arguments, even though they actually separate f's.

Either way, what you propose is currently impossible because line breaks are exactly like commas, and have the same priority as commas. So if you wrote, for instance:

foo! 1, 2, 3
foo2{}

That would be equivalent to foo! 1, 2, 3, foo2{}.

This being said, I am considering adding back semi-colons to serve as that low-priority separator instead (their usage would not be required, of course). Then I would raise the priority of the comma above that of the with operator (among others like ->, : and where), allowing you to write this:

foo with 1, 2, 3

You might prefer something terser, but ! already has a role that requires higher precedence than commas, and I don't really see what else could be used that doesn't look weird. I like with well enough, but feel free to suggest something else.

ghost commented 9 years ago

To omit parentheses is not always a good idea, especially if you have a function inside a function. However sometimes I think it makes code more readable. To omit parentheses is always optional.

I think adding back semi-colons is a good idea.

The following code is fine:

  foo with 1, 2, 3

I think it could be better if we could use a symbol as "with" alias. I don't know which symbols are avaible yet (few I guess). Some suggestions:

  foo · 1, 2, 3
  foo | 1, 2, 3
  foo ~ 1, 2, 3 

(Whitespaces could be significative)

If we don't pass any params to a function, instead of:

  bar()

we could code something like this:

  bar·
  bar|
  bar~

Maybe these sytanxes are a little weired. But I'd like to have a "with" keyword shortcut. Thank you for your reply, breuleux.

breuleux commented 9 years ago

I committed the changes with semicolons and commas (so f with 1, 2, 3 works, as of the latest commit). Perhaps surprisingly given the amount of code, it really didn't break much.

foo · 1, 2, 3 is Unicode and I'd rather not go there right now. | or ~ could work, but I'd rather keep them in reserve, e.g. | would be nice for piping, maybe ~ for partials, I'm not sure.

Really, the best candidate would be foo: 1, 2, 3. foo: already works like foo(), and foo: bar like foo(bar). Unfortunately, foo: 1, 2, 3 currently translates to foo((1, 2, 3)), and that's a tricky corner to get out of because a large number of macros work on this assumption.

Thanks for the feedback :)

ghost commented 9 years ago
foo: 1, 2, 3

It would be very very nice! :)

tacomanator commented 9 years ago

Just adding 2c here. I've used LiveScript (which like CoffeeScript allows omission of parens) for quite a while now and have come to the conclusion that, readability aside, [2] leads to less errors than [3]. Consider:

f 1, g 2, 3

Is that f(1,g(2,3)) or f(1,g(2),3)? Even knowing the rules well, it's a common source of errors.

Also I understand that foo: 1 already works but this syntax seems awfully similar to what people are used to as an object key. I could see easily mistaking this for an object key if not readying very carefully. I'm sure you must have had a reason, however, could you shed some light on why you decided to use = instead of : for object definition?

breuleux commented 9 years ago

In my criteria when designing Earl Grey, I rank consistency over familiarity, meaning that most of the time I will implement unfamiliar syntax if I feel it leads to a more consistent design. In my estimation, few popular languages put any thought into syntactic consistency, which is why EG will sometimes be "weird" (because I try to "correct" what I think they do wrong).

Now, to me, variable definition and object field definition are extremely similar operations: we bind a name to a value, in one case in the lexical scope, in the other case inside an object. Moreover, there is no common situation where it would be ambiguous whether we mean one or the other. So I can't see any good reason why there should be two different operators to do these incredibly similar, non-interfering things. So I pick one operator for definitions (=) and one operator for control structures (:). Surely this is a more consistent choice than Python, which uses the colon for object definitions, for control structures, and for variable annotations. It makes no sense.

I'm not alone in this choice, of course. Lua's table literals use the same syntax as EG.

tacomanator commented 9 years ago

I think that makes sense and I tend to agree that one need not be held back by convention where there is a good reason. Thank you for taking the time to write your thoughts.

Regarding : for control structures, my first thought us that function invocations take arguments but not control structures, and thus is : a consistent choice for function invocation? Consider using that syntax inside an actual control structure predicate:

;; won't work
if foo: 1:
   ;; do someting

;; will work
if foo(1):
   ;; do something

Are you pretty well set on : or open to other ideas? What about <|?

breuleux commented 9 years ago

It's an operator priority issue. : is right-associative.

;; will work
if (foo: 1):
   ;; do something

Control structures do take arguments, the idea is that x y: z is sugar for x(y, z) (more or less). A whole lot of control structures fall into the pattern of taking a condition or specification and a body to execute, which is why the sugar works well. Still, it is true that by convention the things to the right of the colon are usually statements and not arguments, so it would be a bit confusing to merge the notations (I personally would not use it).

Anyway, let's see...

foo <| 1, 2, 3

...I don't know, I think <| is kind of ugly. I might just stick with foo with 1, 2, 3. It already works and it's relatively easy to type.

tacomanator commented 9 years ago

Thanks. Personally I agree, as too many syntaxes that mean the same thing make a language quite complicated.

By the way I took <| operator from LiveScript. They also have a |> operator, which is pretty awesome. At first I thought the operators are ugly as well, but the directionality of them really makes sense when considered as a pair.

shadowhand commented 9 years ago

I think [2] is the right solution. I very much dislike the omission of parens in CoffeeScript, it makes code unreadable in my opinion.

breuleux commented 9 years ago

@tacomanator Yeah, there is some elegance to LiveScript's <| and |>. I'm pondering using | for |>, or perhaps for stream composition.

keithjgrant commented 9 years ago

I agree with @shadowhand -- I never like the omitted parens in CoffeeScript. Far too often I find I'm adding/removing chaining while editing code, which means adding/removing parens, which becomes tedious. Just leave them on. If you're going to use a symbol (other than just a space char), parens are by far the cleanest option listed here.