Expressing "at least one of this type" on Julia master

question

#1

https://github.com/JuliaLang/julia/pull/23117 removed a hack that was used in a number of places to express the concept of a set of arguments, at least one of which is of a particular type. For example, if I have a custom array type and I wanted to overload Base.foreach when at least one argument is of my custom type, I could do:

struct VectorLike{V <: AbstractVector}
	x::V
end

_getvector(x::VectorLike) = x.x
_getvector(x::AbstractVector) = x

function Base.foreach(f, vs::Union{AbstractVector, VectorLike{V}}...) where {V}
	println("custom foreach with inner type $V")
	foreach(f, _getvector.(vs)...)
end

And I would get the following on Julia v0.6:

julia> foreach((x, y) -> println(x, y), [1,2,3], [4,5,6])
14
25
36

julia> foreach((x, y) -> println(x, y), VectorLike([1,2,3]), [4,5,6])
custom foreach with inner type Array{Int64,1}
14
25
36

However, on Julia master, the Base.foreach signature above now matches any number of AbstractVectors, regardless of whether any of them is of my VectorLike type:

julia> foreach((x, y) -> println(x, y), [1,2,3], [4,5,6])
ERROR: UndefVarError: V not defined
Stacktrace:
 [1] foreach(::Function, ::Array{Int64,1}, ::Array{Int64,1}, ::Vararg{Array{Int64,1},N} where N) at /home/rdeits/Projects/jump-containers/types.jl:9
 [2] top-level scope

I realize that this was exactly the intent of https://github.com/JuliaLang/julia/pull/23117 , but I’m wondering how to express this desired behavior at all on Julia v0.7+. Any suggestions?

One idea I had would be to define an @generated Base.foreach that accepts any number of AbstractVectors and then checks in the generator if any of them is a VectorLike. But this would make all foreach calls pass through my new @generated function, which is clearly suboptimal (and seems like it would conflict with other packages trying to do the same thing).

Another option is to generate all of the matching signatures up to some number of arguments, but that requires generating O(N^2) different methods (and choosing some max number of arguments N) which also seems suboptimal.


#2

See also https://github.com/JuliaLang/julia/issues/21026#issuecomment-322878914.

Broadcast is able to handle this kind of thing using BroadcastStyle and binary broadcasting rules, https://docs.julialang.org/en/latest/manual/interfaces/#writing-binary-broadcasting-rules-1. The machinery would need to be set up in Base to dispatch on the equivalent of BroadcastStyle for foreach. You could just overwrite the Base methods to do so.