Building off of my comment in #1299, I think there are a couple things in function application which are still unfamiliar to those coming from JS and use an overloaded syntax which is confusing. The two main pain points I see are named arguments, and aliases. Since these concepts are foreign to JS, I propose using strong differentiating syntax for them so they are not overloaded or ambiguous to readers. In short - my proposed syntax would look something like:
let foo = (~a aa: option(int)=?, ~b bb=10) => 10;
Full proposal can be seen here: https://gist.github.com/rickyvetter/397140267ddcfd4bf76fa23ef0a5d127.
Note that we fall back to ~ to denote a named arg which will be familiar to OCaml and is not a widely used operator.
Other changes as part of this proposal:
Surrounding = and => around function argument definitions. This brings the syntax inline with ES6 arrow functions. I think the => to imply function is very important to explicitly show that this is a function as opposed to some sort of assignment. The leading = is cute because it makes most function signatures valid ES6 as well, but isn't really necessary.
I would love to see the ~ just like in Ocaml but I think it would make more sense to keep the colon instead of the equals sign?
e.g. ~a:10 as aa: int
I opted for the = so that : can remain un-overloaded as only used for types. = also falls in line with JS and Python default args and is symmetric with Python named arg setting, which are (imo) very similar actions of setting a specific param to a value.
Updated ordering to be consistent with OCaml, Flow and TS:
<~> name <: > type < as > alias < = > value
I'm not sold on the equals sign, but I too like the ~ from ocaml. Better separation between specifying a type and specifying a named argument will be a definite benefit to new users. We had a question on discord editorsupport today around this sort of thing.
Glad you like the ~. Why aren't you sold on the equals? And to clarify do you mean the ones that assign values to named arguments at the callsite?
@jordwalke @let-def do you think this proposal (or parts of it) make sense for the direction of Reason? Are these things I could implement easily? I have only basic knowledge of the parser and printer so I don't feel super comfortable doing larger changes, but things like the colon becoming a tilde seem like they would be easy enough.
Thanks for the proposal and thanks for using the proposal template which helps. I don't think the as keyword would be needed at all - if you just take the current syntax in master, and change the :label to ~label. So it ends up being something like let x = (~arg aliasedArg : typ = default) => ...
I only really liked the colon because it's so accessible on the keyboard. Supposedly the :lbl token resembled some common syntactic form from ruby. Any thoughts on @ symbol?
let x = (@arg) => ...
There are analogies with normal as though it's normally used a bit differently: it binds variables to expressions, while in this case it would be binding a variable to a label, which is more of a meta-level concept.
Normal use:
switch (getList ()) {
| [x, ..._] as list => x + List.length list
| [] => 34
};
The fact that labels and expressions aren't perfect analogies is a good point. There is also another syntax case which isn't handled in the gist where you want to use an actual as binding to an alias, which could be confusing with this newly added as:
let f = (~foo as {bar} as baz) => ...
I'm happy with just having space separation:
let f = (~foo {bar} as baz) => ...
/* or
* let f = (~foo ({bar} as baz)) =>
* if necessary
*/
I've updated the gist and example accordingly.
I think that the other change that should happen for consistency is bringing back the => following non-anonymous functions. It makes all function declarations consistent and visually distinctive from other forms of assignment. Will also be easier to refactor from anonymous to named functions.
Your example with only changing ~ in master syntax (so no changes to arrow or leading =) I believe would refmt to:
let x(~arg aliasedArg : typ = default) = ...
which definitely leads to a loss of familiarity coming from JS.
How about the symbol to be used. I don't know how much choice there is.
It seems that at least ~ and @ can be made to work.
Looking at a bunch of international keyboards, the position of ~ ranges from OK to not so OK.
Don't know about @ but I can assume that everyone has it in their muscle memory.
@ symbol would be fine as well. The loss of shared syntax with OCaml is unfortunate, but if the @ is more prevalent/accessible then well worth it.
In my opinion, there isn't much difference between reaching for the ~ or the backtick for polymorphic variants, in what concerns different keyboard layouts
@rickyvetter good point about the two different kinds of as which might be ambiguous/overlapping in some cases.
About let x(~arg) = ... vs. let x(~arg) =>
Personal Opinion:
I believe that the let name(arg) = form will confuse the heck out of JS developers, but Haskell/ML people will understand it. The let x(y) = ret form kind of bugs me because it has the same form as a totally different concept (binding pattern matching): let Some x = Some "hi" binding x to "hi". That's totally different than creating a function let x(y) = ret, yet it shares the form. So, in short I agree that the arrow makes it more consistent. Not everyone agrees though.
Observation:
The let x y => retValue form was more important in the previous syntax because we didn't have ES6 fat arrow syntax. With fat arrows, you can more easily do let x = y => retValue, but with the old syntax it was more verbose with let x = fun y => retValue, so the let x y => retValue shorthand was more necessary.
Observation:
With the new syntax, we might be able to refmt to something even better than either form! Now, the following is unambiguous:
let x(y) {
returnValue
};
I wouldn't always want to format this way, but only if the return expression is so long that a break occurs. So your question about how to format when the return value doesn't break still stands.
For consistency and ease of refactoring I think that
let x = (y) => ret;
and
let x = (y) => {
ret
};
is a really good compromise. If we refmt the latter to
let x(y) {
ret
};
that's gravy. But I think the first two examples should still parse as valid code.
The distinction between using = vs. => vs. { for functions feels confusing. => looks like it's used for inline arrow functions or matching with switch. Having let-based functions use = instead of => seems inconsistent.
let x = y => retValue
is clear as to what it's doing.
let x(y) => retValue
feels like a shorthand to the previous line, but potentially confusing because (y) => retValue is a legitimate arrow function form.
let x = y => {
retValue
}
feels like a long-form or multi-statement form.
let x(y) {
retValue
}
feels like a shorthand to the multi-statement form.
Done for now
Most helpful comment
For consistency and ease of refactoring I think that
and
is a really good compromise. If we
refmtthe latter tothat's gravy. But I think the first two examples should still parse as valid code.