Julia: Unhelpful error message when trying to instantiate an abstract type

Created on 10 Feb 2017  路  12Comments  路  Source: JuliaLang/julia

Hi,

this is more of a cosmetic problem: when trying to instantiate an abstract type (which is not allowed, according to the Julia documentation), the error message is not particularly helpful. Calling the empty constructor results in a suggestion to call the constructor with an argument of type Any, and doing so results in a message about a failed convert call. Example:

julia> abstract Foo

julia> Foo()
ERROR: MethodError: no method matching Foo()
Closest candidates are:
  Foo(::Any) where T at sysimg.jl:24

julia> Foo(1)
ERROR: MethodError: Cannot `convert` an object of type Int64 to an object of type Foo
This may have arisen from a call to the constructor Foo(...),
since type constructors fall back to convert methods.
Stacktrace:
 [1] Foo(::Int64) at ./sysimg.jl:24

I feel these messages are quite misleading for users who are unaware of the restrictions on abstract types. Perhaps a message like "ERROR: type 'Foo' is abstract and therefore cannot be instantiated" is more appropriate?

Versioninfo:

Julia Version 0.6.0-dev.2703
Commit 745da8d (2017-02-09 18:47 UTC)
Platform Info:
OS: Linux (x86_64-pc-linux-gnu)
CPU: Intel(R) Core(TM) i5-3210M CPU @ 2.50GHz
WORD_SIZE: 64
BLAS: libopenblas (USE64BITINT DYNAMIC_ARCH NO_AFFINITY Sandybridge)
LAPACK: libopenblas64_
LIBM: libopenlibm
LLVM: libLLVM-3.9.1 (ORCJIT, ivybridge)

error handling

Most helpful comment

Calling an abstract type is perfectly fine so saying it cannot be constructed will also be very confusing.

julia> Integer(1.0)
1

julia> Ref{Int}()
Base.RefValue{Int64}(0)

All 12 comments

Calling an abstract type is perfectly fine so saying it cannot be constructed will also be very confusing.

julia> Integer(1.0)
1

julia> Ref{Int}()
Base.RefValue{Int64}(0)

How about:

julia> abstract Foo

julia> f(::Int) = 0

julia> f()
ERROR: MethodError: no method matching f()
Closest candidates are:
  f(::Int64) at REPL[2]:1

julia> Foo()
ERROR: MethodError: no method matching Foo()
NOTE: `Foo` is abstract; to call it, there must be an overload for either:
      - convert(::Type{Foo})
      - Foo()
Closest candidates are:
  Foo(::Any) where T at sysimg.jl:24

_Edit: "you must define" -> "there must be"._
_Edit 2: show that only calling an abstract type shows the "NOTE"._
_Edit 3: the signatures of the overloads in the "NOTE" should change according to how many arguments you tried to call Foo with._

That does look non-confusing. Though, isn't it exactly what the MethodError always mean? (i.e. no method match so you should define one if you want it to call it like this...)

@yuyichao Agreed. But it can check whether or not the thing you're trying to call is an abstract type, no?

Yes, it can do all kinds of checks. I just mean that it doesn't seem to be more helpful. I guess I'll just let someone (@traktofon) less familiar with MethodErrors to decide what wording is more helpful...

That printing of the method error does look nice, but @yuyichao is right that it has nothing to do with abstract types. The exact same situation could arise with concrete types --- you try to call Foo() which has no matching definition, where Foo is a concrete type. Foo(x,y,...) always simply attempts to call a method matching those arguments. There is no logic that says e.g. "if Foo is concrete, instantiate it, if it's abstract do a method lookup, etc."

julia> struct Foo
         x
       end

julia> Foo()
ERROR: MethodError: no method matching Foo()
Closest candidates are:
  Foo(::Any) at REPL[1]:2
  Foo(::Any) where T at sysimg.jl:24

@JeffBezanson

Fair point. Perhaps instead, the message should be:

julia> Foo()
ERROR: MethodError: no method matching Foo()
NOTE: Abstract types do not have any automatically generated methods.
Closest candidates are:
  Foo(::Any) where T at sysimg.jl:24

Since both functions and structs have at least one method after they are declared, and, since defining methods is used extensively in Base, and I would imagine that it is reasonably obvious to anyone who's done any kind of programming what methods are, it would not be necessary to show notes for those cases.

That's maybe a bit better, but you'll quickly run into many cases where it doesn't make sense. For example

julia> AbstractVector{Int}([2.0])
1-element Array{Int64,1}:
 2

julia> AbstractVector{Int}("a")
ERROR: MethodError: Cannot `convert` an object of type String to an object of type AbstractArray{Int64,1}
This may have arisen from a call to the constructor AbstractArray{Int64,1}(...),
since type constructors fall back to convert methods.
Stacktrace:
 [1] AbstractArray{Int64,1}(::String) at ./sysimg.jl:24

The only thing happening here is that there's a definition for the first case, but not for the second. Whether any methods were automatically generated doesn't really enter into it.

Maybe one thing we could do is check whether there are any methods for types in a certain family. For example, if Foo{Int}(x) fails, check whether any calls to some sort of Foo type are possible. If not, say something like "type Foo has no constructors".

@JeffBezanson

if Foo{Int}(x) fails, check whether any calls to some sort of Foo type are possible. If not, say something like "type Foo has no constructors".

It's not immediately obvious that you _can_ define constructors on an abstract type, so I'd advise that the message is more like no constructors have been defined for Foo. Unless anyone has any better suggestions. :-)

Like @H-225 said, it wasn't obvious to me that you can define constructors on abstract types. Where the documentation says that "abstract types cannot be instantiated", I read it as "abstract types can't have constructors", but that is not the same. Though, while you can define such constructors, they don't instantiate the abstract type but some concrete subtype. I can see that this can be convenient. And I see that this is already documented in the section on constructors, so I should have read more thoroughly.

With this in mind, I would actually be fine with the current error message which just points out that method definitions are missing. But one thing is still confusing for me: when calling the empty constructor, the second half of the error message suggests that there is a candidate constructor Foo(::Any):

julia> Foo()
ERROR: MethodError: no method matching Foo()
Closest candidates are:
  Foo(::Any) where T at sysimg.jl:24

While it is technically true that this Foo method exists (it dispatches to convert), maybe it would be helpful to already detect at this stage that there are no convert methods for Foo, and inform the user that no constructors/methods for Foo have been defined at all.

But as I said in the beginning, this is more a cosmetic problem, so feel free to close if the current state is deemed acceptable.

That's why I'm not a huge fan of the "closest candidates" feature. We'll never be done arguing about which methods are close enough to be useful suggestions. But I do think it's relevant that the suggested definition at sysimg.jl:24 doesn't actually mention Foo at all. It would be nice to notice that and say no constructors have been defined for Foo.

I'm in the process of making a PR to make that message happen.

Any ideas what the chances are that a PR that always hides the sysimg.jl method from the closest-candidates of getting accepted, is? - I mean... Can that method even be called?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

i-apellaniz picture i-apellaniz  路  3Comments

tkoolen picture tkoolen  路  3Comments

TotalVerb picture TotalVerb  路  3Comments

musm picture musm  路  3Comments

omus picture omus  路  3Comments