JuliaLang / julia

The Julia Programming Language
https://julialang.org/
MIT License
45.77k stars 5.49k forks source link

More helpful MethodErrors for deliberately unimplemented methods #7512

Open ivarne opened 10 years ago

ivarne commented 10 years ago

One of the first problems you stumble upon when learning Julia, is the MethodError. When using the standard library, the reason is usually that you are doing something wrong, but the only thing the users sees is ERROR: no method +(ASCIIString, ASCIIString). It would be very nice if the user would get a string with alternative suggestions.

Currently Exceptions are printed with the help of showerror(io::IO, err::MethodError) in replutil.jl

We could have something like a table for lookup:

deliberatly_unimplemented_methods = [
(:+, (String, String), "Use * for string concatenation"),
(:max, (AbstractArray,), "Use maximum to find the largest element in an array"),
]

, but maybe we can find a better solution that also captures varargs functions.

samuelcolvin commented 10 years ago

gg discussion this came from

This makes a lot of sense. Could it also be used for depreciated functions? Right now I think they're still implemented but throw an exception or warning.

It should also be available for modules, so packages can point out the replacements for old functions.

In fact if all modules could implement deliberatly_unimplemented_methods someone could come along and write for example a "MatlabHelper" package that just defined lots of matlab functions that are not available in Julia with advice on equivalents.

ivarne commented 10 years ago

Deprecated functions usually just prints a warning, and then perform what the old function was supposed to do. There might be something said for keeping a warning here, when the deprecation is fully removed though.

It should definitely be extensible for modules. Would it be enough to just document how to push! more suggestions onto the array?

tpapp commented 10 years ago

IMO encountering minor differences in surface syntax or function names is entirely natural when learning a new language. I am not sure that hints like this belong in the language proper, especially since there are zillion other languages out there from which users can come to Julia, and it is nearly impossible to provide hints to everyone. For example, a former R user is likely to encounter difficulties different from someone coming from Python, etc.

Wiki pages which enumerate differences between Julia and another languages would be more useful IMO. Eg similar to the table at the end of http://norvig.com/python-lisp.html

StefanKarpinski commented 10 years ago

+1 for this general approach.

JeffBezanson commented 10 years ago

I love the idea of deliberately unimplemented methods. It would make the information from applicable and method_exists far more useful.

ivarne commented 10 years ago

@tpapp It depends on how you look at it, but I would not consider replutils.jl part of the Julia language. Currently lots of tooling has found its way into the Base folder, and there is an open issue about cleaning it up. It is somewhat stuck because of technical limitations (system image) and a desire to have everybody use pretty much the same system when debugging issues.

Teaching language philosophy and differences is definitely not the target of this feature, but I don't think there are many functions where former R and Python users have different intentions when trying to call a undefined method in Julia.

It is also possible to save some memory by not lazy loading this list, so I don't think there are going to be problems when 10 languages add their entire standard library with suggestions for Julia alternaltives.

simonster commented 10 years ago

I'm not sure this needs to be extensible from the start, but making this extensible seems like another case of #3988 (see also discussion in #5572).

samuelcolvin commented 10 years ago

It should definitely be extensible for modules. Would it be enough to just document how to push! more suggestions onto the array?

Sounds fine to me.

@tpapp, I see what you mean, and I guess this is the difference between the purist and the popularist. Personally I want Julia to be popular and I think smoothing the initial "bump" of changing language is one of the most important ways of doing this.

I gave an introductory presentation on Julia the other day and one of the thinks people liked most was what they got "in the box", eg.;

Strictly none of these are "part of the language" but it's convenient for them to come with it.

Remember, the biggest reason (by far) that people use matlab is the IDE that comes with the language, not the language.

StefanKarpinski commented 10 years ago

Strictly none of these are "part of the language" but it's convenient for them to come with it.

One of the important things about shipping with something is that it tends to actually work, whereas add-ons have a much lower rate of working.

Aerlinger commented 10 years ago

I really like the idea of having hints, but it seems a bit messy to define these values directly in the code. Perhaps there should be a fixture file (.method_hints?) that defines a list of these methods:

Each row of the .method_hints file would contain a tab-delimited signature of the method to relay the error:

