basically,
Int32(a::UnitRange{T}) = Int32(a.start):Int32(a.stop)
basically,
Int32(a::UnitRange{T}) = Int32(a.start):Int32(a.stop)
It would be pretty weird if Int32(..)
did not return an Int32
.
Int32.(1:10)
is the natural syntax for this sort of thing in Julia.
Unfortunately, Int32.(1:10)
currently gives a dense Array
object, but it seems like a straightforward optimization to update it to give a Range
. For example, if we define
import Base.Broadcast: DefaultArrayStyle, broadcasted
broadcasted(::DefaultArrayStyle{1}, ::Type{T}, r::AbstractUnitRange) where {T<:Integer} = range(T(first(r)), length=T(length(r)))
then we would get:
julia> Int32.(1:10)
1:10
julia> typeof(ans)
UnitRange{Int32}
julia> BigInt.(1:10)
1:10
julia> typeof(ans)
UnitRange{BigInt}
Might make a good first PR if someone wanted to take this on.
ah, I think this makes sense. Would this make a breaking change though?
A range is still an AbstractArray
type, and we don’t generally guarantee what type of array a broadcast
will produce, so I think this would qualify as a “minor change” that could go into (e.g.) Julia 1.4. This would be discussed in a PR.
But people can reasonably rely on the return value of something being mutatable.
Just to add for completeness
Vector{Int32}(1:10)
works too
Favor convert
rather than constructors,
convert(UnitRange{Int32}, 1:10)
You’re stating that like a general rule. What’s the argument? I certainly favor constructors over the very verbose constructor syntax.
Especially in cases where the point is to quickly and conveniently instantiate something at the REPL.
I’d rather Int32(1):Int32(10)
in that case.
No, this materialize the iterator thus break the purpose.
The fundamental difference between constructors and convert
is that the former makes a copy but the latter doesn’t when possible. Here it doesn’t matter since we know the returned object will be of a different type from the input, so a new object will have to be returned (and UnitRange
is immutable anyway).
If you need mutability you should probably be safe and use
convert(Vector, Int32.(1:9))
This will work in Julia v1.0-1.4.
I know the correct way to do it if the change is made, but that doesn’t mean the change is non breaking.
Bug fixes are also breaking changes… I think this is arguably on the level of a bug fix.
No this is definitely not a bug fix. It returns a result that makes sense and you are changing it.
It’s using undocumented behaviour. The fact that UInt32.(1:10)
returns a Vector
is simply an implementation detail, and it’s a users fault for mutating it when there is no a priori guarantee that it’s mutable.
No, that doesn’t make any sense. It’s at most missing document (and it’s not really missing either, see below). Undocumented doesn’t mean it’s implementation detail (and FWIW implementation detail could be documented as well).
It would have made sense if all other functions and syntaxes are documented to this level (what are the exact input types, what are the output types you expect, what are the output types and values under each conditions, etc).
Your argument is just that the user should never rely on anything they see, which means that they should never try type something into the REPL to see if it works since that has absolutely zero meaning. I would actually think that’s a good thing if the documentation is better (too many GC related bugs in user code from this). This would have been the case if you are talking about a specification. The documentation is not good enough to be a specification (yet) so that reasoning is wrong.
As another example, this is the documentation for cos
.
Compute cosine of
x
, wherex
is in radians.
From your logic, it’s the user’s fault if he assume that cos(1)::Float64
(or even cos(1)
is convertible to Float64
). In fact, it’s even his fault to assume the return value is usable at all, the document doesn’t mention returning. It could compute it and return some undefined number and it’ll totally be inline with the document. It could return an RefValue
or an Vector
/Array
containing the result just like matlab and that totally satisfies the document. These are things that must be specified in a specification but is also reasoable to left undocumented otherwise since the behavior is easy to observe and can easily be reasoned about.
And again, just to get the logic as clear as possible, I’m not saying NONE of the undocumented behaviors are implementation detail. I’m not even saying that all of the observable effects are dependable. However, it’s clearly wrong to say that undocumented implies implementation detail.
Also, in this specific case, it is understood that broadcast returns array by default. Any deviation from such a convention needs documentation.
For the case of a literal 1:10
(as opposed to a:b
where a
and b
happen to be 1
and 10
), one could handle this with a macro. Something like:
i32(x::Int64) = typemin(Int32) <= x <= typemax(Int32) ? Int32(x) : x
i32(x::Expr) = Expr(x.head, i32.(x.args)...)
i32(x) = x
macro i32(x)
i32(x)
end
Now @i32 1:10
will return a UnitRange{Int32}
. The macro can also be used on larger blocks of code, for example
@i32 let s = 0
for i = 1:10
s += i
end
s
end
will return an Int32
This type of macro can be useful when targeting the GPU or embedded processors where it’s otherwise easy to accidentally introduce promotion to 64-bit.
Wouldn’t it be better to error then for values that cannot be converted?
Also, I was under the impression that GPU interfaces sanitize their input, and then keep everything in the valid type set.