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

Improving MethodError message for anonymous functions #56325

Open
fonsp opened this issue Oct 25, 2024 · 4 comments
Open

Improving MethodError message for anonymous functions #56325

fonsp opened this issue Oct 25, 2024 · 4 comments
Labels
error messages Better, more actionable error messages good first issue Indicates a good issue for first-time contributors to Julia

Comments

@fonsp
Copy link
Member

fonsp commented Oct 25, 2024

Context: improving error experience for beginner Julia programmers.

In this example, you get a MethodError for your anonymous method (::var"#3#4"):

julia> vals = [5,6,10,20];

julia> accumulate(vals; init=0) do (old, new)
           old + new
       end
ERROR: MethodError: no method matching (::var"#3#4")(::Int64, ::Int64)
The function `#3` exists, but no method is defined for this combination of argument types.

Closest candidates are:
  (::var"#3#4")(::Any)
   @ Main REPL[2]:2

Stacktrace:
 [1] _accumulate!(op::var"#3#4", B::Vector{Union{}}, A::Vector{Int64}, dims::Nothing, init::Some{Int64})
   @ Base ./accumulate.jl:368
 [2] accumulate!(op::Function, B::Vector{Union{}}, A::Vector{Int64}; dims::Nothing, kw::@Kwargs{init::Int64})
   @ Base ./accumulate.jl:350
 [3] accumulate(op::Function, A::Vector{Int64}; dims::Nothing, kw::@Kwargs{init::Int64})
   @ Base ./accumulate.jl:291
 [4] top-level scope
   @ REPL[2]:1

The problem is that my do syntax used (old, new) (a single tuple argument) instead of old, new (two integer arguments):

julia> accumulate(vals; init=0) do old, new
           old + new
       end
4-element Vector{Int64}:
  5
 11
 21
 41

But the error is difficult to understand!

Ideas

I have seen this pattern more often (MethodError on an anonymous function that I defined), and I think we could make meaningful improvements to this error message! Ideas:

  1. Say "anonymous function": We can communicate better that it's about an "anonymous function". The name var"#3#4" is not so useful for beginners. (In the case of a do-block, it might even be non-obvious that you wrote a function.)
  2. Simplify "Closest candidates": Anonymous functions can only have one method (right?). So instead of showing "Closest candidates", the error can be stated in a simpler way:
The anonymous function has signature:
   (::Any)
but it was called with types:
   (::Int64, ::Int64)
  1. Number of arguments: the function was called with 2 arguments, but no method accepts that. When dispatch fails because of the number of arguments, I think this is the most helpful bit of debugging info available (like a sanity check), and we could say this explicitly in the error.
    It could be phrased as advice (when the situation is simple enough):
Tip: The function was called with 2 arguments, but it only accepts 1 argument.
@mikmoore
Copy link
Contributor

mikmoore commented Oct 25, 2024

Just a nitpick, but anonymous functions can have multiple methods. There might be a nicer way to add them, but this works at least:

julia> f = x -> x*x
#1 (generic function with 1 method)

julia> (::typeof(f))(x::Int) = -x

julia> f
#1 (generic function with 2 methods)

julia> f(3.0)
9.0

julia> f(3)
-3

But otherwise I agree that names like var"#3#4" are not very helpful, and your other points. Although they sometimes have the marginal value of distinguishing that var"#3#4" is not the same as var"#5#6".

@nsajko nsajko added the error messages Better, more actionable error messages label Oct 25, 2024
@fonsp fonsp added the good first issue Indicates a good issue for first-time contributors to Julia label Jan 10, 2025
@Bumblebee00
Copy link
Contributor

Hello everyone @nsajko @fonsp @mikmoore, this is my first time working on julia codebase and I'd like to tackle this issue! I will write here what I came up just to do a sanity check with someone more experienced.

So the code responsible for MethodError display is in the file base/erroshow.jl, and the code responsible of printing var"#3#4" is this:

