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

Keyword arguments affect methods dispatch #9498

Open
yuyichao opened this issue Dec 30, 2014 · 15 comments · Fixed by #24422
Open

Keyword arguments affect methods dispatch #9498

yuyichao opened this issue Dec 30, 2014 · 15 comments · Fixed by #24422
Assignees
Labels
bug Indicates an unexpected problem or unintended behavior docs This change adds or pertains to documentation keyword arguments f(x; keyword=arguments) needs decision A decision on this change is needed types and dispatch Types, subtyping and method dispatch

Comments

@yuyichao
Copy link
Contributor

So this is an issue that I find after figuring out how the keyword arguments are currently implemented in Julia. It will probably not be an issue anymore if #2773 is implemented.

The document on methods says

Methods are dispatched based only on positional arguments, with keyword arguments processed after the matching method is identified.

However, method dispatch actually behaves differently when doing a function call with or without keyword arguments. i.e.

julia> function f(::Integer)
       2
       end
f (generic function with 1 method)

julia> function f(::Number; kw...)
       1
       end
f (generic function with 2 methods)

julia> f(1)
2

julia> f(1; a = 2)
1

What happens here is that f.env.kwsorter only has one method defined and therefore when calling with keyword argument, f(::Integer) does not participate in method dispatch.

IMHO, there are several possible ways to fix it,

  1. Fix the document to include this behavior. This should be the easiest fix but will probably make the whole keyword argument/optional argument/multiple dispatch more confusing especially for someone who does not know how it all works behind the scene. (It's already quite confusing/surprising that anonymous function does not support keyword argument for someone (like me) that expects python-like keyword argument implementation.)

  2. Having an entry (that just throw an error) in env.kwsorter even for methods that does not take keyword arguments. This can also avoid the following confusing abuse of overriding method

    julia> function f(::Number; kw...)
           1
           end
    f (generic function with 1 method)
    
    julia> function f(::Number)
           2
           end
    f (generic function with 1 method)
    
    julia> f(1)
    2
    
    julia> f(1; a = 2)
    1

    This is probably the easiest way to fix the code and is consistent with the best long term behavior.

  3. Fix Keyword arguments for anonymous functions #2773 and let the method themselves handle keyword argument dispatch. Since Keyword arguments for anonymous functions #2773 is on 1.0 milestone, hopefully this will be eventually be implemented.

@ihnorton ihnorton added docs This change adds or pertains to documentation needs decision A decision on this change is needed labels Dec 30, 2014
@ihnorton ihnorton added the types and dispatch Types, subtyping and method dispatch label Jan 30, 2015
@vtjnash
Copy link
Sponsor Member

vtjnash commented Mar 27, 2016

see also #4469 (comment)

@JeffBezanson
Copy link
Sponsor Member

I think we should add keyword arguments to the calling convention, replacing the existing kwsorter-based implementation.

Currently, defining a keyword method actually defines three methods:

  1. A method with a hidden name accepting all positional and keyword arguments, containing the full method code.
  2. A method for the function actually being defined, accepting only positional arguments and passing default keyword arg values to method (1).
  3. A method of the function's kwsorter function that does sorting and calls method (1).

My proposal merges methods (2) and (3):

  1. Keep method (1).
  2. Add a method like the existing method (2), but that also contains an Expr(:kwargs) to access a keyword argument container passed as a pointer via the calling convention, and then does sorting.
  3. jl_call_method_internal and its codegen throw an error if no kwarg-accepting entry point is available for a given kwarg call.
  4. Add Expr(:kwcall, ...) that has a slot for a keyword arg container.
  5. When inlining runs on such an expression, it substitutes the passed container for Expr(:kwargs) and will then be able to specialize the sorting code.

It would be possible to get the performance aspect of this just by passing a different kind of container to our existing kwsorter, and changing its lowering to make it easier to specialize. However our current implementation is also very complex, and we'd still need a separate fix for the semantic issue described here. Having 2 methods instead of 3 will also make reflection easier. So I think this redesign should be considered.

@martinholters
Copy link
Member

This shouldn't have been closed, yet, I guess. Or did I miss something?

@martinholters martinholters reopened this Nov 2, 2017
@JeffBezanson
Copy link
Sponsor Member

Will have to punt on this for 1.0.

@sbromberger
Copy link
Contributor

taqtiqa-mark added a commit to taqtiqa-mark/julia that referenced this issue May 14, 2019
Added an example of issue JuliaLang#9498 - since it is not scheduled to be fixed until Julia 2.0.
Kept the example of ambiguous/last-seen default code being run because that is the core
use case for default values.
simeonschaub added a commit that referenced this issue Jan 8, 2021
Not sure why this worked before #37461, perhaps #9498?
simeonschaub added a commit that referenced this issue Jan 8, 2021
Not sure why this worked before #37461, perhaps #9498?
simeonschaub added a commit that referenced this issue Jan 8, 2021
Not sure why this worked before #37461, perhaps #9498?
KristofferC pushed a commit that referenced this issue Jan 9, 2021
Not sure why this worked before #37461, perhaps #9498?

(cherry picked from commit ca07546)
KristofferC pushed a commit that referenced this issue Feb 1, 2021
Not sure why this worked before #37461, perhaps #9498?

(cherry picked from commit ca07546)
ElOceanografo pushed a commit to ElOceanografo/julia that referenced this issue May 4, 2021
@cossio
Copy link
Contributor

cossio commented Jan 16, 2022

Can this behavior be seen as a bug? Then fixing it is not a breaking change.

@Moelf
Copy link
Sponsor Contributor

Moelf commented Sep 2, 2022

I'm pretty sure this is a bug, where should one starts to fix this?

staticfloat pushed a commit that referenced this issue Dec 23, 2022
Not sure why this worked before #37461, perhaps #9498?

(cherry picked from commit ca07546)
@LilithHafner LilithHafner added the bug Indicates an unexpected problem or unintended behavior label Sep 1, 2023
@LilithHafner
Copy link
Member

A path to fixing this would be making a PR that fixes it and running nanosoldier to see how much folks depend on this strange behavior.

@Moelf
Copy link
Sponsor Contributor

Moelf commented Sep 1, 2023

A path to fixing this would be making a PR that fixes it

"in order to fix it you just fix it"? I think myself from 12 months ago was asking which file should I change to actually fix it, not the logistics after fixing it. There's a fair chance this is way above my ability grade, if anyone knows how and doesn't have time to elaborate, please just fix it, this seems pretty bad we didn't treat it with urgency for 9 years.

@LilithHafner
Copy link
Member

Oh, lol, my bad. I was thinking about the social and ecosystem factors, not technical.

@Tortar
Copy link
Contributor

Tortar commented Jul 25, 2024

This should be considered a correctness bug

@LilithHafner LilithHafner removed this from the Potential 2.0 milestone Jul 25, 2024
@matthias314
Copy link
Contributor

There are actually functions in Base that depend on this incorrect behavior: For example,

function all(B::BitArray)

does not support keyword arguments. That all(trues(8); dims=:) doesn't throw an error is because the generic method

all(a::AbstractArray; dims=:) = _all(a, dims)

kicks in. Same for any, maximum and minimum withBitArray argument.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Indicates an unexpected problem or unintended behavior docs This change adds or pertains to documentation keyword arguments f(x; keyword=arguments) needs decision A decision on this change is needed types and dispatch Types, subtyping and method dispatch
Projects
None yet
Development

Successfully merging a pull request may close this issue.