Can I use reverse and Iterators.reverse at the same time?

Can I have the methods for Base.reverse and Base.iterators.reverse available at the same time under just the name reverse? How? Observe:

julia> methods(reverse)
# 10 methods for generic function "reverse":
[1] reverse(t::Tuple) in Base at tuple.jl:485
  [snip]

julia> reverse(i for i = 1 : 3)
ERROR: MethodError: no method matching reverse(::Base.Generator{UnitRange{Int64}, typeof(identity)})
 [snip]

julia> Iterators.reverse(i for i = 1 : 3)
Base.Generator{StepRange{Int64, Int64}, typeof(identity)}(identity, 3:-1:1)

julia> using Base.Iterators: reverse
WARNING: ignoring conflicting import of Iterators.reverse into Main

julia> import Base.Iterators.reverse
WARNING: ignoring conflicting import of Iterators.reverse into Main

julia> using Base: Iterators.reverse
WARNING: ignoring conflicting import of Iterators.reverse into Main

You can define a function

reverse(x) = hasmethod(Base.reverse, (typeof(x),)) ? Base.reverse(x) : Iterators.reverse(x)

But I think it is better to be explicit about what behavior you want since these are different functions.

You could, however that is not best practice. A great strength of Julia’s design is multidispatch (here, we will be dispatching on the type of a single argument, so just dispatch).

julia>  iterable = (i for i in 1:3)
Base.Generator{UnitRange{Int64}, typeof(identity)}(identity, 1:3)

julia>  extensive = collect(iterable)
3-element Vector{Int64}:
 1
 2
 3

julia>  iterable_rev = Iterators.reverse(iterable)
Base.Generator{StepRange{Int64, Int64}, typeof(identity)}(identity, 3:-1:1)

julia>  extensive_rev = reverse(extensive)
3-element Vector{Int64}:
 3
 2
 1

julia> extensive_rev == collect(iterable_rev)
true

Where one is operating with an extensive (concretized) vector, Base.reverse is the way to reverse it. Where one is operating with an iterable (generative) vector, Base.Iterators.reverse is the way to reverse it.

The tendency to overload a function to work with different types of data is well-accepted if (and only if) the activity performed shares the same intent/purpose. For example,

julia> str = "abc"
"abc"
julia> str_rev = reverse(str) 
"cba"

It would not make sense to reverse a data structure that has no intrinsic order. Julia respects that.

julia> set = Set([2 5 3])
Set{Int64} with 3 elements:
  5
  2
  3

julia> reverse(set)
ERROR: MethodError: no method matching reverse(::Set{Int64})

Using the same reasoning, you should expect to reverse the explicit order of something that is not [yet] explicit, like a Generator. You may construct a new iterable entity from a given one, and that new iterable can generate its elements in the reverse sequence of the given one. For that, we use Iterators.reverse.

Thanks for your prompt answers. If they are truly different functions, wouldn’t it make sense to call one Base.reverse and the other Base.it_reverse? What is the benefit in having the first hide the name of the second?

Think of it in terms of namespace and named subspaces.
Base is given, and every function etc. exported from Base is available just by using it. So Base.reverse is available as reverse. Base has some designated subregions that are set apart by their collective name. Base.Iterators is one of these, Base.Threads is another. To work with iterators directly, the functions of Base.Iterators are appropriate see the docs.

Here is the information on Iterators.reverse

Base.Iterators.reverse —Function

Iterators.reverse(itr)

Given an iterator itr , then reverse(itr) is an iterator over the same collection but in the reverse order.

This iterator is “lazy” in that it does not make a copy of the collection in order to reverse it; see Base.reverse for an eager implementation.

Not all iterator types T support reverse-order iteration. If T doesn’t, then iterating over Iterators.reverse(itr::T) will throw a MethodError because of the missing iterate methods for Iterators.Reverse{T} . (To implement these methods, the original iterator itr::T can be obtained from r = Iterators.reverse(itr) by r.itr .)

Thanks again for the longer answer. I agree with you on the benefit of modularity in this setting. I also agree that Base.Iterators.reverse plays a different role from Base.reverse. The part I am not getting is: What is the benefit of intentionally making the names collide? In other words, wouldn’t it be better for Base.Iterators.reverse to be named something other than reverse so that it could be imported without collision?

Maybe, I’m not sure. People usually use it with the namespace Iterators.reverse(x) rather than importing it as reverse, but it could have been called ireverse(x) instead.

You can already import it with whatever name you want:

import .Iterators.reverse as it_reverse