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

Document function argument evaluation order #24603

Closed
wants to merge 4 commits into from
Closed

Document function argument evaluation order #24603

wants to merge 4 commits into from

Conversation

yurivish
Copy link
Contributor

I asked @JeffBezanson about this at the latest Julia meetup in Cambridge. If I understood correctly, starting with 0.7 the order of function argument evaluation is always lexically left to right.

I asked @JeffBezanson about this at the latest Julia meetup in Cambridge. If I understood correctly, starting with 0.7 the order of function argument evaluation is always lexically left to right.
it turns out this is subtle – it looks like the explicitly passed arguments are evaluated first, left to right, followed by the default values for missing arguments.
@JeffBezanson JeffBezanson added the docs This change adds or pertains to documentation label Nov 14, 2017
@@ -328,6 +328,22 @@ Notice the extra set of parentheses in the definition of `range`.
Without those, `range` would be a two-argument function, and this example would
not work.

## Argument evaluation order

All arguments passed to a function are evaluated in lexical left-to-right order.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to show an example with keyword arguments as well. (#23104 was where this changed IIRC.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example is also very slightly misleading --- the reason d default gets printed after is that it actually happens in a subsequent method call. Due to the way optional arguments work, that will also always be lexically later, but it's technically a different reason.

Copy link
Contributor Author

@yurivish yurivish Nov 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point. I tried to pare this example down from a more complicated one with even more subtle behavior involving specifying keyword arguments out of order...

Is it fair to say that the rules can be summarized as: first evaluate all passed arguments from left to right wrt. the order at the call site, and then evaluate all default values from left to right wrt. the order in the method definition, with all defaults being evaluated in a separate method call due to the mechanics of default arguments?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's right. Really, every case is intuitive except maybe f(a, b=1, c, d=2, ...) where keyword and positional arguments are mixed.

Copy link
Contributor Author

@yurivish yurivish Nov 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelatedly, I wonder if we should call them "named arguments" for consistency with named tuples. There's precedent for that nomenclature, though both Python and Ruby opt for "keyword argument" instead.

https://en.wikipedia.org/wiki/Named_parameter

@cstjean
Copy link
Contributor

cstjean commented May 15, 2019

I found an interesting counter-example to left-to-right evaluation, because of setindex!'s lowering.

julia> a = zeros(10); i=1; a[i+=1] = (i+=1); maximum(a)
2.0

julia> a = zeros(10); i=1; a[i+=1] += (i+=1); maximum(a)
3.0

@StefanKarpinski
Copy link
Member

The lowering could be changed so that the evaluation is syntactic left-to-right, which might be better.

@cstjean
Copy link
Contributor

cstjean commented May 15, 2019

Then you could not write methods like Base.setindex!(arr::MyArray, value, indices...) = ...

@StefanKarpinski
Copy link
Member

Why wouldn't you be able to write methods like that?

@cstjean
Copy link
Contributor

cstjean commented May 16, 2019

Nevermind, I thought you suggested changing the order of the setindex! function arguments to setindex!(a, key, value), but that's silly. It didn't occur to me that lowering could just eval the key first.

@JeffBezanson
Copy link
Member

Being pedantic, assignment syntax is not function call syntax, so the same rule does not necessarily apply. I agree it would be better to change it, though that would be breaking.

@StefanKarpinski
Copy link
Member

Being pedantic, assignment syntax is not function call syntax, so the same rule does not necessarily apply.

Of course, it doesn't have to follow the same rule but consistently doing left-to-right evaluation in syntactic order is probably the least surprising thing we can do where it's possible to do so.

@martinholters
Copy link
Member

Bonus material: When is the iteration of argument splatting evaluated? That is, in f(x..., g()), what is happening first, iteration of x or calling g()? At the moment it's the latter, but evaluating left-to-right might mean "evaluating" x... first. (I'm posting in part because I'm experimenting with changes to the handling of splatting which might change the evaluation order here and I wonder how bad that would be.)

@cossio
Copy link
Contributor

cossio commented May 2, 2020

Keyword arguments are evaluated in the order in which they appear in the function definition? Or the order in which they appear at the call site?

Also, it would be nice to merge this.

@vtjnash
Copy link
Member

vtjnash commented Apr 19, 2021

Would you be up for finishing it? Here's an example I put together to show the combinations that you can use:

julia> f(a, b, c...=println("Optional c's after b=$b"); D=println("kw D"), kwargs...) = println("$a & $b & $c & $D & $kwargs")
f (generic function with 5 methods)

julia> (println("f"); f)(println("A"), B=println("B"), println("C"));
f
A
B
C
Optional c's after b=nothing
kw D
nothing & nothing & (nothing,) & nothing & Base.Pairs(:B => nothing)

julia> (println("f"); f)(println("A"), B=println("B"), println("C"), D=println("D"), println("E"));
f
A
B
C
D
E
nothing & nothing & (nothing,) & nothing & Base.Pairs(:B => nothing)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs This change adds or pertains to documentation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants