Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Lang] Named (Optional) arguments #388

Closed
phated opened this issue Oct 15, 2020 · 5 comments · Fixed by #1623
Closed

[Lang] Named (Optional) arguments #388

phated opened this issue Oct 15, 2020 · 5 comments · Fixed by #1623
Assignees
Labels

Comments

@phated
Copy link
Member

phated commented Oct 15, 2020

This would open up some amazing APIs! I really like the ~something syntax, but I'm open to suggestions.

@ospencer
Copy link
Member

I'm cool with ~name for named arguments, but I've never been a huge fan of the (~x=?) syntax in function definitions. I'd wanna do some thinking around that.

@alex-snezhko
Copy link
Member

Would it be possible to allow all positional arguments to be used as named arguments? For example:

let f = (x, y, z) => ...

f(1, 2, 3) // using all positional
<-> f(z=3, y=2, x=1) // all named
<-> f(y=2, 1, 3) // allow mixture; since y is named then 1, 3 correspond to x, z (the missing args)

// possible syntax sugar
let x = 1, y = 2, z = 3
f(~z, ~y, ~x) == f(z=z, y=y, x=x)

This may or may not cause problems depending on how currying is done, although I don't think it should be a big issue unless I'm missing something

// let cf = x => y => z => ...
let cf = curry f

cf(1)(2)(3) == cf(x=1)(y=2)(z=3)
// cf(y=2)(...) not allowed since x is the first curried argument

Then for optional arguments maybe the OCaml-like ?x syntax could be used?

let f1 = (x, y, ?z) => ... // z=None by default
let f2 = (x, y, ?z=3) => ... // z=3 by default

f1(1, 2, z=Some(3))
f2(1, 2) == f2(1, 2, z=3)

Currying for this would be a bit trickier, although perhaps the curried function would somehow be able to maintain the list of optional arguments to be applied at any point before the function gets called

let cf = curry f2
cf(1)(2) // function gets called with z=3
cf(z=4)(1)(2) == cf(1)(z=4)(2) // optional argument can be given at any point before all non-optional arguments are given
cf(1)(2)(z=3) // illegal as cf(1)(2) would complete the argument list

Not sure how a curried function with all optional arguments would be handled though...

@ospencer ospencer self-assigned this Jan 14, 2023
@ospencer
Copy link
Member

I'm actually working on labeled arguments right now 🙂

I've thought about this! Where it gets a little tricky is making the whole system make sense in respect to required labeled arguments. Without required labeled arguments, it's all fine and your type signatures are actually a bit nicer, since the inferred type of a function like let join = (list, sep) => ... would be (list: List<String>, sep: String) -> String, you can call the function with positional arguments or name them if you like, e.g. join(sep="|", []).

Requiring a label doesn't feel as good. Let's say we use ocaml syntax, and we do let join = (list, ~sep) => ..., where sep must be called using the name. Now I could do join(~sep="|", list=[]) but now I've got two different ways to label the arguments and that's confusing.

But what if we just don't have required labels? What's nice about them is you can guarantee a specific look and feel for your APIs. Anytime someone calls join I want them to have to specify the name of sep; I might not want to see any join(v1, v2) in the wild. Not a showstopper for me, but something to consider.

One other change that would need to happen if we allowed any argument to be called by name is forced naming of all arguments. If I were to destructure an argument, I'd need to specify the name of that argument: let fst = (pair as (fst, _)) => fst which is pretty bleh. I suppose we could in those cases not infer a label if one isn't provided, but then it's an inconsistent experience of when you can supply an argument with a name.

I'm definitely open to some thoughts here since I think it would be cool to be able to supply any argument by name. I think it'd take quite a bit of workshopping to come up with something cohesive wrt destructuring and not requiring labels on every argument.

@ospencer
Copy link
Member

Also hilarious I said this more than two years ago:

I'm cool with ~name for named arguments, but I've never been a huge fan of the (~x=?) syntax in function definitions. I'd wanna do some thinking around that.

I had the same feeling now and have spent a considerable amount of time trying to come up with something better and have so far been unable 😛

@alex-snezhko
Copy link
Member

Hmm, those are all good points you bring up; it seems that there is compromise to be made in each approach although the ones I am most partial to are either

  • Not having any forced named arguments and taking the compromise of not allowing a name when the parameter is given as a pattern rather than a name
  • Going with the approach suggested by the issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
No open projects
Status: Done
Development

Successfully merging a pull request may close this issue.

4 participants