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

interpret call f(; x, y) as f(; x=x, y=y) #29333

Closed
KristofferC opened this issue Sep 23, 2018 · 43 comments
Closed

interpret call f(; x, y) as f(; x=x, y=y) #29333

KristofferC opened this issue Sep 23, 2018 · 43 comments
Labels
keyword arguments f(x; keyword=arguments) kind:feature Indicates new feature / enhancement requests needs decision A decision on this change is needed

Comments

@KristofferC
Copy link
Sponsor Member

KristofferC commented Sep 23, 2018

Sorry if this is a dup but I couldn't find one.

I find it quite common that one calls functions with keyword arguments like

f(; x=x, y=y, this_was_a_bit_long=this_was_a_bit_long)

My proposal is to write this as

f(;x, y, this_was_a_bit_long)

and to parse that to the first example.

Since

f(; x, y)

is already a syntax error, I think this should be possible?

@KristofferC KristofferC added compiler:lowering Syntax lowering (compiler front end, 2nd stage) keyword arguments f(x; keyword=arguments) labels Sep 23, 2018
@KristofferC KristofferC changed the title Lower call f(; x, y) to f(; x=x, y=y). Parse call f(; x, y) to f(; x=x, y=y). Sep 23, 2018
@KristofferC KristofferC added parser Language parsing and surface syntax and removed compiler:lowering Syntax lowering (compiler front end, 2nd stage) labels Sep 23, 2018
@KristofferC KristofferC changed the title Parse call f(; x, y) to f(; x=x, y=y). Parse call f(; x, y) as f(; x=x, y=y). Sep 23, 2018
@yuyichao
Copy link
Contributor

This is implementable as a macro if you really need it and it doesn't enabling doing anything that couldn't be done while making the syntax much more confusing (even more so than the significance of ; in the function definition).

It'll also give f(; :a=>b) and x = :a=>b; f(; x) completely different meanings.

@KristofferC
Copy link
Sponsor Member Author

KristofferC commented Sep 23, 2018

This is implementable as a macro

Of course, the whole point of introducing this as syntax is to avoid a macro.

It'll also give f(; :a=>b) and x = :a=>b; f(; x) completely different meanings.

f(a=b) and x = a=b; f(x) already have completely different meanings. I don't think people find that very confusing.

@KristofferC
Copy link
Sponsor Member Author

@yuyichao
Copy link
Contributor

f(a=b) and x = a=b; f(x)

For start, = is not an operator so that's very different.

And it did cause issues with macros in corner cases.

@ararslan
Copy link
Member

My biggest concern over this is that really it shouldn't matter what name you've given to a variable, but in this case you have to be careful if you rename anything, because it can subtly change behavior when using keyword arguments. Instead of just being able to rename your variable, you have to comb through all potential uses of it in this context, and if you miss one you get an unsupported keyword argument error (if you're lucky) or a different result entirely (if you aren't).

@KristofferC
Copy link
Sponsor Member Author

KristofferC commented Sep 23, 2018

My biggest concern over this is that really it shouldn't matter what name you've given to a variable, but in this case you have to be careful if you rename anything, because it can subtly change behavior when using keyword arguments.

This argument is a bit late, you do already have to comb through potential uses, e.g.

function f()
    s = 0
    let
        a = 3
    end
    return s
end

Renaming the variable a to some other name might give you a completely different behavior (e.g by calling it s).

@yuyichao
Copy link
Contributor

yuyichao commented Sep 23, 2018

Renaming the variable a to some other name might give you a completely different behavior (e.g by calling it s).

This isn't a defence at all. It's all about the "subtleness" of the change and more precisely, the localness of the effect.

Of course you can't rename a variable that override another one without any observable effect. You don't even need that let, a = 1; b = 2 will obviously be different if you rename b to a. However, the effect is caused by something that is in the same scope (by which I include parents and children ones). You can easily infer locally whether the override can happen or not. This is also why macro hygiene is so important, you can't expect the user to avoid naming conflict with name introduced in a invisible way by the macro author.