function showerror(io::IO, ex::MethodError)
    # other code here...
        if is_arg_types
            print(io, "no method matching invoke ")
        else
            print(io, "no method matching ")
        end
        buf = IOBuffer()
        iob = IOContext(buf, io)     # for type abbreviation as in #49795; some, like `convert(T, x)`, should not abbreviate
        show_signature_function(iob, Core.Typeof(f))
        show_tuple_as_call(iob, :function, arg_types; hasfirst=false, kwargs = isempty(kwargs) ? nothing : kwargs)
        str = String(take!(buf))
        str = type_limited_string_from_context(io, str)
        print(io, str)
    # other code here...
end

Anonymous function and non anonymous ones have different types, as you can see here:

function f1(x)
    x+1
end

f2 = x->x+1

typeof(f1) # typeof(f1) (singleton type of function f1, subtype of Function)
typeof(f2) # var"#7#8"

so I thought we could check in showerror the type of the function, and if it's anonymous add this information to the error message. I tried implementing it locally and it seems to work:

julia> vals = [5,6,10,20];

julia> accumulate(vals; init=0) do (old, new)
                  old + new
              end
ERROR: MethodError: no method matching the anonymous function (::var"#2#3")(::Int64, ::Int64)
The function `#2` exists, but no method is defined for this combination of argument types.

Closest candidates are:
  (::var"#2#3")(::Any)
   @ Main REPL[2]:2

Stacktrace:
 [1] _accumulate!(op::var"#2#3", B::Vector{Union{}}, A::Vector{Int64}, dims::Nothing, init::Some{Int64})
   @ Base ./accumulate.jl:367
 [2] accumulate!(op::Function, B::Vector{Union{}}, A::Vector{Int64}; dims::Nothing, kw::@Kwargs{init::Int64})
   @ Base ./accumulate.jl:349
 [3] accumulate(op::Function, A::Vector{Int64}; dims::Nothing, kw::@Kwargs{init::Int64})
   @ Base ./accumulate.jl:290
 [4] top-level scope
   @ REPL[2]:1

causing the same error with a non anonymous function instead works as before:

julia> function f(x)
           x+1
       end
f (generic function with 1 method)

julia> accumulate(f, vals; init=0)
ERROR: MethodError: no method matching f(::Int64, ::Int64)
The function `f` exists, but no method is defined for this combination of argument types.

Closest candidates are:
  f(::Any)
   @ Main REPL[3]:1

Stacktrace:
 [1] _accumulate!(op::typeof(f), B::Vector{Union{}}, A::Vector{Int64}, dims::Nothing, init::Some{Int64})
   @ Base ./accumulate.jl:367
 [2] accumulate!(op::Function, B::Vector{Union{}}, A::Vector{Int64}; dims::Nothing, kw::@Kwargs{init::Int64})
   @ Base ./accumulate.jl:349
 [3] accumulate(op::Function, A::Vector{Int64}; dims::Nothing, kw::@Kwargs{init::Int64})
   @ Base ./accumulate.jl:290
 [4] top-level scope
   @ REPL[4]:1

So is this a legit method of implementing this? If yes I will continue implementing also the other suggestion about closest candidates, number of arguments and the tips, and write tests for everything.

@Bumblebee00
Copy link
Contributor

okay I did the pull request implementing also the other suggestions, let me know what you all think!

@Bumblebee00
Copy link
Contributor

I splitted the two features in two different pull request

inkydragon pushed a commit that referenced this issue Feb 26, 2025
If a MethodError arises on a anonyomous function, the words "anonymous
function" are printed in the error like so:
```julia
g=(x,y)->x+y
g(1,2,3)
```
```
ERROR: MethodError: no method of the anonymous function var"#5#6" matching (::var"#5#6")(::Int64, ::Int64, ::Int64)
The function `#5` exists, but no method is defined for this combination of argument types.

Closest candidates are:
  (::var"#5#6")(::Any, ::Any)
   @ Main REPL[4]:1
```

See the [original pull
request](#57319) and
[issue](#56325) #56325
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
error messages Better, more actionable error messages good first issue Indicates a good issue for first-time contributors to Julia
Projects
None yet
Development

No branches or pull requests

4 participants