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

Implement traits Iterable and Callable #34535

Closed
jakobnissen opened this issue Jan 27, 2020 · 7 comments
Closed

Implement traits Iterable and Callable #34535

jakobnissen opened this issue Jan 27, 2020 · 7 comments
Labels
breaking This change will break code speculative Whether the change will be implemented is speculative

Comments

@jakobnissen
Copy link
Contributor

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.

@jakobnissen
Copy link
Contributor Author

Possible duplicate: #23429.

@DilumAluthge
Copy link
Member

Base.Callable already exists.

@stevengj
Copy link
Member

Seems like this would be a breaking change, unless iterable(x) = Iterable() is the default, which would make it much less useful.

@stevengj stevengj added breaking This change will break code speculative Whether the change will be implemented is speculative labels Jan 27, 2020
@JeffBezanson
Copy link
Member

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".

@singularitti
Copy link
Contributor

Base.Callable already 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

@StefanKarpinski
Copy link
Member

Correct. Since any object is callable this way, that would make Callable the same as Any.

@jakobnissen
Copy link
Contributor Author

OK, based on the responses in this thread:

  • Since any object could be callable or iterable, making code dispatch on these traits would place a burden on the developer to define these traits for every type they create, or else risk Base functions computing the wrong result, which is not reasonable.
  • If it is possible at compile time to determine whether a type is iterable and compute the 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking This change will break code speculative Whether the change will be implemented is speculative
Projects
None yet
Development

No branches or pull requests

6 participants