Edit: The effect of renaming a variable in this PR though, is not local. It's observable from not just the scope you see, but also from the callee scope. A macro can serve as a clear marker of customized evaluation rule so it won't be too bad for the reader. The syntax proposed here, however, allows this to happen in a much more subtle way.

@KristofferC KristofferC added the status:triage This should be discussed on a triage call label Sep 24, 2018
@JeffBezanson
Copy link
Sponsor Member

This is already implemented here, commented out:

((symbol? el) ;; x => x = x

It's done during lowering, since f(; x) and f(; x=x) of course parse differently.

Given the precedent in Rust and other languages with some kind of object notation (e.g. C#, VB, JS) I think this is a reasonable feature. Some commentary from David Anthoff: #22194 (comment)

@JeffBezanson JeffBezanson added needs decision A decision on this change is needed and removed parser Language parsing and surface syntax labels Sep 24, 2018
@yurivish
Copy link
Contributor

I work extensively in Javascript/Typescript, where this is an extremely useful feature.

It was originally prototyped in CoffeeScript (a compile-to-Javascript language with terser syntax) and then folded into Javascript proper in Javascript ES6.

If anyone is curious to read the discussion thread where this feature was proposed for Rust, here it is: rust-lang/rfcs#1682

@yuyichao
Copy link
Contributor

extremely useful feature

I don't believe you can call something useful if it doesn't allow you to do anything new. I certainly agree it's "convinient" in some cases but that's also a feature of all the other syntax sugars that makes writing the code slightly easier but reading the code much harder.

Given the precedent in Rust and other languages with some kind of object notation (e.g. C#, VB, JS)

I don't know Rust very well to know for sure but the other examples certainly doesn't have macros similar to what julia have. (Even the rust one doesn't seem as flexible as julia's and it certainly put less work on the macro writers to deal with the syntax details.) So while rust has macro implementation as alternative, other languages mentioned doesn't.

From the linked rust RFC, the alternative syntax is pun! { SomeStruct { field1, field2 } } in rust vs @pun! SomeStruct(field1, field2) in julia. I would agree with their RFC that the additional {} around the expression certainly makes it much less convinient and readable but these are not or much less of an issue for julia given that no parentheis is needed.

@StefanKarpinski
Copy link
Sponsor Member

I don't believe you can call something useful if it doesn't allow you to do anything new.

This is an absurd argument. If this was true we'd all just write assembly code.

@StefanKarpinski
Copy link
Sponsor Member

StefanKarpinski commented Sep 24, 2018

I think the concern about this feature is essentially this. Currently, if you want to rename a variable to a new unique name, all you have to do is find every instance of it, following the scope rules, and change all those instances. (If you want to rename a variable to something else, you have to worry about name collisions, but that's complicated, so let's ignore it.) With this feature, that's no longer true, you have to also expand any instance var in this syntax from f(; var) to f(var=var) first and then you can rename the variable (but not the keyword). That doesn't seem like a show-stopper to me, but that is the complication. I think on the balance, I'm in favor of having this feature.

@JeffBezanson
Copy link
Sponsor Member

Yes, agreed. I think enough other (very widely used) languages have had this feature for long enough to safely conclude that it's not a disaster.

@StefanKarpinski StefanKarpinski added the kind:feature Indicates new feature / enhancement requests label Sep 24, 2018
@yuyichao
Copy link
Contributor

yuyichao commented Sep 24, 2018

If this was true we'd all just write assembly code.

Well, writing assembly code requires significant knowledge of the hardware, is not portable, does not provide features (restrictions) to allow concepts of interfaces so that different code (modules) could interact with each other knowing what other code would use it, etc. All of these are enabling new functionalities that's not doable with assembly code. If theses were doable with macros on top of assembly language, then yes I would totally agree that we don't need any other languages. It seems that this is not the case though, implementation of many of these requires significant restrictions to what the user is allowed to do.

I think the concern about this feature is essentially this. Currently, if you want to rename a variable to a new unique name, all you have to do is find every instance of it, following the scope rules, and change all those instances.

No, not at all, that's not even my objection. I wrote #29333 (comment) only because I think that is a minor issue and the defence against it is not valid.
What's more important objection for me, and why I bring it up first, is #29333 (comment), i.e. it breaks a simple rule of evaluation and makes it much harder for macros writers. (more below.)

enough other (very widely used) languages

None of them has as flexible macro as ours. Only one of them has to make a choice in order to have that function at all (since others don't have macros as alternative).
More importantly though, none of them are using a syntax that's ambiguous. None of them are using a syntax that would otherwise construct an object that gives it completely different behavior for this, while in no other case makes adding temporary variable a syntactically significant change. (i.e. none of them allow a = <...>; f(... a ...) and f(... <...> ...) at the same time while having them mean different things).

This significance of creating temporary variables is basically my biggest objection. I see the variable renaming as a readability and maintainability issue that's only a minor concern since it'll only give a harder life for people that want to use it, I'll be ok with it if that was the only issue. The significance of adding local variable, however, is not like that. All macro writers now have to be careful when splicing expressions and when creating local variables since they might change the semantics of the code they generate.

@yuyichao
Copy link
Contributor

yuyichao commented Sep 24, 2018

In another word, I'll be ok with this if we disallow f(; :a=>b).

Edit: note that this does not imply I'll like this or want to use it in this case, neither does it mean I think this is a worthwhile trade off, just that the two features are in conflict.

@StefanKarpinski
Copy link
Sponsor Member

All macro writers now have to be careful when splicing expressions and when creating local variables since they might change the semantics of the code they generate.

This is always true?

@HarrisonGrodin
Copy link
Contributor

Ref: #27649.

@JeffBezanson
Copy link
Sponsor Member

The thing is, the context f(; <here>) is already not a normal evaluation context --- you can't just put any expressions there and have them evaluated. So macros already have to pay close attention to what's spliced there.

@KristofferC
Copy link
Sponsor Member Author

@HarrisonGrodin oh, wow, that's an exact dup.

@yuyichao
Copy link
Contributor

yuyichao commented Sep 27, 2018

So macros already have to pay close attention to what's spliced there.

It's one thing if you do this and get an error (current behavior), it's much worse if you do this and get working code with completely different result (proposed behavior).

This is always true?

And no. Creating local variable in general should not change the semantics.

@mauro3
Copy link
Contributor

mauro3 commented Nov 2, 2018

Presumably (; i.j.k.SepalLength) would be (; SepalLength=i.j.k.SepalLength).

@ararslan
Copy link
Member

ararslan commented Feb 1, 2019

I think I would like this more if the named context was required for any calls with keyword arguments, e.g. f(a, b; x=1) instead of f(a, b, x=1). That obviously isn't a change we could make until 2.0, but I'm willing to bet no one agrees anyway...

@yurivish
Copy link
Contributor

yurivish commented Feb 2, 2019

I leverage the fact that keyword arguments are not required to be at the end pretty strongly in my Hyperscript package — In HTML attributes are specified at the start, and this lets you write <a href="https://juliacon.org/2019">Click here!</a> as a(href="https://juliacon.org/2019", "Click here!").

@StefanKarpinski
Copy link
Sponsor Member

Another argument in favor of this feature: what else would it mean? Even if we decide later that we want a different way to express this, what else would this particular syntax mean besides this?

@tpapp
Copy link
Contributor

tpapp commented Feb 6, 2019

FWIW, I have been experimenting with a pair of macros for a few months now --- one that allows deconstructing dispatch on named tuple arguments and turned out to be very brittle and useless (because of matching the exact ordered set of names), but the other is

@eponymtuple(a, b, c = 3)

which turns into

(a = a, b = b, c = 3)

I find it very useful to organize code in the following situations:

  1. I am already using NamedTuples to pass around model parameters, but occasionally need to modify a few parts as I keep transforming the model,
  2. I use a complicated struct with many fields to describe a model, so the only sensible interface is the one defined by Base.@kwdef, and splatting this after the ; saves a lot of typing and makes code much easier to read.

I can of course live with a macro, but adding this to Base would make a lot of sense from my POV.

@tomyun
Copy link
Contributor

tomyun commented Jan 7, 2020

Is there a chance that this feature gets released with 1.x in the near future?

@JeffBezanson
Copy link
Sponsor Member

There's a chance of it; the main problem is this seems to be somewhat controversial, with many in favor but several opposed.

@ararslan
Copy link
Member

ararslan commented Jan 7, 2020

My concerns over this as a general feature still stand, but I've definitely felt many cases lately where it would be quite convenient.

@StefanKarpinski
Copy link
Sponsor Member

StefanKarpinski commented Jan 7, 2020

The more I've thought about this, the more I'm in favor of f(; x, y) for f(; x=x, y=y) and (; x, y) for (; x=x, y=y) aka (x=x, y=y). It's all nice and consistent and useful for both function calls and named tuples. When argument/field names are significant, it's just very common and convenient to use those same names for the variables that holds the corresponding values.

@quinnj
Copy link
Member

quinnj commented Jan 7, 2020

I thought I had already commented this, but it looks like I never did; I'm also very much in favor of this. Having done a bunch of javascript/typescript lately, it's widely used and pretty handy; I've grown to really enjoy it. I also think it's a pretty "natural" feature; like, it's pretty easy to guess what it means just by looking at it (in a namedtuple or keyword arg context).

@tknopp
Copy link
Contributor

tknopp commented Jan 9, 2020

I also wanted this feature several times in the past and would be in favor if this moves forward. Without this feature I often try to abbreviate variable names or even abbreviate the keyword name in order to not increase the line length of a function call.

@JeffBezanson
Copy link
Sponsor Member

I'll start on a PR.

I just realized one minor inconsistency is that function f(; x) already has a meaning, which is to make keyword arg x required, not x=x. I don't think it's a show-stopper though.

@ararslan
Copy link
Member

ararslan commented Jan 9, 2020

Function definitions are already different from call sites anyway, so I'd agree that it isn't a showstopper, but it is a bit unfortunate.

@StefanKarpinski
Copy link
Sponsor Member

I don't really think that's so bad. Even if function f(; x) meant function f(; x=x), that's a bit weird in that it creates a new binding for x inside of the function to whatever the outer x is. That's sometimes useful to do, I guess, but mainly only in global scope.

@quinnj
Copy link
Member

quinnj commented Jan 9, 2020

I thought the canonical way to do that was f(; x=error("hey, you need to pass x")

@ararslan
Copy link
Member

ararslan commented Jan 9, 2020

Not since #25830.

JeffBezanson added a commit that referenced this issue Jan 9, 2020
- `x` implies `x=x`
- `a.b` implies `b=a.b`

closes #29333
JeffBezanson added a commit that referenced this issue Jan 10, 2020
- `x` implies `x=x`
- `a.b` implies `b=a.b`

closes #29333
@tpapp
Copy link
Contributor

tpapp commented Jan 10, 2020

Given that the feature suggested is possible to implement in a package with a macro (eg jw3126/EponymKeywordSyntax.jl), I am not sure it should be in Base.

As much as I find the feature useful, I think that making this transformation only when explicitly requested would be the best approach for now.

JeffBezanson added a commit that referenced this issue Mar 19, 2020
- `x` implies `x=x`
- `a.b` implies `b=a.b`

closes #29333
oxinabox pushed a commit to oxinabox/julia that referenced this issue Apr 8, 2020
ravibitsgoa pushed a commit to ravibitsgoa/julia that referenced this issue Apr 9, 2020
KristofferC pushed a commit that referenced this issue Apr 11, 2020
- `x` implies `x=x`
- `a.b` implies `b=a.b`

closes #29333
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
keyword arguments f(x; keyword=arguments) kind:feature Indicates new feature / enhancement requests needs decision A decision on this change is needed
Projects
None yet
Development

No branches or pull requests