-
-
Notifications
You must be signed in to change notification settings - Fork 5.7k
Deprecate defaulting to scalar in broadcast (take 2) #26435
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
Conversation
|
Does wrapping all scalars in a |
|
Numbers have both |
vchuravy
left a comment
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 this direction. Automatically unwrapping Ref is technically a breaking change, but I don't see a clean way of deprecating this. Maybe add a deprecation warning to broadcastable(::Ref)?
base/broadcast.jl
Outdated
| const DefaultMatrixStyle = DefaultArrayStyle{2} | ||
| BroadcastStyle(::Type{<:AbstractArray{T,N}}) where {T,N} = DefaultArrayStyle{N}() | ||
| BroadcastStyle(::Type{<:Ref}) = DefaultArrayStyle{0}() | ||
| BroadcastStyle(::Type{<:Union{Base.RefValue,Number}}) = DefaultArrayStyle{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.
This also changes RefArray (which is a good thing). Should there be a BroadcastStyle for RefArray?
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.
Why is that a good thing (I don't actually know what this return value means)? For context, RefArray is a pointer to precisely one element of an array. It also changes Ptr, if that matters?
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.
All arguments' BroadcastStyles are combined with a promote-like mechanism in order to select a broadcasting implementation. For the most part, it's largely used as a kludge around the lack of a "at least one vararg is of type T" dispatch, but it's more complicated to deal with cases that would otherwise be ambiguities.
Yes, Ptr has neither axes or indexing defined, so it cannot be — by itself — considered like a 0-dimensional array. I don't think RefArray is common enough to add special broadcasting support for it.
Before:
julia> identity.(Ref([1,2,3],1))
0-dimensional Array{Int64,0}:
1
After (edit: now out of date):
julia> identity.(Ref([1,2,3],1))
┌ Warning: broadcast will default to iterating over its arguments in the future. Wrap arguments of
│ type `x::Base.RefArray{Int64,Array{Int64,1},Nothing}` with `Ref(x)` to ensure they broadcast as "scalar" elements.
│ caller = top-level scope
└ @ Core :0
Base.RefArray{Int64,Array{Int64,1},Nothing}([1, 2, 3], 1, nothing)
In the future, it will be an error because:
julia> collect(Ref([1,2,3],1))
ERROR: MethodError: no method matching length(::Base.RefArray{Int64,Array{Int64,1},Nothing})
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 don't think RefArray is common enough to add special broadcasting support for it.
However, RefValue is an implementation detail, and should never be mentioned explicitly either: RefArray is simply an alternative implementation of it. We know that length(::Ref) == 1 forall Ref subtypes, we've just hesitated to add that definition to the code until needed to keep the API small.
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.
This is largely a historical accident due to how I ended up here… and trying to work around Ptr. As it stands, I can easily remove all uses of RefValue. I just pushed a change.
New after:
julia> identity.(Ref([1,2,3],1))
1
| if Base.Broadcast.BroadcastStyle(typeof(x)) isa Broadcast.Unknown | ||
| depwarn(""" | ||
| broadcast will default to iterating over its arguments in the future. Wrap arguments of | ||
| type `x::$(typeof(x))` with `Ref(x)` to ensure they broadcast as "scalar" elements. |
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.
Maybe add:
or define
broadcastable(x::MyType) = Ref(x)for the conversion to be automatic.
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'm guessing that the most common reader of this deprecation will be an end-user.
base/broadcast.jl
Outdated
| broadcastable(x::AbstractArray) = x | ||
| # In the future, default to collecting arguments. TODO: uncomment once deprecations are removed | ||
| # broadcastable(x) = BroadcastStyle(typeof(x)) isa Unknown ? collect(x) : x | ||
| # broadcastable(::Dict) = error("intentionally unimplemented to allow development in 1.x") |
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.
Will this be a different PR? We should deprecate this behaviour in 0.7
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.
AbstractDict hits the fallback ::Any deprecation. Next to that deprecation is a task-item to un-comment these definitions, so when that happens it will go from a warning to an error.
| @inline broadcast(f, A, Bs...) = | ||
| broadcast(f, combine_styles(A, Bs...), nothing, nothing, A, Bs...) | ||
| @inline function broadcast(f, A, Bs...) | ||
| A′ = broadcastable(A) |
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.
It's unfortunate that the prime looks so similar to the adjoint...
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.
¯\_(ツ)_/¯
There's more than one of us using this style in base to represent roughly-the-same-value-but-maybe-slightly-different.
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.
You could also use Ã, but I agree that the \prime style is pretty common.
base/broadcast.jl
Outdated
| broadcastable(x::AbstractArray) = x | ||
| # In the future, default to collecting arguments. TODO: uncomment once deprecations are removed | ||
| # broadcastable(x) = BroadcastStyle(typeof(x)) isa Unknown ? collect(x) : x | ||
| # broadcastable(::Dict) = error("intentionally unimplemented to allow development in 1.x") |
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.
::AbstractDict
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.
Also we might consider if NamedTuple should maybe broadcast together with a AbstractDict{Symbol} rather than by iteration index.
|
@nanosoldier |
|
Nanosoldier is going to fail without that commit I just pushed. |
|
Well it didn't listen to me anyway... @nanosoldier (Put I am just being overly cautious) |
base/deprecated.jl
Outdated
| # Also un-comment the new definition in base/indices.jl | ||
|
|
||
| # Broadcast no longer defaults to treating its arguments as scalar (#) | ||
| function Broadcast.broadcastable(x) |
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.
should have @noinline to ensure depwarn tracking works
nalimilan
left a comment
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.
Thanks for your efforts to improve broadcast!
Cc: @timholy
base/broadcast.jl
Outdated
| Return either `x` or an object like `x` such that it supports `axes` and indexing. | ||
| """ | ||
| broadcastable(x::Union{Symbol,String,Type,Function,UndefInitializer,Nothing,RoundingMode}) = Ref(x) |
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 guess you can add Missing to this list.
base/broadcast.jl
Outdated
| return dest | ||
| end | ||
|
|
||
| # Optimization for the all-Scalar case. |
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.
Is this still the "all scalar case"?
base/broadcast.jl
Outdated
| arguments and the scalars themselves in `As`. Singleton and missing dimensions | ||
| are expanded to match the extents of the other arguments by virtually repeating | ||
| the value. By default, only a limited number of types are considered scalars, | ||
| including `Number`s, `Type`s and `Function`s; all other arguments are iterated |
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.
Could give the full list (in particular strings).
| The resulting container type is established by the following rules: | ||
| - If all the arguments are scalars, it returns a scalar. |
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 imagine this rule still applies? Maybe worth keeping for clarity even if it's covered by more general rules below.
| * `map` on dictionaries previously operated on `key=>value` pairs. This behavior is deprecated, | ||
| and in the future `map` will operate only on values ([#5794]). | ||
|
|
||
| * Previously, broadcast defaulted to treating its arguments as scalars if they were not |
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.
It would be useful to add a sentence for package developers mentioning broadcastable, just like the deprecation does.
base/broadcast.jl
Outdated
| Return either `x` or an object like `x` such that it supports `axes` and indexing. | ||
| """ | ||
| broadcastable(x::Union{Symbol,AbstractString,Type,Function,UndefInitializer,Nothing,RoundingMode,Missing}) = Ref(x) |
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.
Can remove Type here since it has a method below.
4f2c4fe to
fb59643
Compare
|
@nanosoldier |
| broadcastable(x) | ||
| Return either `x` or an object like `x` such that it supports `axes` and indexing. | ||
| """ |
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.
Perhaps add an example?
# Examples
```jldoctest
julia> Broadcast.broadcastable([1, 2, 3])
3-element Array{Int64,1}:
1
2
3
julia> Broadcast.broadcastable("Hello, world!")
Base.RefValue{String}("Hello, world!")
```
base/broadcast.jl
Outdated
| Return either `x` or an object like `x` such that it supports `axes` and indexing. | ||
| If `x` supports iteration, the returned value should match [`collect(x)`](@ref). |
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.
This isn't true. e.g. collect(3) returns an Array{Int,0}, but broadcastable just returns 3.
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.
Fair. How about I add with respect to axes and indexing?
Edit: or If `x` supports iteration, the returned value should have the same `axes` and indexing behaviors as [`collect(x)`](@ref)
3272ab2 to
37bd43e
Compare
|
@nanosoldier |
|
Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @ararslan |
|
That does not look too bad – all these regressions seem like they could be in the noise. |
|
The slowdowns for tuple broadcasting seem like they might be real. They are testing: for various |
|
Well that was a remarkably simple fix. Once CI passes again I'd like to merge. |
|
While we wait on CI. |
Except that Nanosoldier takes about 3 times longer than our longest running CI build. 😉 |
|
I had to restart the server. @nanosoldier |
|
Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @ararslan |
|
There is still a regression for |
|
I fought with that remaining allocation for quite some time with no success. It's the Given that this regression is small and the optimizer is getting revamped, I'm inclined to merge. |
Broadcast now calls a special helper function, `broadcastable`, on each argument. It ensures that the arguments support indexing and have a shape, or otherwise have defined a custom `BroadcastStyle`.
6c76d2b to
54f54a5
Compare
|
Would be interesting to see if the new optimizer can eliminate that allocation. |
|
The manual section on the broadcasting interface still uses when building the docs. |
|
And Lines 593 to 596 in 7eb5d44
julia> broadcast(+, 1.0, (0, -2.0), Ref(1))
(2.0, 0.0)That example seem rather pointless now that |
Amazing what a great exercise writing documentation is. This is a followup to #26435, and more clearly lays out the requirements for defining a custom (non-AbstractArray) type that can participate in broadcasting. The biggest functional change here is that types that define their own BroadcastStyle must now also implement `broadcastable` to be a no-op.
|
Thank you! See #26601. |
* Make LinAlg fess up to using Base._return_type * Simplify broadcastable a bit further and add docs Amazing what a great exercise writing documentation is. This is a followup to #26435, and more clearly lays out the requirements for defining a custom (non-AbstractArray) type that can participate in broadcasting. The biggest functional change here is that types that define their own BroadcastStyle must now also implement `broadcastable` to be a no-op. * Trailing comma, doc fixes from review; more tests
|
Thanks for the design of The only problem is defining If |
|
There is a default, which is |
|
@fredrikekre https://discourse.julialang.org/t/broadcasting-over-structs-in-1-0-what-am-i-supposed-to-do/13408 We have to implement the interface like julia> Broadcast.broadcastable(m::M) = Ref(m)to make a type work as a scalar. A minimal working example: julia> struct A end
julia> println.(A())
ERROR: MethodError: no method matching length(::A)
Closest candidates are:
length(::Core.SimpleVector) at essentials.jl:571
length(::Base.MethodList) at reflection.jl:728
length(::Core.MethodTable) at reflection.jl:802
...
Stacktrace:
[1] _similar_for(::UnitRange{Int64}, ::Type, ::A, ::Base.HasLength) at ./array.jl:532
[2] _collect(::UnitRange{Int64}, ::A, ::Base.HasEltype, ::Base.HasLength) at ./array.jl:563
[3] collect(::A) at ./array.jl:557
[4] broadcastable(::A) at ./broadcast.jl:609
[5] broadcasted(::Function, ::A) at ./broadcast.jl:1134
[6] top-level scope at none:0 |
This PR replaces #26212. The end result is the same, but I like this implementation much better.
This PR consists of two commits — both pass tests locally and I'd like to keep them separate. The first commit changes the default behavior of broadcast to collect its arguments instead of treating them as scalars. Broadcast now calls a special helper function,
broadcastable, on each argument. It ensures that the arguments support indexing and have a shape, or otherwise have defined a customBroadcastStyle. The second commit layers on top a deprecation mechanism that makes this — almost entirely — non-breaking.I believe this to be a compromise that will fix #18618 once deprecations are removed. Note that I kept all the types that are currently tested to behave as scalars as specializing
broadcastableto return something that supports both indexing andaxes, or otherwise has its ownBroadcastStyleand internal implementation.This also changes the behavior of broadcast such that if it ever wants to return a 0-dimensional array, it'll just "unwrap" that 0-dimensional array and return the contents instead.
Reasons I like this approach better than #26212:
Refto make them broadcast as scalars. In RFC: Deprecate defaulting to scalar in broadcast #26212, adding aRefto an otherwise scalar expression would result in a 0-dimensional array as a result. Yes, I generally have a very strong aversion to these kinds of auto-wrapping/unwrapping, but in this case I believe it is warranted and ends up with fewer special cases. 0-dimensional arrays and "scalar" types are treated identically as far as broadcast is concerned.broadcastablepsuedo-conversion method makes it easier for other code to "work-around" broadcast and know how it'll behave. Callbroadcastableon a thing and then you'll be able to know whatbroadcastis going to do on the basis of itsaxes. No digging around inBroadcastStyles or other internals — this one function makes sure that iterables and everything else can just work. (This isn't quite true, yet, asRefisn't fully conforming with the iterable andaxesspec, but I'd like to implement that separately.)Broadcast.Scalar()styles.