Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Julia, how to do method dispatch properly on arguments of (super-)types that are also supplied by the caller?

Tags:

julia

I would like to define a function f(x, t::Type) that executes different behavior depending on whether isa(x, t). Let's say that I want to call b1(x) if it is, and b2(x) otherwise.

I know that I can do a dynamic check at runtime like this:

function f(x, t::Type)
  if isa(x, t)
    b1(x)
  else
    b2(x)
  end
end

However, is there a way to do this purely with parametric method dispatch? For example, if I define

f{T}(x::T, t::Type{T}) = b1(x)
f(x, t::Type) = b2(x)

for f(1, Int) and f(1.0, Int) the correct behavior is called. But I want this to also work for all subtypes of t:

f(1, Number)

This actually calls b2 because the first signature of f does not match. Interestingly, though, f(x::Number, t::Type{Number}) = b1(x) would match in this case.

Am I missing something obvious here?

This was appearently a bug and is fixed in 0.4.


Questions:

  1. Why does f{T}(x::T, t::Type{T}) not match for f(1, Number), even though there is a type substitution for T (Number) that would match?

  2. Using f{T2, T1 <: T2}(x::T1, t::Type{T2}) or something similar does not work because appearently all static parameters come in scope only after the complete static parameter list is closed. Why?

  3. Are there any performance penalties for using the dynamic approach instead?

  4. What about defining the methods as an inner function, so I can bind t to a local variable, like this: function f(x, t::Type); g(x::t) = b1(x); g(x) = b2(x); g(x) end

    That works, but what are the performance costs?

  5. What's the idiomatic/preferred way to solve this?

(I had tried this on 0.3.2.)

like image 553
user4235730 Avatar asked Nov 10 '14 16:11

user4235730


People also ask

What does multiple dispatch mean in Julia?

Using all of a function's arguments to choose which method should be invoked, rather than just the first, is known as multiple dispatch.

Where is Julia keyword?

The where keyword creates a type that is an iterated union of other types, over all values of some variable. For example Vector{T} where T<:Real includes all Vector s where the element type is some kind of Real number.


1 Answers

To answer your questions:

  1. AFAICT it should. As user3580870 commented, this appears to work in Julia 0.4.
  2. This is "triangular dispatch" and is not (yet) implemented. See #3766.
  3. Depends. The compiler can evaluate isa(x, t) at compile time and eliminate the branch. However, there are still a few possible ways using isa could be suboptimal:
    • If the two branches return different types, type inference will not be able to infer the return type properly because it happens before isa(x, t) is made into a constant. (This would likely be costly; the other possible deoptimizations below are probably not a big deal.)
    • If one branch performs heap allocation but the other does not, an unnecessary GC frame may be emitted.
    • The function may not be inlined.
  4. An inner function will have the same performance problems as using isa and more. As of Julia 0.5, an inner function should be as efficient as a top-level function in this case.
  5. File a bug ;-). I think this is supposed to work. It's possible whatever fixed this in 0.4 can be backported to 0.3. But failing that, isa is not a bad approach provided that it doesn't cause a type inference issue.
like image 162
simonster Avatar answered Sep 20 '22 01:09

simonster