На самом деле это отличный пример того, как вы не должны расширять трансляцию.
julia> struct SVMatrix{T} <: Base.AbstractMatrix{T}
value::T
index::Tuple{Int,Int}
size::Tuple{Int,Int}
end
julia> @inline function Base.getindex(A::SVMatrix{T}, i::Vararg{Int,2}) where {T}
@boundscheck checkbounds(A, i...)
if i == A.index
return A.value
else
return zero(T)
end
end
julia> Base.size(A::SVMatrix) = A.size
julia> SVMatrix(1.0, (1,1), (2, 2)) .+ ones(2, 2)
2×2 Array{Float64,2}:
2.0 1.0
1.0 1.0
Результат .+
должен , а не быть [2 0; 0 0]
! Если бы мы использовали вашу реализацию широковещания (исправлено для отправки на ::typeof(+)
, как DNF отметил ), ваш массив был бы удивительно сломан, когда другие использовали его и ожидали, что он будет вести себя как все другие AbstractArray
s.
Теперь операция, в которой вы могли бы возвратить умно пересчитанные значения SVMatrix
, равна .*
:
julia> SVMatrix(2.5, (1,1), (2, 2)) .* ones(2, 2)
2×2 Array{Float64,2}:
2.5 0.0
0.0 0.0
Мы можем выполнить эту операцию в O (1) пространство и время, но реализация по умолчанию зацикливается на всех значениях и возвращает плотный Array
. Вот где сияет многократная диспетчеризация Джулии:
julia> Base.broadcasted(::typeof(*), A::SVMatrix, B::AbstractArray) = SVMatrix(A.value*B[A.index...], A.index, A.size)
julia> SVMatrix(2.5, (1,1), (2, 2)) .* ones(2, 2)
2×2 SVMatrix{Float64}:
2.5 0.0
0.0 0.0
Поскольку это операция O (1) и является огромным выигрышем , мы можем отказаться от широковещательного слияния и немедленнопересчитать новый SVMatrix
- даже внутри "слитого" выражения. Здесь вы еще не закончили!
- Необходимо реализовать проверку ошибок для совместимых фигур.
- Необходимо разрешить трансляцию таких вещей, как
SVMatrix(2.5, (1,1), (2, 2)) .* rand(2)
. - В идеале вы должны реализовать
BroadcastStyle
, чтобы разрешить отправку для "хотя бы одного SVMatrix
в списке аргументов". Затем вы реализуете Base.broadcasted(::ArrayStyle{SVMatrix}, ::typeof(*), args...)
, что позволит SVMatrix
появляться по обе стороны от .*
, но это сложная тема.