diff --git a/base/Base.jl b/base/Base.jl index 5a4826fe5df26..b6e66665fd625 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -216,6 +216,8 @@ include("multidimensional.jl") include("permuteddimsarray.jl") using .PermutedDimsArrays +include("fillarrays.jl") + include("broadcast.jl") using .Broadcast using .Broadcast: broadcasted, broadcasted_kwsyntax, materialize, materialize! diff --git a/base/broadcast.jl b/base/broadcast.jl index fd3fcba74fb57..4ddd6ab9385e3 100644 --- a/base/broadcast.jl +++ b/base/broadcast.jl @@ -9,7 +9,8 @@ module Broadcast using .Base.Cartesian using .Base: Indices, OneTo, tail, to_shape, isoperator, promote_typejoin, @pure, - _msk_end, unsafe_bitgetindex, bitcache_chunks, bitcache_size, dumpbitcache, unalias + _msk_end, unsafe_bitgetindex, bitcache_chunks, bitcache_size, dumpbitcache, unalias, + AbstractFill, Fill, getindex_value import .Base: copy, copyto!, axes export broadcast, broadcast!, BroadcastStyle, broadcast_axes, broadcastable, dotview, @__dot__, broadcast_preserving_zero_d, BroadcastFunction, andand, oror @@ -691,14 +692,16 @@ julia> Broadcast.broadcastable([1,2,3]) # like `identity` since arrays already s 3 julia> Broadcast.broadcastable(Int) # Types don't support axes, indexing, or iteration but are commonly used as scalars -Base.RefValue{Type{Int64}}(Int64) +0-dimensional Fill{Type{Int64}, 0, Tuple{}}: +Int64 julia> Broadcast.broadcastable("hello") # Strings break convention of matching iteration and act like a scalar instead -Base.RefValue{String}("hello") +0-dimensional Fill{String, 0, Tuple{}}: +"hello" ``` """ -broadcastable(x::Union{Symbol,AbstractString,Function,UndefInitializer,Nothing,RoundingMode,Missing,Val,Ptr,AbstractPattern,Pair}) = Ref(x) -broadcastable(::Type{T}) where {T} = Ref{Type{T}}(T) +broadcastable(x::Union{Symbol,AbstractString,Function,UndefInitializer,Nothing,RoundingMode,Missing,Val,Ptr,AbstractPattern,Pair}) = Fill(x) +broadcastable(::Type{T}) where {T} = Fill{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) @@ -1377,4 +1380,35 @@ function Base.show(io::IO, op::BroadcastFunction) end Base.show(io::IO, ::MIME"text/plain", op::BroadcastFunction) = show(io, op) + +# Fill Broadcasting +function broadcasted(::DefaultArrayStyle{N}, op, r::AbstractFill{T,N}) where {T,N} + return Fill(op(getindex_value(r)), axes(r)) +end + +function broadcasted(::DefaultArrayStyle, op, a::AbstractFill, b::AbstractFill) + val = op(getindex_value(a), getindex_value(b)) + return Fill(val, broadcast_shape(axes(a), axes(b))) +end + +# Need to prevent array-valued fills from broadcasting over entry +_broadcast_getindex_value(a::AbstractFill{<:Number}) = getindex_value(a) +_broadcast_getindex_value(a::AbstractFill) = Ref(getindex_value(a)) + +function broadcasted(::DefaultArrayStyle{1}, ::typeof(*), a::AbstractFill, b::AbstractRange) + broadcast_shape(axes(a), axes(b)) == axes(b) || throw(ArgumentError("Cannot broadcast $a and $b. Convert $b to a Vector first.")) + return broadcasted(*, _broadcast_getindex_value(a), b) +end + +function broadcasted(::DefaultArrayStyle{1}, ::typeof(*), a::AbstractRange, b::AbstractFill) + broadcast_shape(axes(a), axes(b)) == axes(a) || throw(ArgumentError("Cannot broadcast $a and $b. Convert $b to a Vector first.")) + return broadcasted(*, a, _broadcast_getindex_value(b)) +end + +broadcasted(::DefaultArrayStyle{N}, op, r::AbstractFill{T,N}, x::Number) where {T,N} = Fill(op(getindex_value(r),x), axes(r)) +broadcasted(::DefaultArrayStyle{N}, op, x::Number, r::AbstractFill{T,N}) where {T,N} = Fill(op(x, getindex_value(r)), axes(r)) +broadcasted(::DefaultArrayStyle{N}, op, r::AbstractFill{T,N}, x::Ref) where {T,N} = Fill(op(getindex_value(r),x[]), axes(r)) +broadcasted(::DefaultArrayStyle{N}, op, x::Ref, r::AbstractFill{T,N}) where {T,N} = Fill(op(x[], getindex_value(r)), axes(r)) + + end # module diff --git a/base/exports.jl b/base/exports.jl index 9a6cdce86b38c..a29048fedf8ab 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -48,6 +48,7 @@ export Dims, Enum, ExponentialBackOff, + Fill, IndexCartesian, IndexLinear, IndexStyle, diff --git a/base/fillarrays.jl b/base/fillarrays.jl new file mode 100644 index 0000000000000..766da0463ddaa --- /dev/null +++ b/base/fillarrays.jl @@ -0,0 +1,134 @@ + +# Basic structure of a FillArray, the last version of fill. +# These definitions are *intended* to be pirated and extended by FillArrays.jl (which originally defined them) + +""" + AbstractFill{T, N, Axes} <: AbstractArray{T, N} +Supertype for lazy array types whose entries are all equal to constant. + +For more `Fill` functionality, use the FillArrays.jl package. +""" +abstract type AbstractFill{T, N, Axes} <: AbstractArray{T, N} end + +==(a::AbstractFill, b::AbstractFill) = axes(a) == axes(b) && getindex_value(a) == getindex_value(b) + +@inline function getindex(F::AbstractFill, k::Integer) + @boundscheck checkbounds(F, k) + getindex_value(F) +end + +@inline function getindex(F::AbstractFill{T, N}, kj::Vararg{<:Integer, N}) where {T, N} + @boundscheck checkbounds(F, kj...) + getindex_value(F) +end + +@inline function setindex!(F::AbstractFill, v, k::Integer) + @boundscheck checkbounds(F, k) + v == getindex_value(F) || throw(ArgumentError("Cannot setindex! to $v for an AbstractFill with value $(getindex_value(F)).")) + F +end + +@inline function setindex!(F::AbstractFill{T, N}, v, kj::Vararg{<:Integer, N}) where {T, N} + @boundscheck checkbounds(F, kj...) + v == getindex_value(F) || throw(ArgumentError("Cannot setindex! to $v for an AbstractFill with value $(getindex_value(F)).")) + F +end + +@inline function fill!(F::AbstractFill, v) + v == getindex_value(F) || throw(ArgumentError("Cannot fill! with $v an AbstractFill with value $(getindex_value(F)).")) + F +end + +IndexStyle(::Type{<:AbstractFill{<:Any,N,<:NTuple{N,Base.OneTo{Int}}}}) where N = IndexLinear() + + +""" + Fill{T, N, Axes} + where `Axes <: Tuple{Vararg{AbstractUnitRange,N}}` +A lazy representation of an array of dimension `N` +whose entries are all equal to a constant of type `T`, +with axes of type `Axes`. +Typically created by `Fill` or `Zeros` or `Ones` +# Examples +```jldoctest +julia> Fill(1) +0-dimensional Fill{Int64, 0, Tuple{}}: +1 + +julia> Fill(7, (2,3)) +2×3 Fill{Int64,2,Tuple{Base.OneTo{Int64},Base.OneTo{Int64}}}: + 7 7 7 + 7 7 7 + +julia> Fill{Float64, 1, Tuple{UnitRange{Int64}}}(7., (1:2,)) +2-element Fill{Float64,1,Tuple{UnitRange{Int64}}} with indices 1:2: + 7.0 + 7.0 +``` + +For more `Fill` functionality and efficient routines, use the FillArrays.jl package. +""" +struct Fill{T, N, Axes} <: AbstractFill{T, N, Axes} + value::T + axes::Axes + + Fill{T,N,Axes}(x::T, sz::Axes) where Axes<:Tuple{Vararg{AbstractUnitRange,N}} where {T, N} = + new{T,N,Axes}(x,sz) + Fill{T,0,Tuple{}}(x::T, sz::Tuple{}) where T = new{T,0,Tuple{}}(x, sz) +end + +Fill{T,N,Axes}(x, sz::Axes) where Axes<:Tuple{Vararg{AbstractUnitRange,N}} where {T, N} = + Fill{T,N,Axes}(convert(T, x)::T, sz) + +Fill{T,0}(x::T, ::Tuple{}) where T = Fill{T,0,Tuple{}}(x, ()) # ambiguity fix + +@inline Fill{T, N}(x::T, sz::Axes) where Axes<:Tuple{Vararg{AbstractUnitRange,N}} where {T, N} = + Fill{T,N,Axes}(x, sz) +@inline Fill{T, N}(x, sz::Axes) where Axes<:Tuple{Vararg{AbstractUnitRange,N}} where {T, N} = + Fill{T,N}(convert(T, x)::T, sz) + +@inline Fill{T, N}(x, sz::SZ) where SZ<:Tuple{Vararg{Integer,N}} where {T, N} = + Fill{T,N}(x, Base.OneTo.(sz)) +@inline Fill{T, N}(x, sz::Vararg{Integer, N}) where {T, N} = Fill{T,N}(convert(T, x)::T, sz) + + +@inline Fill{T}(x, sz::Vararg{<:Integer,N}) where {T, N} = Fill{T, N}(x, sz) +@inline Fill{T}(x, sz::Tuple{Vararg{<:Any,N}}) where {T, N} = Fill{T, N}(x, sz) +""" `Fill(x, dims...)` construct lazy version of `fill(x, dims...)` """ +@inline Fill(x::T, sz::Vararg{<:Integer,N}) where {T, N} = Fill{T, N}(x, sz) +""" `Fill(x, dims)` construct lazy version of `fill(x, dims)` """ +@inline Fill(x::T, sz::Tuple{Vararg{<:Any,N}}) where {T, N} = Fill{T, N}(x, sz) + +# We restrict to when T is specified to avoid ambiguity with a Fill of a Fill +@inline Fill{T}(F::Fill{T}) where T = F +@inline Fill{T,N}(F::Fill{T,N}) where {T,N} = F +@inline Fill{T,N,Axes}(F::Fill{T,N,Axes}) where {T,N,Axes} = F + +@inline axes(F::Fill) = F.axes +@inline size(F::Fill) = length.(F.axes) + +AbstractArray{T}(F::Fill{T}) where T = F +AbstractArray{T,N}(F::Fill{T,N}) where {T,N} = F +AbstractArray{T}(F::Fill{V,N}) where {T,V,N} = Fill{T}(convert(T, F.value)::T, F.axes) +AbstractArray{T,N}(F::Fill{V,N}) where {T,V,N} = Fill{T}(convert(T, F.value)::T, F.axes) + +convert(::Type{AbstractArray{T}}, F::Fill{T}) where T = F +convert(::Type{AbstractArray{T,N}}, F::Fill{T,N}) where {T,N} = F +convert(::Type{AbstractArray{T}}, F::Fill) where {T} = AbstractArray{T}(F) +convert(::Type{AbstractArray{T,N}}, F::Fill) where {T,N} = AbstractArray{T,N}(F) +convert(::Type{AbstractFill}, F::AbstractFill) = F +convert(::Type{AbstractFill{T}}, F::AbstractFill) where T = convert(AbstractArray{T}, F) +convert(::Type{AbstractFill{T,N}}, F::AbstractFill) where {T,N} = convert(AbstractArray{T,N}, F) + +copy(F::Fill) = Fill(F.value, F.axes) + +getindex(F::Fill{<:Any,0}) = getindex_value(F) + +@inline getindex_value(F::Fill) = F.value + +map(f::Function, r::AbstractFill) = Fill(f(getindex_value(r)), axes(r)) + +iterate(f::AbstractFill{<:Any, 0}) = (getindex_value(f), nothing) +iterate( ::AbstractFill{<:Any, 0}, s) = nothing + +getindex(f::AbstractFill{<:Any, 0}, ::CartesianIndex{0}) = getindex_value(f) diff --git a/doc/src/manual/arrays.md b/doc/src/manual/arrays.md index 2afc264556713..82c0b3efb28e9 100644 --- a/doc/src/manual/arrays.md +++ b/doc/src/manual/arrays.md @@ -946,13 +946,10 @@ 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. +from broadcast's behavior of iterating over all of its elements. By placing it inside another container 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]) .+ Fill([1, 2, 3]) ([2, 4, 6], [5, 7, 9]) ``` diff --git a/test/choosetests.jl b/test/choosetests.jl index 4a3b4c7ddd028..4379f56873fef 100644 --- a/test/choosetests.jl +++ b/test/choosetests.jl @@ -55,7 +55,7 @@ function choosetests(choices = []) "boundscheck", "error", "ambiguous", "cartesian", "osutils", "channels", "iostream", "secretbuffer", "specificity", "reinterpretarray", "syntax", "corelogging", "missing", "asyncmap", - "smallarrayshrink", "opaque_closure", "filesystem", "download" + "smallarrayshrink", "fillarrays", "opaque_closure", "filesystem", "download" ] tests = [] diff --git a/test/fillarrays.jl b/test/fillarrays.jl new file mode 100644 index 0000000000000..e4251b9c1fda7 --- /dev/null +++ b/test/fillarrays.jl @@ -0,0 +1,135 @@ +@testset "fill array constructors and convert" begin + + @test Fill(1) ≡ Fill{Int}(1) ≡ Fill{Int,0}(1) ≡ Fill{Int,0,Tuple{}}(1,()) + @test Fill(1,(-1,5)) ≡ Fill(1,(0,5)) + @test Fill(1.0,5) isa AbstractVector{Float64} + @test Fill(1.0,5,5) isa AbstractMatrix{Float64} + @test Fill(1,5) ≡ Fill(1,(5,)) + @test Fill(1,5,5) ≡ Fill(1,(5,5)) + @test eltype(Fill(1.0,5,5)) == Float64 + + @test_throws InexactError Matrix{Float64}(Fill(1.0+1.0im,10,10)) + + for T in (Int, Float64) + F = Fill{T}(one(T), 5) + + @test eltype(F) == T + @test Array(F) == fill(one(T),5) + @test Array{T}(F) == fill(one(T),5) + @test Array{T,1}(F) == fill(one(T),5) + + F = Fill{T}(one(T), 5, 5) + @test eltype(F) == T + @test Array(F) == fill(one(T),5,5) + @test Array{T}(F) == fill(one(T),5,5) + @test Array{T,2}(F) == fill(one(T),5,5) + + @test convert(AbstractArray,F) ≡ F + @test convert(AbstractArray{T},F) ≡ AbstractArray{T}(F) ≡ F + @test convert(AbstractMatrix{T},F) ≡ AbstractMatrix{T}(F) ≡ F + + @test convert(AbstractArray{Float32},F) ≡ AbstractArray{Float32}(F) ≡ + Fill{Float32}(one(Float32),5,5) + @test convert(AbstractMatrix{Float32},F) ≡ AbstractMatrix{Float32}(F) ≡ + Fill{Float32}(one(Float32),5,5) + + @test Fill{T}(F) ≡ Fill{T,2}(F) ≡ typeof(F)(F) ≡ F + end + + @testset "copy should return Fill" begin + x = Fill(1.0,10) + @test copy(x) ≡ x + end +end + +# @testset "indexing" begin +# A = Fill(3.0,5) +# @test A[1:3] ≡ Fill(3.0,3) +# @test A[1:3,1:1] ≡ Fill(3.0,3,1) +# @test_throws BoundsError A[1:3,2] +# @test_throws BoundsError A[1:26] +# @test A[[true, false, true, false, false]] ≡ Fill(3.0, 2) +# A = Fill(3.0, 2, 2) +# @test A[[true true; true false]] ≡ Fill(3.0, 3) +# @test_throws DimensionMismatch A[[true, false]] + +# @testset "colon" begin +# @test Fill(3.0,2)[:] ≡ Fill(3.0,2)[Base.Slice(Base.OneTo(2))] ≡ Fill(3.0,2) + +# @test Fill(3.0,2,2)[:,:] ≡ Fill(3.0,2,2)[Base.Slice(Base.OneTo(2)),Base.Slice(Base.OneTo(2))] ≡ Fill(3.0,2,2) +# end + +# @testset "mixed integer / vector /colon" begin +# a = Fill(2.0,5) +# z = Zeros(5) +# @test a[1:5] ≡ a[:] ≡ a +# @test z[1:5] ≡ z[:] ≡ z + +# A = Fill(2.0,5,6) +# Z = Zeros(5,6) +# @test A[:,1] ≡ A[1:5,1] ≡ Fill(2.0,5) +# @test A[1,:] ≡ A[1,1:6] ≡ Fill(2.0,6) +# @test A[:,:] ≡ A[1:5,1:6] ≡ A[1:5,:] ≡ A[:,1:6] ≡ A +# @test Z[:,1] ≡ Z[1:5,1] ≡ Zeros(5) +# @test Z[1,:] ≡ Z[1,1:6] ≡ Zeros(6) +# @test Z[:,:] ≡ Z[1:5,1:6] ≡ Z[1:5,:] ≡ Z[:,1:6] ≡ Z +# A = Fill(2.0,5,6,7) +# Z = Zeros(5,6,7) +# @test A[:,1,1] ≡ A[1:5,1,1] ≡ Fill(2.0,5) +# @test A[1,:,1] ≡ A[1,1:6,1] ≡ Fill(2.0,6) +# @test A[:,:,:] ≡ A[1:5,1:6,1:7] ≡ A[1:5,:,1:7] ≡ A[:,1:6,1:7] ≡ A +# end +# end + + +@testset "Broadcast" begin + x = Fill(5,5) + @test (.+)(x) ≡ x + @test (.-)(x) ≡ -x + @test exp.(x) ≡ Fill(exp(5),5) + @test x .+ 1 ≡ Fill(6,5) + @test 1 .+ x ≡ Fill(6,5) + @test x .+ x ≡ Fill(10,5) + f = (x,y) -> cos(x*y) + + @testset "support Ref" begin + @test Fill(1,10) .- 1 ≡ Fill(1,10) .- Ref(1) ≡ Fill(1,10) .- Ref(1I) + @test Fill([1 2; 3 4],10) .- Ref(1I) == Fill([0 2; 3 3],10) + @test Ref(1I) .+ Fill([1 2; 3 4],10) == Fill([2 2; 3 5],10) + end +end + +@testset "map" begin + x = Fill(2,5,3) + @test map(exp,x) === Fill(exp(2),5,3) +end + +@testset "0-dimensional" begin + A = Fill{Int,0,Tuple{}}(3, ()) + + @test A[] ≡ A[1] ≡ 3 + @test A ≡ Fill{Int,0}(3, ()) ≡ Fill(3, ()) ≡ Fill(3) + @test size(A) == () + @test axes(A) == () +end + +@testset "setindex!/fill!" begin + F = Fill(1,10) + @test (F[1] = 1) == 1 + @test_throws BoundsError (F[11] = 1) + @test_throws ArgumentError (F[10] = 2) + + F = Fill(1,10,5) + @test (F[1] = 1) == 1 + @test (F[3,3] = 1) == 1 + @test_throws BoundsError (F[51] = 1) + @test_throws BoundsError (F[1,6] = 1) + @test_throws ArgumentError (F[10] = 2) + @test_throws ArgumentError (F[10,1] = 2) + + @test (F[:,1] .= 1) == fill(1,10) + @test_throws ArgumentError (F[:,1] .= 2) + + @test fill!(F,1) == F + @test_throws ArgumentError fill!(F,2) +end