A pattern I encounter every once in a while is that I want a user to be able to pass a function to one of my package’s functions. Then I’d like to do different things with this function depending on what arguments it can take. However, I’m not sure what the best way to do this is. I do think that given Julia’s multi-method functions, there should be a clean way of achieving this.
Here is an example where I use hasmethod, however that doesn’t really feel clean to me, as I assume hasmethod is a runtime check, and I would like something that feels more like dispatch.
function mapper(f, vec)
if hasmethod(f, Tuple{eltype(vec), Int})
map(enumerate(vec)) do (idx, v)
f(idx, v)
end
else
map(f, vec)
end
end
userfunc1(x) = -x
userfunc2(i, x) = iseven(i) ? -x : zero(x)
The idea is that the user can opt in to more complex behavior (in this case modifying the function application depending on the index of a value in the array) by passing in a Function that supports this.
Of course a function could have multiple methods so that it’s unclear which variant to choose, but in most cases that I encounter, these things would be anonymous functions where only one version is defined.
Behavior such as this could be achieved by passing boolean flags along with the function, however I would like it the most if the function itself could be what decides the behavior.
A pattern I encounter every once in a while is that I want a user to be able to pass a function to one of my package’s functions. Then I’d like to do different things with this function depending on what arguments it can take.
While I wish for something like this myself from time to time, I think it is not a strength of julia and I would avoid it. Personally I would force the user to be explicit about the signature of the passed function. Either by having variants like mapper_elements, mapper_pairs etc. Or by wrapping the function argument OnPairs(f), OnElements(f).
It is less elegant and less generic, but more robust and easier to debug.
Probably more explicit with wrapper types, yes. But I still find it annoying that how a function behaves is information that is not really supposed to be accessed before applying it. I mean you can, with Base.return_types and applicable etc, but I rarely see that used in real code and it seems heavily discouraged.
I think the way Julia handles this is to let the user to choose the function. Thus, in this specific case you wouldn’t actually need the mapper function at all, if the user does:
I agree with Leandro on this one. This should not be something to the library writer to worry or consider. Either have a function that always expects a function that has a specific set of arguments, and it is the user responsibility to use anonymous functions to adequate what they have to pass to your function; or have multiple differently named functions, for the different kinds of functions you want the user to be able to pass (depending, adding a parameter to select between them is also a possibility).
This example is of course again just a bad illustration of what I really want to do (which is too niche a problem to be worth explaining here) but I guess the response was mostly expected. It’s probably just not a good idea to design an api around this kind of behavior in Julia.
Well, the specific problem matters for the opinion on the specific solution. Maybe it makes sense for some niche case, but if you do not give us more information than that, well, then my previous opinion stands.