-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Add range(start => stop, length) #39069
Conversation
This is not the first time I've wondered whether IntervalSets should be in Base. Of course the piracy issue for IntervalArithmetic would be a potential concern. Doing this via a Pair doesn't seem like too much of an abuse; Pairs do sometimes feel like key/value pairs, and this is not such a case, but the idea of a Pair representing an interval is not foreign to the way they print.The one slight oddity might be |
Using the Interval operator
I do like the julia> range(5 => 3, 2)
5.0:-2.0:3.0
julia> range(5 => 3, 3)
5.0:-1.0:3.0
julia> 4 ∈ range(5 => 3, 3)
true
julia> range(5; stop = 3) # For comparison, this is empty
5:4
julia> isempty( range(5; stop = 3) ) # For comparison, this is empty
true
julia> range(5, 3; length = 2) # Consistent with this proposal
5.0:-2.0:3.0 In contrast, julia> using IntervalSets
julia> 4 ∈ 5..3
false
julia> 4 ∈ 3..5
true
julia> isempty(5..3)
true |
I don't like the spelling |
To me, |
There is precedent for using pairs to provide arguments to a function in Base. For example, I can use |
Yes and it always seemed confusing for me, but there there is the added utility of being able to pass multiple pairs, which is not present here. |
How is the pair syntax confusing? For the bounds of the range, it's maybe slightly less intuitive, but I still like it. As @MasonProtter says, |
I don't really understand how the presence of multiple pairs or just one pair is at all relevant to this, but julia> replace("foo", 'f' => 'b')
"boo"
julia> replace("foo", 'f' => 'b', 'o' => 'a')
ERROR: MethodError: no method matching replace(::String, ::Pair{Char, Char}, ::Pair{Char, Char})
Closest candidates are:
replace(::AbstractString, ::Pair, ::Pair) at set.jl:613
replace(::Any, ::Pair...; count) at set.jl:555
replace(::Union{Function, Type}, ::Pair, ::Pair; count) at set.jl:612
...
Stacktrace:
[1] replace(a::String, b::Pair{Char, Char}, c::Pair{Char, Char})
@ Base ./set.jl:613
[2] top-level scope
@ REPL[1]:1 |
Why is |
That's a good question. The short answer is rarely, and the default is not there for utility. It is there mainly to promote understanding of this form of First, let's compare this syntax and prior syntax. julia> collect( range(1 => 5.5) )
2-element Array{Float64,1}:
1.0
5.5
julia> collect( range(1; stop=5.5) )
5-element Array{Float64,1}:
1.0
2.0
3.0
4.0
5.0
julia> collect( 1:5.5 )
5-element Array{Float64,1}:
1.0
2.0
3.0
4.0
5.0 The syntax Given that we want the first element to be One answer might there is no good default for Another answer is that perhaps we should pick some large number like The reason I chose |
You could just make it a method error to not provide the length. That would be a bit more consistent with this: julia> range(1, 10)
ERROR: ArgumentError: At least one of `length` or `step` must be specified |
An alternative to a default length would be using julia> Base.range(start_stop::Pair, length::Integer) =
Base._range(start_stop.first, nothing, start_stop.second, length)
julia> Base.range(start_stop::Pair) = Base.Fix1(Base.range, start_stop)
julia> r = Base.range(1 => 10)
(::Base.Fix1{typeof(range),Pair{Int64,Int64}}) (generic function with 1 method)
julia> print( collect( r(10) ) )
[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
julia> print( collect( r(5) ) )
[1.0, 3.25, 5.5, 7.75, 10.0]
julia> print( collect( r(3) ) )
[1.0, 5.5, 10.0] That does seem really useful actually. I can compute integrals with this. julia> p( n, r = range(-10 => 10) ) = sum( ( exp( -x^2 )*20/n ) for x in r(n) )^2
p (generic function with 2 methods)
julia> p(10^2)
3.0790749597833598
julia> p(10^4)
3.1409643664749933
julia> p(10^8)
3.141592590613959 |
I'm reusing the same underlying machinery actually, so I would just have to remove the default. julia> Base.range(start_stop::Pair, length::Integer) =
Base._range(start_stop.first, nothing, start_stop.second, length)
julia> range( 1 => 10 )
ERROR: ArgumentError: At least one of `length` or `stop` must be specified
Stacktrace:
[1] _range(::Pair{Int64,Int64}, ::Nothing, ::Nothing, ::Nothing) at .\range.jl:126
[2] range(::Pair{Int64,Int64}; length::Nothing, stop::Nothing, step::Nothing) at .\range.jl:91
[3] range(::Pair{Int64,Int64}) at .\range.jl:91
[4] top-level scope at REPL[2]:1 |
I think I prefer length not having a default. The use of the function can
be demonstrated in the examples and by experiment.
Curried form is cute. Maybe someone could dig up the guidelines for
currying in Base and see if it fits?
…On Sun, 3 Jan 2021, 06:45 Mark Kittisopikul, ***@***.***> wrote:
You could just make it a method error to not provide the length
I'm reusing the same underlying machinery actually, so I would just have
to remove the default.
julia> Base.range(start_stop::Pair, length::Integer) =
Base._range(start_stop.first, nothing, start_stop.second, length)
julia> range( 1 => 10 )
ERROR: ArgumentError: At least one of `length` or `stop` must be specified
Stacktrace:
[1] _range(::Pair{Int64,Int64}, ::Nothing, ::Nothing, ::Nothing) at .\range.jl:126
[2] range(::Pair{Int64,Int64}; length::Nothing, stop::Nothing, step::Nothing) at .\range.jl:91
[3] range(::Pair{Int64,Int64}) at .\range.jl:91
[4] top-level scope at REPL[2]:1
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#39069 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABNZA6ISSWZ2AV723RE6FV3SYAHBFANCNFSM4VQZVLQA>
.
|
Yes but not the
It's not confusing when you read it, it's confusing when you write it : I would naively go for |
Breaking means that some syntax that used to work no longer works after this change. This pull request is not breaking. This pull request does not remove or preclude any positional argument syntax consisting of scalar numbers. The availability of this All prior syntax, including past and future prospective positional forms of julia> Base.range(start_stop::Pair, length::Integer) =
Base._range(start_stop.first, nothing, start_stop.second, length)
julia> Base.range(start, stop, length::Integer) = Base._range(start, nothing, stop, length)
julia> range(1, 5; length = 5)
1.0:1.0:5.0
julia> range(1, 5; step = 1)
1:1:5
julia> range(1, 5, 3) # Fully positional argument syntax works
1.0:2.0:5.0
julia> range(1 => 5, 3) # Pair syntax works along side above positional argument syntax
1.0:2.0:5.0 |
In response to feedback, I have removed the default julia> Base.range(start_stop::Pair, length::Integer) =
Base._range(start_stop.first, nothing, start_stop.second, length)
julia> Base.range(start_stop::Pair, length::Nothing = nothing) =
throw(ArgumentError("`length` must be specified after $start_stop"))
julia> range( 5 => 3 )
ERROR: ArgumentError: `length` must be specified after 5 => 3
Stacktrace:
[1] range(::Pair{Int64,Int64}) at .\REPL[6]:1
[2] top-level scope at REPL[7]:1
julia> range( 5 => 3, nothing ) # As of 83f5d53
ERROR: ArgumentError: `length` must be specified after 5 => 3
Stacktrace:
[1] range(::Pair{Int64,Int64}, ::Nothing) at .\REPL[2]:1
[2] top-level scope at REPL[5]:1
julia> range( 5 => 3, 2 )
5.0:-2.0:3.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like it. I was initially hesitant about using 5=>3
for a negative step, but I'm coming around to it — it's a nice feature of using =>
for this. This PR itself is great — documented, tested, ready to go.
As a purely bikesheddy surfacy opiniony thing, I think it'd be good to just do a quick up/down on triage.
I'm against this. It adds too many different ways to do the same simple thing. As more such things pile up, it becomes quite user-hostile. |
Co-authored-by: Matt Bauman <mbauman@gmail.com>
@JeffBezanson, I agree there is a user-hostile situation here with With this PR, I'm responding to two issues:
Should this PR fail, I hope we can focus on emphasizing the Thank you for your consideration. |
@JeffBezanson what do you object to? The syntax of the functionality itself? A number of people have agreed that we need a simpler way to create a range of a given length. The following options have been proposed:
This is orthogonal to improvements to the handling of kwargs by I'd prefer nothing than 2 (departure from the way arguments are usually used) or 3 (unnatural to me, and against matlab and python's linspace). I used to like 4, but now I prefer 5, for consistency with logspace and for future extensibility (eg endpoints). |
The |
What @antoine-levitt wants is As you noted in #38750 (comment) this conflicts with This proposal represents a collective brainstorming session on how to avoid the Python conflict while also getting the succinct |
What I do like about the current The current documentation is confusing until you get to the examples. Let me try to read this as a very naïve new user:
Let's try to turn that first paragraph into code: julia> range(11, length = 3) # "construct a range by length". Great. Seems verbose versus 11:13 though.
11:13
julia> range(1, 5) # "or from start to stop" `step` defaults to 1, and I specified `stop` .... what gives?
ERROR: ArgumentError: At least one of `length` or `step` must be specified
julia> r = range(1, stop = 5.5) # OK that works. This is the same as `1:5.5`. What is the difference versus colon?
1.0:1.0:5.0
julia> typeof(r) # "optionally with a given step (defaults to 1, a UnitRange)". OK, so this should be a `UnitRange`....
StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}
julia> range(1, 5; length = 5, step = 1) # "If length, stop, and step are all specified, they must agree."
ERROR: ArgumentError: Too many arguments specified; try passing only one of `stop` or `length` Hmm. That first paragraph did not make much too sense. I'll keep reading ...
I thought julia> range(1, 5.5; length = 3) # 1:3 ?
1.0:2.25:5.5 Strange. Suddenly
julia> range(;step = 1, stop = 5) # "If step and stop are provided and length is not",
ERROR: MethodError: no method matching range(; step=1, stop=5)
julia> r = range(4 ;step = 3, stop = 5) # It should probably say `start` is always needed in words.
4:3:4
juila> # "the overall range length will be computed automatically..."
julia> length(r) # " such that the elements are step spaced... " well there is only one element. Where's the space?
1
Oh, I like my intermediate values computed rationally. Why would I want them computed irrationally? It would be irrational to use this
I thought I tried this already ... julia> range(1, 3) # Except sometimes it is required to be a keyword argument??
ERROR: ArgumentError: At least one of `length` or `step` must be specified If we are stuck with the status quo in terms of syntax, could we revisit the documentation as in #37875 ? |
Yes, those are all good points and I agree. There are some existing PRs to help address them. I'm only commenting on
Yes, we cannot break existing code in 1.x. |
To be clear, nothing in this pull request causes any existing code to stop functioning. It also does not preclude a prospective |
closing based on triage discussion. (which was basically "oh god no. We'll do anything") |
This PR is clearly superseded by the superior usage of emojis in #40298 |
The pull request creates a new form of
range
wherestart
andstop
are given as aPair
along with an integerlength
:range(start => stop, length)
.The idea for providing
start => stop
as aPair
originates with @mcabbott and @MasonProtter via Zulip. Providingstart => stop
as aPair
has several advantages over other proposals:start
andstop
provided as aPair
is distinct and can be easily distinguished fromlength
.start
andstop
are provided as a separate type, allowing us to provide separate conventions due to multiple dispatch. This allows us to focus onlength
and frees us from having to assumestep = 1
iflength
is not provided in a future pull request.stop
is always included as the last element of the iteration.Because of the default positional parameterlength=2
, this proposal also implementsrange(start => stop)
which may differ fromstart:stop
andrange(start; stop=stop)
sincestep
is not assumed to be1
.This is an alternate option to pull request #39055
range(start, stop, length)
. Therelength
as positional argument has no default and a two argumentrange(start,stop)
is not allowed. It is possible for this version to coexist with #39055range(start, stop, length)
if needed.Edit 2021/01/02: Fix attribution of using
Pair
Edit 2021/01/03: Remove default
length
value of2
.length
is now required.