From 62e629f85dd7af388c5703fd316d67a342c71362 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Thu, 15 Oct 2020 14:36:08 +0200 Subject: [PATCH 01/17] add range(;start, step, stop, length) --- base/range.jl | 116 +++++++++++++++++++++++++++-------------- base/twiceprecision.jl | 4 +- 2 files changed, 80 insertions(+), 40 deletions(-) diff --git a/base/range.jl b/base/range.jl index 2fd5866dc5d6b..7caff333f78a5 100644 --- a/base/range.jl +++ b/base/range.jl @@ -67,6 +67,9 @@ To avoid this induced overhead, see the [`LinRange`](@ref) constructor. !!! compat "Julia 1.1" `stop` as a positional argument requires at least Julia 1.1. +!!! compat "Julia 1.6" + `start` as a keyword argument requires at least Julia 1.6. + # Examples ```jldoctest julia> range(1, length=100) @@ -88,49 +91,86 @@ julia> range(1, 100, step=5) 1:5:96 ``` """ +function range end + range(start; length::Union{Integer,Nothing}=nothing, stop=nothing, step=nothing) = - _range(start, step, stop, length) + __range(start, step, stop, length) range(start, stop; length::Union{Integer,Nothing}=nothing, step=nothing) = - _range2(start, step, stop, length) - -_range2(start, ::Nothing, stop, ::Nothing) = - throw(ArgumentError("At least one of `length` or `step` must be specified")) - -_range2(start, step, stop, length) = _range(start, step, stop, length) - -# Range from start to stop: range(a, [step=s,] stop=b), no length -_range(start, step, stop, ::Nothing) = (:)(start, step, stop) -_range(start, ::Nothing, stop, ::Nothing) = (:)(start, stop) - -# Range of a given length: range(a, [step=s,] length=l), no stop -_range(a::Real, ::Nothing, ::Nothing, len::Integer) = UnitRange{typeof(a)}(a, oftype(a, a+len-1)) -_range(a::AbstractFloat, ::Nothing, ::Nothing, len::Integer) = _range(a, oftype(a, 1), nothing, len) -_range(a::AbstractFloat, st::AbstractFloat, ::Nothing, len::Integer) = _range(promote(a, st)..., nothing, len) -_range(a::Real, st::AbstractFloat, ::Nothing, len::Integer) = _range(float(a), st, nothing, len) -_range(a::AbstractFloat, st::Real, ::Nothing, len::Integer) = _range(a, float(st), nothing, len) -_range(a, ::Nothing, ::Nothing, len::Integer) = _range(a, oftype(a-a, 1), nothing, len) - -_range(a::T, step::T, ::Nothing, len::Integer) where {T <: AbstractFloat} = + __range(start, step, stop, length) + +range(;start=nothing, stop=nothing, length::Union{Integer, Nothing}=nothing, step=nothing) = + __range(start, step, stop, length) + +__range(start::Nothing, step::Nothing, stop::Nothing, length::Nothing) = range_error(start, step, stop, length) +__range(start::Nothing, step::Nothing, stop::Nothing, length ) = range_error(start, step, stop, length) +__range(start::Nothing, step::Nothing, stop , length::Nothing) = range_error(start, step, stop, length) +__range(start::Nothing, step::Nothing, stop , length ) = range_error(start, step, stop, length) +__range(start::Nothing, step , stop::Nothing, length::Nothing) = range_error(start, step, stop, length) +__range(start::Nothing, step , stop::Nothing, length ) = range_error(start, step, stop, length) +__range(start::Nothing, step , stop , length::Nothing) = range_error(start, step, stop, length) +__range(start::Nothing, step , stop , length ) = range_step_stop_length(step, stop, length) + +__range(start , step::Nothing, stop::Nothing, length::Nothing) = range_error(start, step, stop, length) +__range(start , step::Nothing, stop::Nothing, length ) = range_start_length(start, length) +__range(start , step::Nothing, stop , length::Nothing) = range_error(start, step, stop, length) +__range(start , step::Nothing, stop , length ) = range_start_stop_length(start, stop, length) +__range(start , step , stop::Nothing, length::Nothing) = range_error(start, step, stop, length) +__range(start , step , stop::Nothing, length ) = range_start_step_length(start, step, length) +__range(start , step , stop , length::Nothing) = range_start_step_stop(start, step, stop) +__range(start , step , stop , length ) = range_error(start, step, stop, length) + +range_step_stop_length(step, stop, length) = reverse(range_start_step_length(stop, -step, length)) + +range_start_length(a::Real, len::Integer) = UnitRange{typeof(a)}(a, oftype(a, a+len-1)) +range_start_length(a::AbstractFloat, len::Integer) = range_start_step_length(a, oftype(a, 1), len) +range_start_length(a, len::Integer) = range_start_step_length(a, oftype(a-a, 1), len) + +range_start_step_length(a::AbstractFloat, step::AbstractFloat, len::Integer) = + range_start_step_length(promote(a, step)..., len) +range_start_step_length(a::Real, step::AbstractFloat, len::Integer) = + range_start_step_length(float(a), step, len) +range_start_step_length(a::AbstractFloat, step::Real, len::Integer) = + range_start_step_length(a, float(step), len) +range_start_step_length(a::T, step::T, len::Integer) where {T <: AbstractFloat} = _rangestyle(OrderStyle(T), ArithmeticStyle(T), a, step, len) -_range(a::T, step, ::Nothing, len::Integer) where {T} = +range_start_step_length(a::T, step, len::Integer) where {T} = _rangestyle(OrderStyle(T), ArithmeticStyle(T), a, step, len) + _rangestyle(::Ordered, ::ArithmeticWraps, a::T, step::S, len::Integer) where {T,S} = StepRange{T,S}(a, step, convert(T, a+step*(len-1))) _rangestyle(::Any, ::Any, a::T, step::S, len::Integer) where {T,S} = StepRangeLen{typeof(a+0*step),T,S}(a, step, len) -# Malformed calls -_range(start, step, ::Nothing, ::Nothing) = # range(a, step=s) - throw(ArgumentError("At least one of `length` or `stop` must be specified")) -_range(start, ::Nothing, ::Nothing, ::Nothing) = # range(a) - throw(ArgumentError("At least one of `length` or `stop` must be specified")) -_range(::Nothing, ::Nothing, ::Nothing, ::Nothing) = # range(nothing) - throw(ArgumentError("At least one of `length` or `stop` must be specified")) -_range(start::Real, step::Real, stop::Real, length::Integer) = # range(a, step=s, stop=b, length=l) - throw(ArgumentError("Too many arguments specified; try passing only one of `stop` or `length`")) -_range(::Nothing, ::Nothing, ::Nothing, ::Integer) = # range(nothing, length=l) - throw(ArgumentError("Can't start a range at `nothing`")) +range_start_step_stop(start, step, stop) = start:step:stop + +function range_error(start, step, stop, length) + hasstart = start === nothing + hasstep = step === nothing + hasstop = stop === nothing + haslength = start === nothing + hint = if hasstart && hasstep && hasstop && haslength + "Try omitting one argument." + elseif !hasstop && !haslength + "At least one of `length` or `stop` must be specified." + elseif !hasstep && !haslength + "At least one of `length` or `step` must be specified." + elseif !hasstart && !hasstop + "At least one of `start` or `stop` must be specified." + else + "Try specifying more arguments." + end + + msg = """ + Cannot construct range from arguments: + start = $start + step = $step + stop = $stop + length = $length + $hint + """ + throw(ArgumentError(msg)) +end ## 1-dimensional ranges ## @@ -419,13 +459,13 @@ function LinRange(start, stop, len::Integer) LinRange{T}(start, stop, len) end -function _range(start::T, ::Nothing, stop::S, len::Integer) where {T,S} +function range_start_stop_length(start::T, stop::S, len::Integer) where {T,S} a, b = promote(start, stop) - _range(a, nothing, b, len) + range_start_stop_length(a, b, len) end -_range(start::T, ::Nothing, stop::T, len::Integer) where {T<:Real} = LinRange{T}(start, stop, len) -_range(start::T, ::Nothing, stop::T, len::Integer) where {T} = LinRange{T}(start, stop, len) -_range(start::T, ::Nothing, stop::T, len::Integer) where {T<:Integer} = +range_start_stop_length(start::T, stop::T, len::Integer) where {T<:Real} = LinRange{T}(start, stop, len) +range_start_stop_length(start::T, stop::T, len::Integer) where {T} = LinRange{T}(start, stop, len) +range_start_stop_length(start::T, stop::T, len::Integer) where {T<:Integer} = _linspace(float(T), start, stop, len) ## for Float16, Float32, and Float64 we hit twiceprecision.jl to lift to higher precision StepRangeLen # for all other types we fall back to a plain old LinRange diff --git a/base/twiceprecision.jl b/base/twiceprecision.jl index 1490a0624c7d6..e7a2e5041f4ef 100644 --- a/base/twiceprecision.jl +++ b/base/twiceprecision.jl @@ -427,7 +427,7 @@ end step(r::StepRangeLen{T,TwicePrecision{T},TwicePrecision{T}}) where {T<:AbstractFloat} = T(r.step) step(r::StepRangeLen{T,TwicePrecision{T},TwicePrecision{T}}) where {T} = T(r.step) -function _range(a::T, st::T, ::Nothing, len::Integer) where T<:Union{Float16,Float32,Float64} +function range_start_step_length(a::T, st::T, len::Integer) where T<:Union{Float16,Float32,Float64} start_n, start_d = rat(a) step_n, step_d = rat(st) if start_d != 0 && step_d != 0 && @@ -591,7 +591,7 @@ end ## LinRange # For Float16, Float32, and Float64, this returns a StepRangeLen -function _range(start::T, ::Nothing, stop::T, len::Integer) where {T<:IEEEFloat} +function range_start_stop_length(start::T, stop::T, len::Integer) where {T<:IEEEFloat} len < 2 && return _linspace1(T, start, stop, len) if start == stop return steprangelen_hp(T, start, zero(T), 0, len, 1) From 208eaf03cffe3957a4995c6bbf3bfa20b3776295 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Thu, 15 Oct 2020 16:27:30 +0200 Subject: [PATCH 02/17] allow a few more range argument patterns --- base/range.jl | 63 ++++++++++++++++++++++++++++++++++++-------------- test/ranges.jl | 31 ++++++++++++++++++++++--- 2 files changed, 74 insertions(+), 20 deletions(-) diff --git a/base/range.jl b/base/range.jl index 7caff333f78a5..cc4e39611e6fc 100644 --- a/base/range.jl +++ b/base/range.jl @@ -47,21 +47,21 @@ function _colon(start::T, step, stop::T) where T end """ - range(start[, stop]; length, stop, step=1) + range(start[, stop]; length, stop, step) + range(;start, length, stop, step) -Given a starting value, construct a range either by length or from `start` to `stop`, -optionally with a given step (defaults to 1, a [`UnitRange`](@ref)). -One of `length` or `stop` is required. If `length`, `stop`, and `step` are all specified, they must agree. - -If `length` and `stop` are provided and `step` is not, the step size will be computed -automatically such that there are `length` linearly spaced elements in the range. - -If `step` and `stop` are provided and `length` is not, the overall range length will be computed -automatically such that the elements are `step` spaced. +Construct a `range` 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 all four arguments. If the arguments are inconsistent, an error will be thrown. +* 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 and a [`UnitRange`](@ref) will be returned. Special care is taken to ensure intermediate values are computed rationally. To avoid this induced overhead, see the [`LinRange`](@ref) constructor. +`start` may be specified as either a positional or keyword argument. `stop` may be specified as either a positional or keyword argument. !!! compat "Julia 1.1" @@ -89,11 +89,20 @@ julia> range(1, 10, length=101) julia> range(1, 100, step=5) 1:5:96 + +julia> range(stop=10, length=5) +6:10 + +julia> range(stop=10, step=1, length=5) +6:1:10 + +julia> range(start=1, step=1, stop=10) +1:1:10 ``` """ function range end -range(start; length::Union{Integer,Nothing}=nothing, stop=nothing, step=nothing) = +range(start; stop=nothing, length::Union{Integer,Nothing}=nothing, step=nothing) = __range(start, step, stop, length) range(start, stop; length::Union{Integer,Nothing}=nothing, step=nothing) = @@ -105,7 +114,7 @@ range(;start=nothing, stop=nothing, length::Union{Integer, Nothing}=nothing, ste __range(start::Nothing, step::Nothing, stop::Nothing, length::Nothing) = range_error(start, step, stop, length) __range(start::Nothing, step::Nothing, stop::Nothing, length ) = range_error(start, step, stop, length) __range(start::Nothing, step::Nothing, stop , length::Nothing) = range_error(start, step, stop, length) -__range(start::Nothing, step::Nothing, stop , length ) = range_error(start, step, stop, length) +__range(start::Nothing, step::Nothing, stop , length ) = range_stop_length(stop, length) __range(start::Nothing, step , stop::Nothing, length::Nothing) = range_error(start, step, stop, length) __range(start::Nothing, step , stop::Nothing, length ) = range_error(start, step, stop, length) __range(start::Nothing, step , stop , length::Nothing) = range_error(start, step, stop, length) @@ -113,12 +122,14 @@ __range(start::Nothing, step , stop , length ) = range_s __range(start , step::Nothing, stop::Nothing, length::Nothing) = range_error(start, step, stop, length) __range(start , step::Nothing, stop::Nothing, length ) = range_start_length(start, length) -__range(start , step::Nothing, stop , length::Nothing) = range_error(start, step, stop, length) +__range(start , step::Nothing, stop , length::Nothing) = range_start_stop(start, stop) __range(start , step::Nothing, stop , length ) = range_start_stop_length(start, stop, length) __range(start , step , stop::Nothing, length::Nothing) = range_error(start, step, stop, length) __range(start , step , stop::Nothing, length ) = range_start_step_length(start, step, length) __range(start , step , stop , length::Nothing) = range_start_step_stop(start, step, stop) -__range(start , step , stop , length ) = range_error(start, step, stop, length) +__range(start , step , stop , length ) = range_start_step_stop_length(start, step, stop, length) + +range_stop_length(stop, length) = (stop-length+1):stop range_step_stop_length(step, stop, length) = reverse(range_start_step_length(stop, -step, length)) @@ -126,6 +137,8 @@ range_start_length(a::Real, len::Integer) = UnitRange{typeof(a)}(a, oft range_start_length(a::AbstractFloat, len::Integer) = range_start_step_length(a, oftype(a, 1), len) range_start_length(a, len::Integer) = range_start_step_length(a, oftype(a-a, 1), len) +range_start_stop(start, stop) = start:stop + range_start_step_length(a::AbstractFloat, step::AbstractFloat, len::Integer) = range_start_step_length(promote(a, step)..., len) range_start_step_length(a::Real, step::AbstractFloat, len::Integer) = @@ -144,14 +157,30 @@ _rangestyle(::Any, ::Any, a::T, step::S, len::Integer) where {T,S} = range_start_step_stop(start, step, stop) = start:step:stop +function range_start_step_stop_length(start, step, stop, length) + r = range_start_stop_length(start, stop, length) + if Base.step(r) == step + return r + else + msg = """ + Could not construct range with arguments + start = $start + step = $step + stop = $stop + length = $length + Try omitting one argument. + Closest candidate was $r. + """ + throw(ArgumentError(msg)) + end +end + function range_error(start, step, stop, length) hasstart = start === nothing hasstep = step === nothing hasstop = stop === nothing haslength = start === nothing - hint = if hasstart && hasstep && hasstop && haslength - "Try omitting one argument." - elseif !hasstop && !haslength + hint = if !hasstop && !haslength "At least one of `length` or `stop` must be specified." elseif !hasstep && !haslength "At least one of `length` or `step` must be specified." diff --git a/test/ranges.jl b/test/ranges.jl index 3e87f68e9c1a5..2c2b8d16dffa2 100644 --- a/test/ranges.jl +++ b/test/ranges.jl @@ -1,5 +1,33 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +@testset "range costruction" begin + @testset "range(;kw...)" begin + @test_throws ArgumentError range(start=1, step=1, stop=2, length=10) + @test range(start=1, step=1, stop=10, length=10) == 1:1:10 + @test_throws ArgumentError range(start=1, step=1, stop=10, length=11) + + r = 3.0:2:11 + @test r == range(start=first(r), step=step(r), stop=last(r) ) + @test r == range(start=first(r), step=step(r), length=length(r)) + @test r == range(start=first(r), stop=last(r), length=length(r)) + @test r == range( step=step(r), stop=last(r), length=length(r)) + + r = 4:9 + @test r === range(start=first(r), stop=last(r) ) + @test r === range(start=first(r), length=length(r)) + @test r == range(start=first(r), stop=last(r), length=length(r)) + @test r === range( stop=last(r), length=length(r)) + end + + @test range(1, 100) === 1:100 # ArgumentError before 1.6 + @test_throws range(1.0, step=0.25, stop=2.0, length=6) + r = range(1.0, step=0.25, stop=2.0, length=5) # ArgumentError before 1.6 + @test first(r) === 1.0 + @test step(r) === 0.25 + @test last(r) === 2.0 + @test length(r) === 5 +end + using Dates, Random isdefined(Main, :PhysQuantities) || @eval Main include("testhelpers/PhysQuantities.jl") using .Main.PhysQuantities @@ -1418,7 +1446,6 @@ end @test_throws ArgumentError range(nothing) @test_throws ArgumentError range(1, step=4) @test_throws ArgumentError range(nothing, length=2) - @test_throws ArgumentError range(1.0, step=0.25, stop=2.0, length=5) end @testset "issue #23300#issuecomment-371575548" begin @@ -1597,8 +1624,6 @@ end end end end - # require a keyword arg - @test_throws ArgumentError range(1, 100) end @testset "Reverse empty ranges" begin From 8d5c3ba318f8e4f4ffbb612e42723fd007cf10e1 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Thu, 15 Oct 2020 20:58:29 +0200 Subject: [PATCH 03/17] fix --- test/ranges.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ranges.jl b/test/ranges.jl index 2c2b8d16dffa2..f6bee1d36c807 100644 --- a/test/ranges.jl +++ b/test/ranges.jl @@ -20,7 +20,7 @@ end @test range(1, 100) === 1:100 # ArgumentError before 1.6 - @test_throws range(1.0, step=0.25, stop=2.0, length=6) + @test_throws ArgumentError range(1.0, step=0.25, stop=2.0, length=6) r = range(1.0, step=0.25, stop=2.0, length=5) # ArgumentError before 1.6 @test first(r) === 1.0 @test step(r) === 0.25 From a5ef9054a942aa13cdd02f5e0a5db88e2b1b22dd Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Tue, 20 Oct 2020 15:24:39 +0200 Subject: [PATCH 04/17] Update base/range.jl Co-authored-by: Michael Abbott <32575566+mcabbott@users.noreply.github.com> --- base/range.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/range.jl b/base/range.jl index cc4e39611e6fc..0de0d49000425 100644 --- a/base/range.jl +++ b/base/range.jl @@ -56,7 +56,7 @@ Valid invocations of range are: * Call `range` with all four arguments. If the arguments are inconsistent, an error will be thrown. * 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 and a [`UnitRange`](@ref) will be returned. +to be one. If all arguments are integers, a [`UnitRange`](@ref) will be returned. Special care is taken to ensure intermediate values are computed rationally. To avoid this induced overhead, see the [`LinRange`](@ref) constructor. From 50734fd283e19b8d193f48f638c291699998ac72 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Sat, 24 Oct 2020 22:32:55 +0200 Subject: [PATCH 05/17] remove four argument range --- base/range.jl | 135 +++++++++++++++++++++++++------------------------ test/ranges.jl | 8 +-- 2 files changed, 70 insertions(+), 73 deletions(-) diff --git a/base/range.jl b/base/range.jl index 0de0d49000425..04c83fb050026 100644 --- a/base/range.jl +++ b/base/range.jl @@ -53,23 +53,10 @@ end Construct a `range` 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 all four arguments. If the arguments are inconsistent, an error will be thrown. * 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 all arguments are integers, a [`UnitRange`](@ref) will be returned. -Special care is taken to ensure intermediate values are computed rationally. -To avoid this induced overhead, see the [`LinRange`](@ref) constructor. - -`start` may be specified as either a positional or keyword argument. -`stop` may be specified as either a positional or keyword argument. - -!!! compat "Julia 1.1" - `stop` as a positional argument requires at least Julia 1.1. - -!!! compat "Julia 1.6" - `start` as a keyword argument requires at least Julia 1.6. - # Examples ```jldoctest julia> range(1, length=100) @@ -99,35 +86,54 @@ julia> range(stop=10, step=1, length=5) julia> range(start=1, step=1, stop=10) 1:1:10 ``` +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 +julia> range(1, 3.5, step=2) +1.0:2.0:3.0 + +julia> range(1, 2.5) +1.0:1.0:2.0 +``` + +Special care is taken to ensure intermediate values are computed rationally. +To avoid this induced overhead, see the [`LinRange`](@ref) constructor. + +`start` may be specified as either a positional or keyword argument. +`stop` may be specified as either a positional or keyword argument. + +!!! compat "Julia 1.1" + `stop` as a positional argument requires at least Julia 1.1. + +!!! compat "Julia 1.6" + `start` as a keyword argument requires at least Julia 1.6. """ function range end range(start; stop=nothing, length::Union{Integer,Nothing}=nothing, step=nothing) = - __range(start, step, stop, length) + _range(start, step, stop, length) range(start, stop; length::Union{Integer,Nothing}=nothing, step=nothing) = - __range(start, step, stop, length) + _range(start, step, stop, length) range(;start=nothing, stop=nothing, length::Union{Integer, Nothing}=nothing, step=nothing) = - __range(start, step, stop, length) - -__range(start::Nothing, step::Nothing, stop::Nothing, length::Nothing) = range_error(start, step, stop, length) -__range(start::Nothing, step::Nothing, stop::Nothing, length ) = range_error(start, step, stop, length) -__range(start::Nothing, step::Nothing, stop , length::Nothing) = range_error(start, step, stop, length) -__range(start::Nothing, step::Nothing, stop , length ) = range_stop_length(stop, length) -__range(start::Nothing, step , stop::Nothing, length::Nothing) = range_error(start, step, stop, length) -__range(start::Nothing, step , stop::Nothing, length ) = range_error(start, step, stop, length) -__range(start::Nothing, step , stop , length::Nothing) = range_error(start, step, stop, length) -__range(start::Nothing, step , stop , length ) = range_step_stop_length(step, stop, length) - -__range(start , step::Nothing, stop::Nothing, length::Nothing) = range_error(start, step, stop, length) -__range(start , step::Nothing, stop::Nothing, length ) = range_start_length(start, length) -__range(start , step::Nothing, stop , length::Nothing) = range_start_stop(start, stop) -__range(start , step::Nothing, stop , length ) = range_start_stop_length(start, stop, length) -__range(start , step , stop::Nothing, length::Nothing) = range_error(start, step, stop, length) -__range(start , step , stop::Nothing, length ) = range_start_step_length(start, step, length) -__range(start , step , stop , length::Nothing) = range_start_step_stop(start, step, stop) -__range(start , step , stop , length ) = range_start_step_stop_length(start, step, stop, length) + _range(start, step, stop, 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::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::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) +_range(start::Any , step::Nothing, stop::Any , len::Nothing) = range_start_stop(start, stop) +_range(start::Any , step::Nothing, stop::Any , len::Any ) = range_start_stop_length(start, stop, len) +_range(start::Any , step::Any , stop::Nothing, len::Nothing) = range_error(start, step, stop, len) +_range(start::Any , step::Any , stop::Nothing, len::Any ) = range_start_step_length(start, step, len) +_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_length(stop, length) = (stop-length+1):stop @@ -139,48 +145,45 @@ range_start_length(a, len::Integer) = range_start_step_length(a, range_start_stop(start, stop) = start:stop -range_start_step_length(a::AbstractFloat, step::AbstractFloat, len::Integer) = +function range_start_step_length(a::AbstractFloat, step::AbstractFloat, len::Integer) range_start_step_length(promote(a, step)..., len) -range_start_step_length(a::Real, step::AbstractFloat, len::Integer) = - range_start_step_length(float(a), step, len) -range_start_step_length(a::AbstractFloat, step::Real, len::Integer) = - range_start_step_length(a, float(step), len) -range_start_step_length(a::T, step::T, len::Integer) where {T <: AbstractFloat} = +end + +function range_start_step_length(a::Real, step::AbstractFloat, len::Integer) + range_start_step_length(float(a), step, len) +end + +function range_start_step_length(a::AbstractFloat, step::Real, len::Integer) + range_start_step_length(a, float(step), len) +end + +function range_start_step_length(a::T, step::T, len::Integer) where {T <: AbstractFloat} _rangestyle(OrderStyle(T), ArithmeticStyle(T), a, step, len) -range_start_step_length(a::T, step, len::Integer) where {T} = +end + +function range_start_step_length(a::T, step, len::Integer) where {T} _rangestyle(OrderStyle(T), ArithmeticStyle(T), a, step, len) +end -_rangestyle(::Ordered, ::ArithmeticWraps, a::T, step::S, len::Integer) where {T,S} = +function _rangestyle(::Ordered, ::ArithmeticWraps, a::T, step::S, len::Integer) where {T,S} StepRange{T,S}(a, step, convert(T, a+step*(len-1))) -_rangestyle(::Any, ::Any, a::T, step::S, len::Integer) where {T,S} = +end + +function _rangestyle(::Any, ::Any, a::T, step::S, len::Integer) where {T,S} StepRangeLen{typeof(a+0*step),T,S}(a, step, len) +end range_start_step_stop(start, step, stop) = start:step:stop -function range_start_step_stop_length(start, step, stop, length) - r = range_start_stop_length(start, stop, length) - if Base.step(r) == step - return r - else - msg = """ - Could not construct range with arguments - start = $start - step = $step - stop = $stop - length = $length - Try omitting one argument. - Closest candidate was $r. - """ - throw(ArgumentError(msg)) - end -end - function range_error(start, step, stop, length) - hasstart = start === nothing - hasstep = step === nothing - hasstop = stop === nothing - haslength = start === nothing - hint = if !hasstop && !haslength + hasstart = start !== nothing + hasstep = step !== nothing + hasstop = stop !== nothing + haslength = start !== nothing + + hint = if hasstart && hasstep && hasstop && haslength + "Try specifying only three arguments" + elseif !hasstop && !haslength "At least one of `length` or `stop` must be specified." elseif !hasstep && !haslength "At least one of `length` or `step` must be specified." diff --git a/test/ranges.jl b/test/ranges.jl index f6bee1d36c807..302b1639e3730 100644 --- a/test/ranges.jl +++ b/test/ranges.jl @@ -3,7 +3,6 @@ @testset "range costruction" begin @testset "range(;kw...)" begin @test_throws ArgumentError range(start=1, step=1, stop=2, length=10) - @test range(start=1, step=1, stop=10, length=10) == 1:1:10 @test_throws ArgumentError range(start=1, step=1, stop=10, length=11) r = 3.0:2:11 @@ -20,12 +19,6 @@ end @test range(1, 100) === 1:100 # ArgumentError before 1.6 - @test_throws ArgumentError range(1.0, step=0.25, stop=2.0, length=6) - r = range(1.0, step=0.25, stop=2.0, length=5) # ArgumentError before 1.6 - @test first(r) === 1.0 - @test step(r) === 0.25 - @test last(r) === 2.0 - @test length(r) === 5 end using Dates, Random @@ -1446,6 +1439,7 @@ end @test_throws ArgumentError range(nothing) @test_throws ArgumentError range(1, step=4) @test_throws ArgumentError range(nothing, length=2) + @test_throws ArgumentError range(1.0, step=0.25, stop=2.0, length=5) end @testset "issue #23300#issuecomment-371575548" begin From 3796650de3b8d83e973aa0355cd5a51cc3f87cf4 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Tue, 27 Oct 2020 15:18:17 +0100 Subject: [PATCH 06/17] force a keyword argument in range(start, stop) --- base/range.jl | 20 +++++++++++++++----- test/ranges.jl | 4 ++-- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/base/range.jl b/base/range.jl index 04c83fb050026..dee3232686438 100644 --- a/base/range.jl +++ b/base/range.jl @@ -90,16 +90,14 @@ If `length` is not specified and `stop - start` is not an integer multiple of `s ```jldoctest julia> range(1, 3.5, step=2) 1.0:2.0:3.0 - -julia> range(1, 2.5) -1.0:1.0:2.0 ``` Special care is taken to ensure intermediate values are computed rationally. To avoid this induced overhead, see the [`LinRange`](@ref) constructor. `start` may be specified as either a positional or keyword argument. -`stop` may be specified as either a positional or keyword argument. +`stop` may be specified as either a positional or keyword argument. If it is specified as a positional argument, +one of `step` or `length` must also be provided. !!! compat "Julia 1.1" `stop` as a positional argument requires at least Julia 1.1. @@ -112,8 +110,20 @@ function range end range(start; stop=nothing, length::Union{Integer,Nothing}=nothing, step=nothing) = _range(start, step, stop, length) -range(start, stop; length::Union{Integer,Nothing}=nothing, step=nothing) = +function range(start, stop; length::Union{Integer,Nothing}=nothing, step=nothing) + # For code clarity, the user must pass step or length + # See https://github.com/JuliaLang/julia/pull/28708#issuecomment-420034562 + if step === length === nothing + msg = """ + Neither step nor length were provided. To fix this do one of the following: + * Pass one of them + * Use `$(start):$(stop)` + * Use `range($start, stop=$stop)` + """ + throw(ArgumentError(msg)) + end _range(start, step, stop, length) +end range(;start=nothing, stop=nothing, length::Union{Integer, Nothing}=nothing, step=nothing) = _range(start, step, stop, length) diff --git a/test/ranges.jl b/test/ranges.jl index 302b1639e3730..8eb680e365a1f 100644 --- a/test/ranges.jl +++ b/test/ranges.jl @@ -17,8 +17,6 @@ @test r == range(start=first(r), stop=last(r), length=length(r)) @test r === range( stop=last(r), length=length(r)) end - - @test range(1, 100) === 1:100 # ArgumentError before 1.6 end using Dates, Random @@ -1618,6 +1616,8 @@ end end end end + # require a keyword arg + @test_throws ArgumentError range(1, 100) end @testset "Reverse empty ranges" begin From cc25cf3b9b294af5c40ca33e31d2b2150feffe1c Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Fri, 6 Nov 2020 16:50:15 +0100 Subject: [PATCH 07/17] Update base/range.jl Co-authored-by: Johnny Chen --- base/range.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/base/range.jl b/base/range.jl index dee3232686438..61741c13f47be 100644 --- a/base/range.jl +++ b/base/range.jl @@ -47,7 +47,8 @@ function _colon(start::T, step, stop::T) where T end """ - range(start[, stop]; length, stop, step) + range(start, stop; length, step) + range(start; length, stop, step) range(;start, length, stop, step) Construct a `range` from the arguments. From 8c327e964108d1bb702d368af69433bed1063572 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Thu, 12 Nov 2020 09:41:15 +0100 Subject: [PATCH 08/17] Update base/range.jl Co-authored-by: Johnny Chen --- base/range.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/range.jl b/base/range.jl index 61741c13f47be..254e5bde9d5c1 100644 --- a/base/range.jl +++ b/base/range.jl @@ -116,7 +116,7 @@ function range(start, stop; length::Union{Integer,Nothing}=nothing, step=nothing # See https://github.com/JuliaLang/julia/pull/28708#issuecomment-420034562 if step === length === nothing msg = """ - Neither step nor length were provided. To fix this do one of the following: + Neither `step` nor `length` was provided. To fix this do one of the following: * Pass one of them * Use `$(start):$(stop)` * Use `range($start, stop=$stop)` From 7712b4cafc85c0696d0abd2ac04c3072cbe2c551 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Wed, 6 Jan 2021 19:18:00 +0100 Subject: [PATCH 09/17] Update test/ranges.jl Co-authored-by: Mark Kittisopikul --- test/ranges.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ranges.jl b/test/ranges.jl index a06cba3c47b0b..0353d1d241eb0 100644 --- a/test/ranges.jl +++ b/test/ranges.jl @@ -1,6 +1,6 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -@testset "range costruction" begin +@testset "range construction" begin @testset "range(;kw...)" begin @test_throws ArgumentError range(start=1, step=1, stop=2, length=10) @test_throws ArgumentError range(start=1, step=1, stop=10, length=11) From 083a4f6dd04ee34f0f8f9092a25f4312dd899e37 Mon Sep 17 00:00:00 2001 From: Matt Bauman Date: Thu, 7 Jan 2021 10:51:06 -0500 Subject: [PATCH 10/17] Update NEWS.md --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 48aebc9344980..f355b537365bc 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,7 @@ Julia v1.7 Release Notes New language features --------------------- * `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]). Language changes ---------------- From 1097661f56ccc69baa011c90b562b9ee51b77ca9 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Thu, 7 Jan 2021 18:31:17 +0100 Subject: [PATCH 11/17] Update base/range.jl Co-authored-by: Matt Bauman --- base/range.jl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/base/range.jl b/base/range.jl index 9218dd764aadf..f58fe9471f6ac 100644 --- a/base/range.jl +++ b/base/range.jl @@ -96,9 +96,8 @@ julia> range(1, 3.5, step=2) Special care is taken to ensure intermediate values are computed rationally. To avoid this induced overhead, see the [`LinRange`](@ref) constructor. -`start` may be specified as either a positional or keyword argument. -`stop` may be specified as either a positional or keyword argument. If it is specified as a positional argument, -one of `step` or `length` must also be provided. +Both `start` and `stop` may be specified as either a positional or keyword arguments. +If both are specified as positional arguments, one of `step` or `length` must also be provided. !!! compat "Julia 1.1" `stop` as a positional argument requires at least Julia 1.1. From ba32fef76968cb31b4078012c22c245c4476736d Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Thu, 7 Jan 2021 18:31:27 +0100 Subject: [PATCH 12/17] Update base/range.jl Co-authored-by: Matt Bauman --- base/range.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/range.jl b/base/range.jl index f58fe9471f6ac..d31f5a91a8607 100644 --- a/base/range.jl +++ b/base/range.jl @@ -51,7 +51,7 @@ end range(start; length, stop, step) range(;start, length, stop, step) -Construct a `range` from the arguments. +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`. From 6a0e25ebd2e1aa11e52e3ed9a607bc64feec5998 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Thu, 7 Jan 2021 18:36:01 +0100 Subject: [PATCH 13/17] fix spelling --- base/range.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/range.jl b/base/range.jl index d31f5a91a8607..2ed771190782f 100644 --- a/base/range.jl +++ b/base/range.jl @@ -56,7 +56,7 @@ Mathematically a range is uniquely determined by any three of `start`, `step`, ` 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 all arguments are integers, a [`UnitRange`](@ref) will be returned. +to be one. If all arguments are Integers, a [`UnitRange`](@ref) will be returned. # Examples ```jldoctest From 0c5fc366dee596f98bda7670b2102224b9e75d8a Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Thu, 7 Jan 2021 18:38:30 +0100 Subject: [PATCH 14/17] fix documented julia version in range --- base/range.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/range.jl b/base/range.jl index cec844833bb2b..e087d056cd45b 100644 --- a/base/range.jl +++ b/base/range.jl @@ -102,8 +102,8 @@ If both are specified as positional arguments, one of `step` or `length` must al !!! compat "Julia 1.1" `stop` as a positional argument requires at least Julia 1.1. -!!! compat "Julia 1.6" - `start` as a keyword argument requires at least Julia 1.6. +!!! compat "Julia 1.7" + `start` as a keyword argument requires at least Julia 1.7. """ function range end From 307d59bcfd5b7f1ead0652c26ccfcd37c2b0e2da Mon Sep 17 00:00:00 2001 From: Matt Bauman Date: Thu, 7 Jan 2021 12:45:28 -0500 Subject: [PATCH 15/17] Move the NEWS to the right place --- NEWS.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 410f43247b66d..dc532ea43526b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,8 +3,6 @@ Julia v1.7 Release Notes New language features --------------------- -* `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]). Language changes ---------------- @@ -42,6 +40,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]). * `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 From 65898ed0fb4648816fb55d3a2743061efc42f9b1 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Fri, 8 Jan 2021 13:04:15 +0100 Subject: [PATCH 16/17] fix --- base/range.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/range.jl b/base/range.jl index e087d056cd45b..aec23e852589b 100644 --- a/base/range.jl +++ b/base/range.jl @@ -56,7 +56,7 @@ Mathematically a range is uniquely determined by any three of `start`, `step`, ` 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 all arguments are Integers, a [`UnitRange`](@ref) will be returned. +to be one. If both arguments are Integers, a [`UnitRange`](@ref) will be returned. # Examples ```jldoctest From 01f6cccedfc1255f414ea2c73a96574428402484 Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Mon, 11 Jan 2021 09:57:51 +0100 Subject: [PATCH 17/17] add minor comment to range unit test --- test/ranges.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/ranges.jl b/test/ranges.jl index a9632a6d012a2..b9f77f211193d 100644 --- a/test/ranges.jl +++ b/test/ranges.jl @@ -14,6 +14,7 @@ r = 4:9 @test r === range(start=first(r), stop=last(r) ) @test r === range(start=first(r), length=length(r)) + # 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)) end