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

range: Assume start=1 when not given. Use OneTo #39223

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Standard library changes

* `count` and `findall` now accept an `AbstractChar` argument to search for a character in a string ([#38675]).
* `range` now supports `start` as an optional keyword argument ([#38041]).
* `range` now accepts a single positional argument as `stop` and may assume `start = 1` when `start` is not provided ([#39223])
* `islowercase` and `isuppercase` are now compliant with the Unicode lower/uppercase categories ([#38574]).
* `iseven` and `isodd` functions now support non-`Integer` numeric types ([#38976]).
* `escape_string` can now receive a collection of characters in the keyword
Expand Down
43 changes: 38 additions & 5 deletions base/range.jl
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,16 @@ end
range(start, stop; length, step)
range(start; length, stop, step)
range(;start, length, stop, step)
range(stop)

Construct a specialized array with evenly spaced elements and optimized storage (an [`AbstractRange`](@ref)) from the arguments.
Mathematically a range is uniquely determined by any three of `start`, `step`, `stop` and `length`.
Valid invocations of range are:
* Call `range` with any three of `start`, `step`, `stop`, `length`.
* Call `range` with two of `start`, `stop`, `length`. In this case `step` will be assumed
to be one. If both arguments are Integers, a [`UnitRange`](@ref) will be returned.
* Call `range` with `step` and either `stop` or `length`. `start` will be assumed to be one.
* Call `range` with one of `stop` or `length`. `start` and `step` will be assumed to be one.
Copy link
Sponsor Member

@mbauman mbauman Jan 13, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this accurate and succinct?

Suggested change
* Call `range` with any three of `start`, `step`, `stop`, `length`.
* Call `range` with two of `start`, `stop`, `length`. In this case `step` will be assumed
to be one. If both arguments are Integers, a [`UnitRange`](@ref) will be returned.
* Call `range` with `step` and either `stop` or `length`. `start` will be assumed to be one.
* Call `range` with one of `stop` or `length`. `start` and `step` will be assumed to be one.
* Call `range` with any three of `start`, `step`, `stop`, `length`. The omitted parameter is computed.
* Call `range` with fewer than three parameters, but including `stop` and/or `length`. If not provided, `start` and/or `step` are assumed to be one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is a good question. Can we simplify the documentation?

I'm wary of saying "The omitted parameter is computed" because someone may think additional processing occurs that may take time. In some cases, no additional computation is actually done unless the value of the parameter is actually requested. The preceding paragraph states "Mathematically a range is uniquely determined by any three of start, step, stop and `length". That covers what needs to be said well. I would keep the first bullet point as is.

For the second bullet point,

If stop and length are specified, then only step is assumed to be one. We could have assumed start to be one, but that would have a distinct result. We at least need to say step is assumed to be one first.

How about this:

  • Call range with fewer than three parameters. Either stop or length must be specified. If not provided, step and/or start are assumed to be one in that order of precedence.


# Examples
```jldoctest
Expand Down Expand Up @@ -86,6 +89,15 @@ julia> range(stop=10, step=1, length=5)

julia> range(start=1, step=1, stop=10)
1:1:10

julia> range(5)
Base.OneTo(5)

julia> range(; length = 10)
Base.OneTo(10)

julia> range(; stop = 5)
1:5
```
If `length` is not specified and `stop - start` is not an integer multiple of `step`, a range that ends before `stop` will be produced.
```jldoctest
Expand All @@ -103,11 +115,27 @@ If both are specified as positional arguments, one of `step` or `length` must al
`stop` as a positional argument requires at least Julia 1.1.

!!! compat "Julia 1.7"
`start` as a keyword argument requires at least Julia 1.7.
Assuming `start` is one when not provided,
`start` as a keyword argument, or
`length` as a positional argument requires at least Julia 1.7.

# Extended Help

`range` will produce a `Base.OneTo` when the arguments are Integers and
* Only `length` is provided
* Only `stop` is provided

`range` will produce a `UnitRange` when the arguments are Integers and
* Only `start` and `stop` are provided
* Only `length` and `stop` are provided

A `UnitRange` is not produced if `step` is specified even if specified as one.
"""
function range end

range(start; stop=nothing, length::Union{Integer,Nothing}=nothing, step=nothing) =
stop === length === step === nothing ?
range_stop(start) :
_range(start, step, stop, length)

function range(start, stop; length::Union{Integer,Nothing}=nothing, step=nothing)
Expand All @@ -128,13 +156,15 @@ end
range(;start=nothing, stop=nothing, length::Union{Integer, Nothing}=nothing, step=nothing) =
_range(start, step, stop, length)

range(length::Integer) = OneTo(length)

_range(start::Nothing, step::Nothing, stop::Nothing, len::Nothing) = range_error(start, step, stop, len)
_range(start::Nothing, step::Nothing, stop::Nothing, len::Any ) = range_error(start, step, stop, len)
_range(start::Nothing, step::Nothing, stop::Any , len::Nothing) = range_error(start, step, stop, len)
_range(start::Nothing, step::Nothing, stop::Nothing, len::Any ) = OneTo(len)
_range(start::Nothing, step::Nothing, stop::Any , len::Nothing) = range_stop(stop)
_range(start::Nothing, step::Nothing, stop::Any , len::Any ) = range_stop_length(stop, len)
_range(start::Nothing, step::Any , stop::Nothing, len::Nothing) = range_error(start, step, stop, len)
_range(start::Nothing, step::Any , stop::Nothing, len::Any ) = range_error(start, step, stop, len)
_range(start::Nothing, step::Any , stop::Any , len::Nothing) = range_error(start, step, stop, len)
_range(start::Nothing, step::Any , stop::Nothing, len::Any ) = range_start_step_length(oneunit(step), step, len)
_range(start::Nothing, step::Any , stop::Any , len::Nothing) = range_start_step_stop(oneunit(stop), step, stop)
_range(start::Nothing, step::Any , stop::Any , len::Any ) = range_step_stop_length(step, stop, len)
_range(start::Any , step::Nothing, stop::Nothing, len::Nothing) = range_error(start, step, stop, len)
_range(start::Any , step::Nothing, stop::Nothing, len::Any ) = range_start_length(start, len)
Expand All @@ -145,6 +175,9 @@ _range(start::Any , step::Any , stop::Nothing, len::Any ) = range_start
_range(start::Any , step::Any , stop::Any , len::Nothing) = range_start_step_stop(start, step, stop)
_range(start::Any , step::Any , stop::Any , len::Any ) = range_error(start, step, stop, len)

range_stop(stop) = oneunit(stop):stop
range_stop(stop::Integer) = Base.OneTo(stop)

range_stop_length(stop, length) = (stop-length+1):stop

range_step_stop_length(step, stop, length) = reverse(range_start_step_length(stop, -step, length))
Expand Down
16 changes: 16 additions & 0 deletions test/ranges.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,22 @@
# the next one uses ==, because it changes the eltype
@test r == range(start=first(r), stop=last(r), length=length(r))
@test r === range( stop=last(r), length=length(r))

r = 1:5
o = Base.OneTo(5)
let start=first(r), step=step(r), stop=last(r), length=length(r)
@test o === range( length)
@test o === range(; stop )
@test o === range(; length)
@test r === range(; start, stop )
@test r === range(; stop, length)
# the next four uses ==, because it changes the eltype
@test r == range(; step, stop )
@test r == range(; step, length)
@test r == range(; start, stop, length)
@test r == range(; start, step, length)
@test r == range(Float64(stop))
end
end
end

Expand Down