Multiple Dispatch based on the number of function's arguments that is by itself an argument

Hello, I’m currently doing a numerical solver for a certain type of PDEs (Neural Field Equations) and these equations accepts functions as inputs (and parameters as well). The equation in study is given by:


where I, K and S are functions provided by the user.

The function I in a general way depends on time but sometimes it doesn’t and the algorithm can be simplified in these cases, which leads to faster run times.
So, I was thinking about taking advantage of the multiple dispatch here. Let me explain:

I_t(x,t)=x*t
I(x)=x
sol = solve(I_t,K,S,param) # this algorithm would contemplate a time dependent I
sol = solve(I,K,S,param)   # this algorithm would contemplate a time independent I

Basically, I was thinking to know, somehow, the number of arguments of a function and based on that use multiple dispatach. But as far as I know there is no simple way to know the number of arguments of a function, or it is? I searched and didn’t find nothing straight forward.

I wrote more about the solver that I’m trying to build in this post, here in discourse.

I don’t see another way to this but probably, there is a better way to approach this. I’m open to suggestions and tips!

Thanks,
Tiago

Probably the question is not quite propertly formulated. Multiple dispatch is dependent on all the arguments, so it is dependent on the number their number.

Your example is one in which you want to dispatch differently depending on the number of arguments of a function that is by itself an argument. That is different.

You can of course define two solver functions with different types of arguments. For instance, are the K, S and param identical for both cases? I can imagine that param has extra parameters for the time-dependent case (initial time, final time, for example). In that case, you can dispatch on different types of params:

julia> struct TimeDependentParams
          x
          tini
       end

julia> struct NotTimeDependentParams
         x
       end

julia> p1 = TimeDependentParams(1,0)
TimeDependentParams(1, 0)

julia> p2 = NotTimeDependentParams(1)
NotTimeDependentParams(1)

julia> f(x,t) = x*t
f (generic function with 1 method)

julia> f(x) = x
f (generic function with 2 methods)

julia> solver(f,params::TimeDependentParams) = f(params.x,params.tini)
solver (generic function with 1 method)

julia> solver(f,params::NotTimeDependentParams) = f(params.x) 
solver (generic function with 2 methods)

julia> solver(f,p1)
0

julia> solver(f,p2)
1

julia>
1 Like

You’re right, the question was not well formulated. I edited now to be better understood.

Unfortunately that doesn’t happen :confused:, K, S and param are the identical, only I changes. I could put a boolean to act like a “flag” in solver and then use multiple dispatch

function solver(I,K,S,param,timedepend_I) # solver contemplate I(x,t)
    # stuff
end

function solver(I,K,S,param) # solver contemplate I(x)
    # easier stuff
end

But probably this solution is a bit ugly :confused:

There an option to define functors, which are callalable objects. But I would not go that line, I think that is too obscure for a user interface:

julia> struct TimeDependentFunction
       end

julia> struct NotTimeDependentFunction
       end

julia> function (f::TimeDependentFunction)(x,t)
         x*t
       end

julia> function (f::NotTimeDependent)(x)
         x
       end

julia> f1 = TimeDependentFunction()
TimeDependentFunction()

julia> f2 = NotTimeDependentFunction()
NotTimeDependentFunction()

julia> solver(f::TimeDependentFunction) = "time dependent"
solver (generic function with 1 method)

julia> solver(f::NotTimeDependentFunction) = "not time dependent"
solver (generic function with 2 methods)

julia> solver(f1)
"time dependent"

julia> solver(f2)
"not time dependent"



Leandro is right in that it is not really multiple dispatch, because a function doesn’t carry around it’s signatures in it’s type. That sait, there is a way to check how a given function may be called: hasmethod or methods or which

How you want to implement this depends on how the rest of these things is set up. A minimal example on how you find out if a supplied function takes one or two arguments:

julia> with_t = (x,t)-> 2*x^2 + sin(2.1*x^t)
#9 (generic function with 1 method)

julia> without_t = x-> 2*x^2 + cos(3.2*x)
#11 (generic function with 1 method)

julia> hasmethod(with_t, (Any,))
false

julia> hasmethod(with_t, (Any,Any))
true

julia> hasmethod(without_t, (Any,))
true

julia> hasmethod(without_t, (Any, Any))
false
julia> function one_or_two(fun)
           if hasmethod(fun, (Any,)) println("$(fun) can take one argument!") end
           if hasmethod(fun, (Any,Any)) println("$(fun) can take two arguments!") end
       end
one_or_two (generic function with 1 method)

julia> one_or_two(with_t)
#9 can take two arguments!

julia> one_or_two(without_t)
#11 can take one argument!

julia> one_or_two(+)

julia> one_or_two(>)
> can take one argument!
> can take two arguments!

As you can see, this is not a really generic approach so I belive that there is a more elegant solution, and it does sound like a problem that has probably been solved already in some package, so maybe someone with more experience in julia may suggest a better approach, but it would be a start.

Edit: What you may try to preserve generality is the following: use hasmethod with the exact signature that will be used to call the function in the special case that it is independent of time and thus faster, and if that fails default to calling it in the generic way. Don’t try to dispatch every case by hand. This way if the user supplies a really awkward but applicable function, you don’t fail for no reason because you didn’t forsee this signature. Good luck!

2 Likes

That solution works fine, but as you said, it’s a bit obscure for an user interface, it’s not intuitive at all. I guess I have to think in another approach or find on the internet an use case similar that could be applied to mine.
But thanks, you already helped me a lot!

Yes, it’s not a very simple and elegant approach to what I’m trying to do, but gave me some lights, thanks!!
I’ll look for more about this on the internet or just figure out other way to do what I want to do!

Thanks, once again!

One pattern is to accept either f! which mutates x, or f which doesn’t, via fit(f!, x, y, z) vs fit(f, y, z). You give fit one extra argument, which is the one the function is going to mutate, to tell it that’s what the function you gave it wants to do.

I don’t know whether something like solve(I_t, times, K, S, param) would make sense here or not. Looking at methods seems a bit unreliable, as methods you weren’t intending to call will suddenly matter.

Is this fit function already implemented in Base? Or are you talking about some other function to be written by the user using, e.g., hasmethod?