Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dispatch on a type being anywhere in a signature #39878

Open
MasonProtter opened this issue Mar 1, 2021 · 14 comments
Open

Dispatch on a type being anywhere in a signature #39878

MasonProtter opened this issue Mar 1, 2021 · 14 comments
Labels
domain:types and dispatch Types, subtyping and method dispatch kind:feature Indicates new feature / enhancement requests kind:speculative Whether the change will be implemented is speculative

Comments

@MasonProtter
Copy link
Contributor

MasonProtter commented Mar 1, 2021

Okay, so this is a bit of a weird feature request, but there are some circumstances, for example when writing a CAS, where you want to dispatch on whether some type T occurs anywhere in an args list. Currently, the only real way to do this is with a trait like mechanism, e.g.

struct T end
struct HasT end
struct DoesntHaveT end

has_T(::Tup) where {Tup <: Tuple} = T  Tup.parameters ? HasT() : DoesntHaveT();

f(args...) = f(has_T(args), args...)
f(::HasT, args...) = 1;
f(::DoesntHaveT, args...) = 2;
julia> f(1, 2, 3, T(), 4)
1

julia> f(1, 2, 3, 4)
2

I think it would be quite useful if there was a built-in dispatch mechanism for this. Perhaps a syntax like

f(args::Tup) where {Tup <: Tuple, T  Tup} = ...

or even

f(args_left..., ::T, args_right...) = ...

where args_right is allowed to have instances of T in it?

I get that this is probably too complicated and niche a thing to put in the dispatch system, but I figured I should bring it up in case anyone else and thoughts on this and I didn't see an issue for it.

@JeffBezanson
Copy link
Sponsor Member

Very fancy, but definitely not weird at all! This has certainly come up before, and would be really cool. I think it does make it even easier to write lots of ambiguities, but that's never really stopped us before.

@JeffBezanson JeffBezanson added the domain:types and dispatch Types, subtyping and method dispatch label Mar 1, 2021
@JeffBezanson
Copy link
Sponsor Member

I think the first interesting thing that comes up is that we don't get a way to represent the intersection of these "for free". I.e. let All{A} == Tuple{Vararg{A}}, and Any{A} be the new thing, then

All{A} ∪ All{B} = Union{All{A}, All{B}}  # since we have union types anyway
All{A} ∩ All{B} = All{A ∩ B}  # can compute it recursively
Any{A} ∪ Any{B} = Any{A ∪ B}
Any{A} ∩ Any{B} = uhoh!

So we might have to add explicit intersection types at that point too...

@oxinabox
Copy link
Contributor

oxinabox commented Mar 2, 2021

TensorFlow.jl wanted this for example.
(If any argument is a TensorFlow.Tensor then all other arguments need to be a converted to TensorFlow.constants).
As a work around we just unrolled 3-5 arguments and handled each.
Could probably make a macro for it

@MasonProtter
Copy link
Contributor Author

Are intersection types something that we'd want in the type system? I certainly think I'd want it, but I can imagine that it'd be unwanted because it's yet another avenue for writing non-terminating type-level manipulations.

@alok
Copy link

alok commented Jun 7, 2021

@MasonProtter do the non terminations matter in practice though, GHC triggers plenty of opportunities for them and seems to do fine with a timeout.

@MasonProtter
Copy link
Contributor Author

As I said, I think I want it. I was more wondering if the language devs could explain if there's some terrible downside to this.

@vtjnash
Copy link
Sponsor Member

vtjnash commented Jun 7, 2021

I recall GHC uses only the much more limited HM-type-inference. Non-terminations do occur relatively frequently in Julia already at the inference level, but don't happen in the type system. It seems unlikely that explicit intersection type representations would make this non-terminating, since it is already very similar to existing expressions. (Specifically, I think it is already quite close to being expressed by the constraints implied by repeated TypeVars such as typeintersect(Tuple{T, T} where T, Tuple{A, B} where {A, B}))

@stevengj
Copy link
Member

stevengj commented Oct 1, 2021

Currently, the only real way to do this is with a trait like mechanism, e.g.

As discussed in #42455, you can't in general accomplish this in Julia right now if you are extending someone else's method, without resorting to type piracy.

