JuliaLang / julia

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

range(start, stop, length) #38750

Closed antoine-levitt closed 3 years ago

antoine-levitt commented 3 years ago

This issue is to propose defining range(start, stop, length) = range(start, stop; length=length). I searched the issues and PR, expecting pages of heated debate, but I couldn't find any, so here goes.

Pros:

Cons:

mkitti commented 3 years ago

range(start, stop) is just start:stop like range(start, step, stop) is just start:step:stop. Adding either range(start, stop) and range(start, step, stop) just adds redundancy.

For the length oriented design, we can just document this as:

`length` is always the last argument when only positional arguments are used.

I would deprecate range(start, stop; length) by just removing the documentation for it and leaving range(start, stop; step) . I'm not sure why you would use range(start, stop; length) when you have range(start, stop, length). It could be left as a compatibility note.

The combined documentation would then be:

    range(  length  )
    range(  start, length )
    range(  start, stop, length )
    range(  start, stop; step )
    range(  start; stop, step, length )
    range(; start, stop, step, length )

The code part of the PR is then three lines:

# One positional argument
range(length::Integer) = Base.OneTo(length) # Integers only!
# range(stop) = Base.OneTo(stop) # Equivalent

# range(start, stop) = range_start_stop(start, stop) # Redundant with `start:stop`
range(start, length) = range_start_length(start, length) # Julia is not Python

# Three positional argument
range(start, stop, length) = range_start_stop_length(start, stop, length)

We might almost as well just fold this into #38041 since that is also approved and the effective code is just two or three lines aliasing into non-exported functions that were created in #38041 .

mkitti commented 3 years ago

I considered range( [ start, [ stop, ] ] length ). If compactness were key, it might work. Otherwise, it is challenging to read.

mbauman commented 3 years ago

IMO range(start, length) is a non-starter due to range(start, stop; ...). I think the "length-oriented" design is overly confusing due to that existing signature.

mkitti commented 3 years ago

IMO range(start, length) is a non-starter due to range(start, stop; ...). I think the "length-oriented" design is overly confusing due to that existing signature.

I would lean towards deprecation of range(start, stop; ...) in Julia 2.0 and de-emphasizing its documentation now. range(start, stop; length) would be covered by range(start, stop, length) range(start, stop; step) is covered by start:step:stop range(start, stop; step, length), with both keywords specified, causes an error

If you really want keywords galore for very specific syntax you still have range(start; stop, step, length) for a highly backwards compatible syntax and now range(; start, stop, step, length)

range(start, stop; ...) can still work for compatibility reasons only, but I'm failing to see why that might hinder us in the long term. It has little use after we add this and the all keyword version.

StefanKarpinski commented 3 years ago

I think that given that we already allow range(start, stop; length) and range(start, stop; step) we're pretty much forced to go with the "stop-oriented" design. I don't really think that either design is strictly better or worse, so I'm fairly happy to have a decision forced upon us.

fredrikekre commented 3 years ago

For 1.0 we had a push to introduce keyword arguments in the public API (see https://github.com/JuliaLang/julia/issues/25188 for example) partly to avoid ambiguous cases just like this IIRC. Are people really using this function frequently enough such that having to type length= is a problem?

mkitti commented 3 years ago

Note, too, that it becomes more useful if it returns a Base.OneTo — which is commonly used in high-performance axis munging.

Is it range(stop) or range(length)?

The difference is length has long been an Integer and Base.OneTo only accepts an Integer. Meanwhile, stop can be anything, or at least any Number.

Do we need to define two forms of a single argument range?

range(stop) = range_start_stop( oneunit(stop), stop ) # Equivalent to `1:stop`
range(length::Integer) = Base.OneTo( length )

Or should we restrict it to only Integer?

mkitti commented 3 years ago

For three argument range(start, stop, length) can length or any of the arguments be nothing?

If length == nothing, then this just becomes equivalent to range(start, stop). stop could be nothing if length is not nothing. This should be equivalent to range(start; length) start could be nothing if either stop or length is defined. If only one is defined, then start defaults to 1.

mkitti commented 3 years ago

I think the answers to most of my questions on nothing can be deferred to the Base._range 4-argument block in #38041

https://github.com/JuliaLang/julia/blob/65898ed0fb4648816fb55d3a2743061efc42f9b1/base/range.jl#L131-L146

mkitti commented 3 years ago

One option is the "stop-oriented" design:

  • range(stop) with start = step = 1

I'm leaving a note in this design issue that my last effort towards range(stop) in the stop-oriented design above is in #39241 which implements the keyword form range(; stop) which is a necessary precedent to range(stop).

The main motivation for range(stop) is to provide the Python user coming to Julia some familiarity as expressed in this Discourse comment.

However, range(stop), the positional form, has some outstanding questions and concerns:

  1. Can stop be any valid value for which oneunit(stop):stop works?
  2. Should stop be an Integer when given as a single argument? In Python, range(stop) only accepts integers.
  3. Can this be implemented without having strange method signatures displayed such as range(stop; stop, length, step)?
  4. Is it confusing that the single positional argument is start when given with a keyword, but stop without a keyword?
  5. Does it matter that axes and eachindex can produce an AbstractUnitRange, Base.OneTo, but range cannot create this exactly, in terms of ===?

As I do not see a path forward on range(stop) in the next few releases, I'm moving on. I'm sorry to disappoint Stefan's sympathies. Thank you for the discussion.