Is it safe to dispatch on function types?

Let’s say I have a generic function,

test(f, a) = f(a)

where f is a function and a some number.

I want to dispatch on the function argument f. Specifically, do something like

test(f::typeof(cos), a) = "haha!"

I am using Julia 1.0 and this works.

julia> test(sin, 1)
0.8414709848078965

julia> test(cos, 1)
"haha!"

But I am worried whether it is (a) safe and (b) has some gotchas.

I couldn’t find something about it at the docs, if I missed it please let me know.

1 Like

Yup, perfectly safe. It is not uncommon for me to do this (I go pretty crazy with multiple dispatch).

It’s funny, Julia isn’t object oriented, but another way of looking at it is that everything is an object. You can add methods to functions, so in a sense every function in Julia is a “functor” (this is a common but clearly incorrect use of the word, the true definition is here).

Almost forgot, the one caveat I’d give is that you should only do this on named functions that appear in the global scope of your package. I’m not sure whether it’s possible to do this with functions for which this is not true, but if it is, I’d imagine there are probably lots of “gotchas”.

2 Likes

It’s actually also used in Base for optimizations, when for a specific function a general operation can be optimized. For example this was done here for reduce(hcat, v) where v is a vector of vectors. Naively this would keep reallocating the result of each hcat, but now Julia overloads this signature to make it efficient:

julia> v = [rand(1_000) for _ in 1:1000];

julia> function f1(v)
           h = reshape(v[1], :, 1)
           for i in 2:length(v)
               h = hcat(h, v[i])
           end
           h
       end
f1 (generic function with 1 method)

julia> f2(v) = reduce(hcat, v)
f2 (generic function with 1 method)

julia> @benchmark f1($v)
BenchmarkTools.Trial: 
  memory estimate:  3.73 GiB
  allocs estimate:  2998
  --------------
  minimum time:     318.887 ms (9.31% GC)
  median time:      337.090 ms (9.61% GC)
  mean time:        338.537 ms (11.05% GC)
  maximum time:     419.100 ms (27.04% GC)
  --------------
  samples:          15
  evals/sample:     1


julia> @benchmark f2($v)
BenchmarkTools.Trial: 
  memory estimate:  7.63 MiB
  allocs estimate:  2
  --------------
  minimum time:     700.406 μs (0.00% GC)
  median time:      756.052 μs (0.00% GC)
  mean time:        839.459 μs (10.05% GC)
  maximum time:     40.769 ms (93.56% GC)
  --------------
  samples:          5912
  evals/sample:     1
5 Likes

The above would work even if cos was an anonymous function.

2 Likes

So if I see that correctly, Julia does dispatch on function types but not on the individual methods that belong to that function?

And Julia usually compiles a different method for each combinations of argument types that are passed to a function. Does it also do that, when you pass a function to a function?

yes

Be careful about that. Sometimes julia saves compile time by not specializing on function arguments. This is a good heuristic but can come to bite you and is hard to debug. Last paragraph:

4 Likes