Skip to content

Commit 59b8341

Browse files
committed
make Some type a zero-dim broadcast container (e.g. a scalar)
Replaces #35778 Replaces #39184 Fixes #39151 Refs #35675 Refs #43200
1 parent 08ea2d8 commit 59b8341

File tree

4 files changed

+59
-19
lines changed

4 files changed

+59
-19
lines changed

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ Standard library changes
8282
* TCP socket objects now expose `closewrite` functionality and support half-open mode usage ([#40783]).
8383
* Intersect returns a result with the eltype of the type-promoted eltypes of the two inputs ([#41769]).
8484
* `Iterators.countfrom` now accepts any type that defines `+`. ([#37747])
85+
* `Some`containers now support broadcast as zero dimensional immutable containers. `Some(x)`
86+
should be preferred to `Ref(x)` when you wish to exempt `x` from broadcasting ([#35778]).
8587

8688
#### InteractiveUtils
8789
* A new macro `@time_imports` for reporting any time spent importing packages and their dependencies ([#41612])

base/broadcast.jl

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -614,8 +614,9 @@ Base.@propagate_inbounds Base.getindex(bc::Broadcasted) = bc[CartesianIndex(())]
614614
615615
Index into `A` with `I`, collapsing broadcasted indices to their singleton indices as appropriate.
616616
"""
617-
Base.@propagate_inbounds _broadcast_getindex(A::Union{Ref,AbstractArray{<:Any,0},Number}, I) = A[] # Scalar-likes can just ignore all indices
617+
Base.@propagate_inbounds _broadcast_getindex(A::Union{Ref,Some,AbstractArray{<:Any,0},Number}, I) = A[] # Scalar-likes can just ignore all indices
618618
Base.@propagate_inbounds _broadcast_getindex(::Ref{Type{T}}, I) where {T} = T
619+
Base.@propagate_inbounds _broadcast_getindex(::Some{Type{T}}, I) where {T} = T
619620
# Tuples are statically known to be singleton or vector-like
620621
Base.@propagate_inbounds _broadcast_getindex(A::Tuple{Any}, I) = A[1]
621622
Base.@propagate_inbounds _broadcast_getindex(A::Tuple, I) = A[I[1]]
@@ -661,6 +662,20 @@ Base.@propagate_inbounds function _broadcast_getindex(bc::Broadcasted{<:Any,<:An
661662
args = _getindex(tail(tail(bc.args)), I)
662663
return _broadcast_getindex_evalf(bc.f, T, S, args...)
663664
end
665+
Base.@propagate_inbounds function _broadcast_getindex(bc::Broadcasted{<:Any,<:Any,<:Any,<:Tuple{Some{Type{T}},Vararg{Any}}}, I) where {T}
666+
args = _getindex(tail(bc.args), I)
667+
return _broadcast_getindex_evalf(bc.f, T, args...)
668+
end
669+
Base.@propagate_inbounds function _broadcast_getindex(bc::Broadcasted{<:Any,<:Any,<:Any,<:Tuple{Any,Some{Type{T}},Vararg{Any}}}, I) where {T}
670+
arg1 = _broadcast_getindex(bc.args[1], I)
671+
args = _getindex(tail(tail(bc.args)), I)
672+
return _broadcast_getindex_evalf(bc.f, arg1, T, args...)
673+
end
674+
Base.@propagate_inbounds function _broadcast_getindex(bc::Broadcasted{<:Any,<:Any,<:Any,<:Tuple{Some{Type{T}},Some{Type{S}},Vararg{Any}}}, I) where {T,S}
675+
args = _getindex(tail(tail(bc.args)), I)
676+
return _broadcast_getindex_evalf(bc.f, T, S, args...)
677+
end
678+
664679

665680
# Utilities for _broadcast_getindex
666681
Base.@propagate_inbounds _getindex(args::Tuple, I) = (_broadcast_getindex(args[1], I), _getindex(tail(args), I)...)
@@ -691,15 +706,15 @@ julia> Broadcast.broadcastable([1,2,3]) # like `identity` since arrays already s
691706
3
692707
693708
julia> Broadcast.broadcastable(Int) # Types don't support axes, indexing, or iteration but are commonly used as scalars
694-
Base.RefValue{Type{Int64}}(Int64)
709+
Base.Some{Type{Int64}}(Int64)
695710
696711
julia> Broadcast.broadcastable("hello") # Strings break convention of matching iteration and act like a scalar instead
697-
Base.RefValue{String}("hello")
712+
Base.Some{String}("hello")
698713
```
699714
"""
700-
broadcastable(x::Union{Symbol,AbstractString,Function,UndefInitializer,Nothing,RoundingMode,Missing,Val,Ptr,AbstractPattern,Pair,IO}) = Ref(x)
701-
broadcastable(::Type{T}) where {T} = Ref{Type{T}}(T)
702-
broadcastable(x::Union{AbstractArray,Number,AbstractChar,Ref,Tuple,Broadcasted}) = x
715+
broadcastable(x::Union{Symbol,AbstractString,Function,UndefInitializer,Nothing,RoundingMode,Missing,Val,Ptr,AbstractPattern,Pair,IO}) = Some(x)
716+
broadcastable(::Type{T}) where {T} = Some{Type{T}}(T)
717+
broadcastable(x::Union{AbstractArray,Number,AbstractChar,Some,Ref,Tuple,Broadcasted}) = x
703718
# Default to collecting iterables — which will error for non-iterables
704719
broadcastable(x) = collect(x)
705720
broadcastable(::Union{AbstractDict, NamedTuple}) = throw(ArgumentError("broadcasting over dictionaries and `NamedTuple`s is reserved"))
@@ -722,7 +737,7 @@ combine_eltypes(f, args::Tuple) =
722737
"""
723738
broadcast(f, As...)
724739
725-
Broadcast the function `f` over the arrays, tuples, collections, [`Ref`](@ref)s and/or scalars `As`.
740+
Broadcast the function `f` over the arrays, tuples, collections, [`Some`](@ref)s and/or scalars `As`.
726741
727742
Broadcasting applies the function `f` over the elements of the container arguments and the
728743
scalars themselves in `As`. Singleton and missing dimensions are expanded to match the
@@ -781,7 +796,7 @@ julia> abs.((1, -2))
781796
julia> broadcast(+, 1.0, (0, -2.0))
782797
(1.0, -1.0)
783798
784-
julia> (+).([[0,2], [1,3]], Ref{Vector{Int}}([1,-1]))
799+
julia> (+).([[0,2], [1,3]], Some{Vector{Int}}([1,-1]))
785800
2-element Vector{Vector{Int64}}:
786801
[1, 1]
787802
[2, 2]

base/some.jl

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,39 @@
11
# This file is a part of Julia. License is MIT: https://julialang.org/license
22

33
"""
4-
Some{T}
4+
Some{T} <: AbstractArray{T,0}
55
66
A wrapper type used in `Union{Some{T}, Nothing}` to distinguish between the absence
77
of a value ([`nothing`](@ref)) and the presence of a `nothing` value (i.e. `Some(nothing)`).
88
99
Use [`something`](@ref) to access the value wrapped by a `Some` object.
1010
"""
11-
struct Some{T}
11+
struct Some{T} <: AbstractArray{T,0}
1212
value::T
1313
end
1414

1515
Some(::Type{T}) where {T} = Some{Type{T}}(T)
1616

17-
promote_rule(::Type{Some{T}}, ::Type{Some{S}}) where {T, S<:T} = Some{T}
17+
eltype(x::Type{<:Some{T}}) where {T} = @isdefined(T) ? T : Any
18+
size(x::Some) = ()
19+
axes(x::Some) = ()
20+
length(x::Some) = 1
21+
isempty(x::Some) = false
22+
ndims(x::Some) = 0
23+
ndims(::Type{<:Some}) = 0
24+
iterate(r::Some) = (r.value, nothing)
25+
getindex(r::Some) = r.value
26+
iterate(r::Some, s) = nothing
27+
IteratorSize(::Type{<:Some}) = HasShape{0}()
1828

1929
nonnothingtype(::Type{T}) where {T} = typesplit(T, Nothing)
30+
function nonnothingtype_checked(T::Type)
31+
R = nonnothingtype(T)
32+
R >: T && error("could not compute non-nothing type")
33+
return R
34+
end
35+
36+
promote_rule(::Type{Some{T}}, ::Type{Some{S}}) where {T, S<:T} = Some{T}
2037
promote_rule(T::Type{Nothing}, S::Type) = Union{S, Nothing}
2138
function promote_rule(T::Type{>:Nothing}, S::Type)
2239
R = nonnothingtype(T)
@@ -26,12 +43,6 @@ function promote_rule(T::Type{>:Nothing}, S::Type)
2643
return Union{R, Nothing}
2744
end
2845

29-
function nonnothingtype_checked(T::Type)
30-
R = nonnothingtype(T)
31-
R >: T && error("could not compute non-nothing type")
32-
return R
33-
end
34-
3546
convert(::Type{T}, x::T) where {T>:Nothing} = x
3647
convert(::Type{T}, x) where {T>:Nothing} = convert(nonnothingtype_checked(T), x)
3748
convert(::Type{Nothing}, x) = throw(MethodError(convert, (Nothing, x)))

test/broadcast.jl

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,13 @@ end
427427
@test (+).(Ref(1), Ref(2)) == 3
428428
@test (+).([[0,2], [1,3]], Ref{Vector{Int}}([1,-1])) == [[1,1], [2,2]]
429429

430+
# Some as 0-dimensional array for broadcast
431+
@test (-).(C_NULL, C_NULL)::UInt == 0
432+
@test (+).(1, Some(2)) == 3
433+
@test (+).(Some(1), Some(2)) == 3
434+
@test (+).([[0,2], [1,3]], Some{Vector{Int}}([1,-1])) == [[1,1], [2,2]]
435+
436+
430437
# Check that broadcast!(f, A) populates A via independent calls to f (#12277, #19722),
431438
# and similarly for broadcast!(f, A, numbers...) (#19799).
432439
@test let z = 1; A = broadcast!(() -> z += 1, zeros(2)); A[1] != A[2]; end
@@ -573,6 +580,11 @@ let io = IOBuffer()
573580
broadcast(x -> print(io, x), [Ref(1.0)])
574581
@test String(take!(io)) == "Base.RefValue{Float64}(1.0)"
575582
end
583+
@test getindex.([Some(1), Some(2)]) == [1, 2]
584+
let io = IOBuffer()
585+
broadcast(x -> print(io, x), [Some(1.0)])
586+
@test String(take!(io)) == "Some(1.0)"
587+
end
576588

577589
# Test that broadcast's promotion mechanism handles closures accepting more than one argument.
578590
# (See issue #19641 and referenced issues and pull requests.)
@@ -635,7 +647,7 @@ end
635647
@test broadcast(foo, "x", [1, 2, 3]) == ["hello", "hello", "hello"]
636648

637649
@test isequal(
638-
[Set([1]), Set([2])] .∪ Ref(Set([3])),
650+
[Set([1]), Set([2])] .∪ Some(Set([3])),
639651
[Set([1, 3]), Set([2, 3])])
640652
end
641653

@@ -916,7 +928,7 @@ end
916928
@test reduce(paren, bcraw) == foldl(paren, xs)
917929

918930
# issue #41055
919-
bc = Broadcast.instantiate(Broadcast.broadcasted(Base.literal_pow, Ref(^), [1,2], Ref(Val(2))))
931+
bc = Broadcast.instantiate(Broadcast.broadcasted(Base.literal_pow, Some(^), [1,2], Some(Val(2))))
920932
@test sum(bc, dims=1, init=0) == [5]
921933
bc = Broadcast.instantiate(Broadcast.broadcasted(*, ['a','b'], 'c'))
922934
@test prod(bc, dims=1, init="") == ["acbc"]

0 commit comments

Comments
 (0)