For example:

:+      (String, String)     "Use * for string concatenation"
:max    (AbstractArray,)     "Use maximum to find the largest element in an array"
ivarne commented 10 years ago

@Aerlinger That saves some typing, but I don't think a special purpose parser makes things less messy. It will also be a new syntax to learn (even though it is intuitive). Julia code can also be placed in a separate file and loaded on demand.

While we wait for someone to implement something like this, we should probably collect more examples on commonly missing methods. The format you use, can be treated as a vote.

simonster commented 10 years ago

Per #7686, it would also be great to print "closest" matching methods for general MethodErrors, although defining the metric is going to be difficult.

jhasse commented 10 years ago

This is how I think it would be ideal to display:

f(x::Float64, y::Float64) = println("ff")
f(x::Int, y::Int) = println("ii")
f(x::Float64, y::Int) = println("fi")

f(1, 1.0)
ERROR: `f` has no method matching f(::Int64, ::Float64)
Closest candidates are:
    f(::Float64, ::Float64)
      ^~~~~~~~~
    f(::Int, ::Int)
             ^~~~~
    f(::Float64, ::Int)
      ^~~~~~~~~  ^~~~~

These underlines should indicate which arguments don't match.

Even better would be if this utilizes colored output: The wrong types are printed red and the underlining ^~~~ can be removed.

prcastro commented 10 years ago

I like @jhasse suggestion

lindahua commented 10 years ago

I suspect that this might cause more daunting messages for methods that are heavily overloaded.

Consider:

type A end
A() + A()

This could be equally distant to all methods of +. Then this would produce a miles long list of things that virtually enumerate all 125 methods of + (probably many more if other packages are being used).

jhasse commented 10 years ago

Maybe remove all candidates where no arguments match at all? E. g. in my previous example:

f(1, 1.0)
ERROR: `f` has no method matching f(::Int64, ::Float64)
Closest candidates are:
    f(::Float64, ::Float64)
      ^~~~~~~~~
    f(::Int, ::Int)
             ^~~~~

Then your example would result in no list, but A() + 0 would have one. This would still be rather long, but I think that is wanted isn't it? Or the list could be truncated after 5 elements or so.

StefanKarpinski commented 10 years ago

That would never print suggestions for functions with fewer than two arguments – which might be ok. This is also pretty non-trivial to implement, but if you want to have a crack at it, please feel free. This would be awesome to have.

ceving commented 7 years ago

Suggesting functions with different type signatures will not help in case of "a" + "b". Only the suggestion of a different function will help. I do not think that the correct suggestion can be found in general. A correct suggestion has to know, that the person has a Java background. But how about "a" . "b"? How to know, that this is a Perl guy? And how about 'a' || 'b'?

ivarne commented 7 years ago

@ceving I don't understand what will not help.

The suggestion in this issue is to print helpful messages together with MethodError, when we see cases where we (think we) know what the user wants to do. The message might be anything, and will certainly often suggest different functions (actually both my examples suggest to change function (+ -> * and max -> maximum))

We know many programmers don't read the full manual before starting to program in Julia, and if we manage to save them of a few searches to figure out our api, that would save them a lot of time.

If a common language defines something similar to +(::String,::String) to mean something else, we can suggest the proper way to do that too.

shamsulalam1114 commented 1 month ago

Define the table for deliberately unimplemented methods and their suggestions

deliberately_unimplemented_methods = Dict( :+ => ((String, String), "Use * for string concatenation"), :max => ((AbstractArray,), "Use maximum to find the largest element in an array") )

Extend the showerror function to use this table

function Base.showerror(io::IO, err::MethodError)

Check if the method is deliberately unimplemented

for (method, (argtypes, suggestion)) in deliberately_unimplemented_methods
    if err.f == method && err.types == argtypes
        println(io, "MethodError: $(method)$(argtypes) is not implemented. $suggestion")
        return
    end
end
# Default behavior
Base.showerror(io, err)

end

Examples to trigger the MethodError

try "Hello" + "World" # This will raise a MethodError catch e println(e) end

try max([1, 2, 3]) # This will raise a MethodError catch e println(e) end