Skip to content
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