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

Define machinery so that Some can be used for broadcast #35778

Closed
wants to merge 11 commits into from
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ New language features
* The compiler optimization level can now be set per-module using the experimental macro
`Base.Experimental.@optlevel n`. For code that is not performance-critical, setting
this to 0 or 1 can provide significant latency improvements ([#34896]).
* `Some`containers now support broadcast as zero dimensional immutable containers. `Some(x)`
should be preferred to `Ref(x)` when you wish to exempt `x` from broadcasting ([#35778]).

Language changes
----------------
Expand Down
2 changes: 2 additions & 0 deletions base/Base.jl
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ include("broadcast.jl")
using .Broadcast
using .Broadcast: broadcasted, broadcasted_kwsyntax, materialize, materialize!

include("somebroadcast.jl")

# missing values
include("missing.jl")

Expand Down
10 changes: 5 additions & 5 deletions base/broadcast.jl
Original file line number Diff line number Diff line change
Expand Up @@ -668,15 +668,15 @@ julia> Broadcast.broadcastable([1,2,3]) # like `identity` since arrays already s
2
3

julia> Broadcast.broadcastable(Int) # Types don't support axes, indexing, or iteration but are commonly used as scalars
Base.RefValue{Type{Int64}}(Int64)
julia> Broadcast.broadcastable(Int64) # Types don't support axes, indexing, or iteration but are commonly used as scalars
Some(Int64)

julia> Broadcast.broadcastable("hello") # Strings break convention of matching iteration and act like a scalar instead
Base.RefValue{String}("hello")
Some("hello")
```
"""
broadcastable(x::Union{Symbol,AbstractString,Function,UndefInitializer,Nothing,RoundingMode,Missing,Val,Ptr,Regex,Pair}) = Ref(x)
broadcastable(::Type{T}) where {T} = Ref{Type{T}}(T)
broadcastable(x::Union{Symbol,AbstractString,Function,UndefInitializer,Nothing,RoundingMode,Missing,Val,Ptr,Regex,Pair}) = Some(x)
broadcastable(::Type{T}) where {T} = Some{Type{T}}(T)
broadcastable(x::Union{AbstractArray,Number,Ref,Tuple,Broadcasted}) = x
# Default to collecting iterables — which will error for non-iterables
broadcastable(x) = collect(x)
Expand Down
2 changes: 2 additions & 0 deletions base/some.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
A wrapper type used in `Union{Some{T}, Nothing}` to distinguish between the absence
of a value ([`nothing`](@ref)) and the presence of a `nothing` value (i.e. `Some(nothing)`).

`Some` is also used in broadcasting to treat its enclosed value as a scalar.

Use [`something`](@ref) to access the value wrapped by a `Some` object.
"""
struct Some{T}
Expand Down
18 changes: 18 additions & 0 deletions base/somebroadcast.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#Methods some Some can broadcast
getindex(s::Some) = s.value
getindex(s::Some, ::CartesianIndex{0}) = s.value

iterate(s::Some) = (s.value, nothing)
iterate( ::Some, s) = nothing

ndims(::Some) = 0
ndims(::Type{<:Some}) = 0

length(::Some) = 1
size(::Some) = ()
axes(::Some) = ()

IteratorSize(::Type{<:Some}) = HasShape{0}()
Broadcast.broadcastable(s::Some) = s

eltype(::Type{Some{T}}) where {T} = T
7 changes: 2 additions & 5 deletions doc/src/manual/arrays.md
Original file line number Diff line number Diff line change
Expand Up @@ -947,12 +947,9 @@ julia> string.(1:3, ". ", ["First", "Second", "Third"])

Sometimes, you want a container (like an array) that would normally participate in broadcast to be "protected"
from broadcast's behavior of iterating over all of its elements. By placing it inside another container
(like a single element [`Tuple`](@ref)) broadcast will treat it as a single value.
(we recommend [`Some`](@ref)), broadcast will treat it as a single value.
```jldoctest
julia> ([1, 2, 3], [4, 5, 6]) .+ ([1, 2, 3],)
([2, 4, 6], [5, 7, 9])

julia> ([1, 2, 3], [4, 5, 6]) .+ tuple([1, 2, 3])
julia> ([1, 2, 3], [4, 5, 6]) .+ Some([1, 2, 3])
([2, 4, 6], [5, 7, 9])
```

Expand Down
11 changes: 11 additions & 0 deletions test/some.jl
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,14 @@ using Base: notnothing
# isnothing()
@test !isnothing(1)
@test isnothing(nothing)

#Eltype
@test eltype(Some{Int}) == Int
@test eltype(Some(1)) == Int

# Broadcast with Some
@testset "Some Broadcast" begin
@test Some(1) .+ 1 == 2
@test Some([1, 2]) .+ [[1, 2,], [3, 4]] == [[2, 4], [4, 6]]
@test isa.(Some([1,2,3]), [Array, Dict, Int]) == [true, false, false]
end