#42455 suggests a simpler syntax for this analogous to the Vararg type, which doesn't require parser changes. Your example above would become:

f(args::VarargOne{T,Any,N}) where {N} = ...

(The VarargOne name can be bikeshedded, of course. We could even just call it Vararg{Any,N,T}, for that matter, where Vararg{S,N,T} would denote a tuple of N elements of type S, at least one of which must be of type T, but I'm not sure if adding an optional parameter to Vararg would be considered a breaking change or too punny.)

@stevengj stevengj added the kind:speculative Whether the change will be implemented is speculative label Oct 1, 2021
@stevengj
Copy link
Member

stevengj commented Oct 4, 2021

Regarding intersections, you could have

VarargSome{S,N,(T1,)} ∩ VarargSome{S,N,(T2,)} = VarargSome{S,N,(T1,T2)}

if you allowed VarargSome{S,N,(T...)} to be parameterized by a tuple of types T, each of which must appear in the tuple at least once.

@MasonProtter
Copy link
Contributor Author

MasonProtter commented Oct 4, 2021

Yes, this seems like a great way to get the thing that I wanted (it seems we also have very similar use cases).

The name can be bikeshed, but I do like that this doesn’t require any funny parser changes or anything like that.

@ericphanson
Copy link
Contributor

Just to say this would be useful for Convex.jl, which currently pirates hcat, vcat, and hvcat to support when at least one argument is a Convex AbstractExpr. It is a much worse api to require users use a function like e.g. Convex.hcat because that doesn't hook into the special [A B] hcat syntax (and similarly for vcat and hvcat). (But it seems we can downgrade our piracy to the point the package will at least precompile on 1.10, so maybe there isn't urgency).

@vtjnash
Copy link
Sponsor Member

vtjnash commented Oct 24, 2023

That PR seems right for the current state of affairs for vcat (waiting on #2326). You are currently permitted to add your own types to hcat, as long as they do not subtype Number or AbstractArray; that is not piracy. For example, LinearAlgebra demonstrates how to add AbstractQ and UniformScaling to the set of joinable objects.

@ericphanson
Copy link
Contributor

ericphanson commented Oct 24, 2023

You are currently permitted to add your own types to hcat, as long as they do not subtype Number or AbstractArray; that is not piracy

We have aliases

const Value = Union{Number,AbstractArray}
const AbstractExprOrValue = Union{AbstractExpr,Value}

so I think Base.hcat(args::AbstractExprOrValue...) violates that rule, right?

LinearAlgebra demonstrates how to add AbstractQ and UniformScaling to the set of joinable objects

Are you saying using these methods?

# used in concatenations: Base.__cat_offset1!
Base._copy_or_fill!(A, inds, Q::AbstractQ) = (A[inds...] = collect(Q))
# overloads of helper functions
Base.cat_size(A::AbstractQ) = size(A)
Base.cat_size(A::AbstractQ, d) = size(A, d)
Base.cat_length(a::AbstractQ) = prod(size(a))
Base.cat_ndims(a::AbstractQ) = ndims(a)
Base.cat_indices(A::AbstractQ, d) = axes(A, d)
Base.cat_similar(A::AbstractQ, T::Type, shape::Tuple) = Array{T}(undef, shape)
Base.cat_similar(A::AbstractQ, T::Type, shape::Vector) = Array{T}(undef, shape...)

Maybe that can work. If there is any AbstractExpr in the arguments, we have to promote the whole thing to an AbstractExpr (which is a symbolic object, not really an actual array), so we need some way to ensure Convex's methods win out.

edit: it looks like currently only the first object matters for cat_similar, so this wouldn't work as-is. If Base had some promotion mechanism to promote among the cat_similar types for each argument, maybe that could work.

@vtjnash
Copy link
Sponsor Member

vtjnash commented Oct 24, 2023

Not those, it defines new vcat methods very similar to that PR, which is a super type of AbstractArray

@nsajko nsajko added the kind:feature Indicates new feature / enhancement requests label Jun 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
domain:types and dispatch Types, subtyping and method dispatch kind:feature Indicates new feature / enhancement requests kind:speculative Whether the change will be implemented is speculative
Projects
None yet
Development

No branches or pull requests

8 participants