Consider a Julia neophyte trying to sort Chars:
julia> maximum('A', 'B')
ERROR: MethodError: objects of type Char are not callable
Stacktrace:
[1] mapreduce_first(::Char, ::Function, ::Char) at ./reduce.jl:293
[2] mapfoldl_impl(::Char, ::Function, ::NamedTuple{(),Tuple{}}, ::Char) at ./reduce.jl:60
[3] #mapfoldl#186(::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}, ::typeof(mapfoldl), ::Char, ::Function, ::Char) at ./reduce.jl:72
[4] mapfoldl(::Char, ::Function, ::Char) at ./reduce.jl:72
[5] #mapreduce#194(::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}, ::typeof(mapreduce), ::Char, ::Function, ::Char) at ./reduce.jl:200
[6] mapreduce(::Char, ::Function, ::Char) at ./reduce.jl:200
[7] maximum(::Char, ::Char) at ./reduce.jl:524
[8] top-level scope at none:0
Of course they should have used max. But that stack trace is long and confusing, considering that already the very first call maximum(::Char, ::Char), the types are all wrong for maximum. Normally, Julia methods have a signature that takes only appropritate abstract types, like Number and so on, precisely so that the method only apply to inputs of the correct types.
The problem is that there are no types for Callable or Iterable, so the signature of maximum is totally untyped: maximum(f, a) = mapreduce(f, max, a).
To solve this, I suggest implementing Holy traits Iterable and Callable:
maximum(f, itr) = maximum(f, callable(f), itr, iterable(itr))
maximum(f, ::Callable, itr, ::Iterable)
One argument against doing this is that it could open the door to all kinds of traits in Base for this or that property. However, in my experience, it seems like the properties "iterable" and "callable" are particularly often used, just consider the amount of confusion about what is iterable or not we often see.
Possible duplicate: https://github.com/JuliaLang/julia/issues/23429.
Base.Callable already exists.
Seems like this would be a breaking change, unless iterable(x) = Iterable() is the default, which would make it much less useful.
For addressing this specific case --- giving a better error message --- something much simpler would suffice. If we make applicable checks efficient, these functions could just add checks that raise a clear error if the expected methods aren't defined.
But, understanding the implications of method errors is critical to understanding how the language works. While traits might be very useful and indeed could be part of the future of the language, it's questionable whether adding a complex language feature truly ameliorates the confusion here. If you get a confusing error when trying to call maximum, your best move is ?maximum. Some rewording might also help here, e.g. "a Char was passed to a context that expected a function".
Base.Callablealready exists.
It does not seem to work with function-like objects?
julia> struct A end
julia> A isa Base.Callable
true
julia> (::A)() = 1
julia> a = A()
A()
julia> a isa Base.Callable
false
Correct. Since any object is callable this way, that would make Callable the same as Any.
OK, based on the responses in this thread:
Iterable() trait from that, say using a compile-time version of the function hasmethod(iterate, (Foo,)), then you might as well just use that function to check that the input types are applicable instead of using the trait.So it seems to be not possible to make these traits practically useful barring some complete redesign of how traits work in Julia, which is something outside the scope of this issue anyway. Closing the issue.
Most helpful comment
For addressing this specific case --- giving a better error message --- something much simpler would suffice. If we make
applicablechecks efficient, these functions could just add checks that raise a clear error if the expected methods aren't defined.But, understanding the implications of method errors is critical to understanding how the language works. While traits might be very useful and indeed could be part of the future of the language, it's questionable whether adding a complex language feature truly ameliorates the confusion here. If you get a confusing error when trying to call
maximum, your best move is?maximum. Some rewording might also help here, e.g. "aCharwas passed to a context that expected a function".