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 construction with colon, one, and zero #41840

Closed
wants to merge 3 commits into from

Conversation

mkitti
Copy link
Contributor

@mkitti mkitti commented Aug 9, 2021

Currently in Base, we have the methods one and zero. Implicitly, we also then have the types typeof(one) and typeof(zero). In this pull request, we dispatch (:) on typeof(one) and typeof(zero).

When one is used as start, start is one(stop). Likewise for zero.
When one is used as stop, stop is one(start). Likewise for zero.
When one is used as step, it is equivalent to start:stop.

one:(stop::T) where {T <: Integer} returns a Base.OneTo.

It's not clear to me where the best place to document this behavior is at the moment.

Concepts here were developed in during a conversation with @oschulz .

@oschulz
Copy link
Contributor

oschulz commented Aug 9, 2021

Copied over from #41840:

I'm not sure if there's already an issue on that - the range-from-one and range-from-zero could be done very elegantly if we had precision-independent representations for one and zero in the language, with separate types for one and zero (along the lines of https://github.com/perrutquist/Zeros.jl), like we have with pi and so on. It would be very useful in many contexts anyway (often we use true/false as a minimum-precision one/zero literal, but it's not very readable and can't be dispatched on).

This approach has worked nicely for constants like π and , and one and zero are even more fundamental than those two. :-)

If we had special types for one and zero, let's call them One an Zero (we should define convenience literals), then it would be quite natural that

Zero():n == Base.ZeroTo(n)
One():n == Base.OneTo(n)

(We currently don't have Base.ZeroTo yet - of course).

That would be even cleaner than one:n and zero:n, and having a dispatchable one and zero would open up lot's of potential in other places as well (removing the need for constructs like FillArrays.Ones, etc.

A convenience Syntax for zero-based ranges should wait until we have something like Base.ZeroTo in the language, so we won't break things later on.

@mkitti
Copy link
Contributor Author

mkitti commented Aug 10, 2021

What is the difference between One and typeof(one)? Couldn't we just do const One = typeof(one)?

@oschulz
Copy link
Contributor

oschulz commented Aug 10, 2021

What is the difference between One and typeof(one)?

One would be a number that can be used in calculations (with resulting optimizations), so typeof(One) would Integer or Real, whereas one is a function.

@vtjnash
Copy link
Member

vtjnash commented Aug 16, 2021

this seems too clever, and thus unlikely to work very well in practice (see discussion in #41840)

@mkitti
Copy link
Contributor Author

mkitti commented Aug 17, 2021

I think the main discussion occured in #41853. This is 41840.

This pull request is quite distinct from #41853 as this is a pull request extending existing methods in to work on existing types in Base. Rather than introducing "unending stream of bugs and missing methods" #41853 (comment) as suggested by @JeffBezanson , it gives the user an easy to use syntax to avoid bugs caused by accidental type promotion. If there are missing methods that should take typeof(one), then those methods are already missing.

Here is an example of how easy accidental type promotion is in Julia at the moment.

julia> stop = UInt8(5)
0x05

julia> 1:stop
1:5

julia> typeof(1:stop) # Where did the Int64 come from? How clear is this to a new user of Julia?
UnitRange{Int64}

julia> one(stop):stop # This is awkward and redundant syntax, but ...
0x01:0x05

julia> typeof(ans) # ... it does not make Int64s appear from nowhere.
UnitRange{UInt8}

In contrast, with this pull request, observe how natural and intuitive the following code looks and functions.

julia> (:)(::typeof(one), stop::T) where {T<:Integer} = Base.OneTo(stop)
: (generic function with 1 method)

julia> stop = UInt8(5)
0x05

julia> one:stop
Base.OneTo(5)

julia> typeof(ans) # No accidental type promotion
Base.OneTo{UInt8}

Accidental type promotion leads to bugs such as #39808 in Base due to literal 1s as demonstrated in fixes such as #40467. While I do not think @ararslan meant to introduce type promotion to Int64 below, the use of a literal 1 had that unintended effect. While this anecdote applied to the internal method Base._linspace, this pull request addresses a common public syntax.

start_n == stop_n && return steprangelen_hp(T, (start_n, den), (zero(start_n), den), 0, len, 1)

Rather than introducing abstract algebraic concepts and types into Base as in #41853, this pull request extends existing methods with existing types to solve the practical issue of accidental type promotion in order to avoid demonstrated bugs via an intuitive and compact syntax. This pull request deserves its own discussion distinct from that had in #41853.

Thank you for the consideration.

@ararslan
Copy link
Member

While I do not think @ararslan meant to introduce type promotion to Int64 below

I program only with malicious intent 👺

perrutquist added a commit to perrutquist/Zeros.jl that referenced this pull request Aug 17, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants