Function as argument: name, symbol or string?

I have a function (a Machine Learning algorithm) where one of the parameters is a function (custom versions of gini, variance or entropy).

Do you advice using as parameter the name of the function, the symbol or a text string ? :

function myf(x)
    x + 1
end

# Solution 1: the function parameter is a symbol
function outerf(x,f)
    y = eval(Meta.parse("$a($x)"))
end
outerf(3,:myf)

# solution 2: the function parameter is the name/placeholder of the function
function outerf2(x,f)
    y = f(x)
end
outerf2(4,myf)

# solution 3: the function parameter is the string of the name/placeholder of the function
function outerf3(x,f)
    fsymbol = Symbol(a)
    y =  eval(Meta.parse("$fsymbol($x)"))
end
outerf3(5,"myf")

While using directly the function name seems the simplest solution, I am concerned that the inner function may be in scope for the outer function (so that it can indeed be called), but not in the place calling the outer function. However there is a huge performance gain in calling the function directly:

@btime outerf(3,:myf)   #  92.932 ÎĽs (44 allocations: 2.67 KiB)
@btime outerf2(4,myf)   #   0.029 ns (0 allocations: 0 bytes)
@btime outerf3(5,"myf") #  93.474 ÎĽs (44 allocations: 2.67 KiB)

ok, the way to go seems so use symbol, but using getfield() to actually call the function instead of eval. This is fast and flexible:

function outerf4(x,f)
    y = getfield(Main, Symbol(f))(x)
end
outerf4(6,:myf)
@btime outerf4(6,:myf) # 0.029 ns (0 allocations: 0 bytes)

See also this SO thread.

1 Like

You should definitely, 100%, use option 2 (unless I’m missing something of course).

It’s much clearer what’s going on, and that’s how the language is supposed to be used: If you want to pass a function as parameter, then pass the function :slight_smile:

As the manual says:

Functions in Julia are first-class objects: they can be assigned to variables, and called using the standard function call syntax from the variable they have been assigned to. They can be used as arguments, and they can be returned as values.

When you do outerf2(4,myf), Julia will pass the function as parameter to outer2. Then for outer2 it doesn’t matter in which module or scope the function was defined: it got the value so it can use it.

Example:

module A
myf(x) = 2x
outerf(f, x) = f(x)
end

module B
myf() = 3x
outerf(f, x) = f(x)
end

myf(x) = 4x

@show A.outerf(myf, 1)
@show B.outerf(myf, 1)
@show B.outerf(A.myf, 1)

gives

A.outerf(myf, 1) = 4
B.outerf(myf, 1) = 4
B.outerf(A.myf, 1) = 2
8 Likes

Yes, but myf must be defined in the place where outerf is called.

for example, given the content of functionTest.jl is :


module myFTest
export outerfName, outerfSymbol

function innerf(x)
    x + 1
end
function outerfName(x,f)
    y = f(x)
end
function outerfSymbol(x,f)
    y = getfield(Main.myFTest, Symbol(f))(x)
end

end

Then you get :

julia> include("functionTest.jl")
Main.myFTest

julia> using .myFTest

julia> outerfName(1,innerf)
ERROR: UndefVarError: innerf not defined
Stacktrace:
 [1] top-level scope
   @ REPL[3]:1

julia> outerfSymbol(1,:innerf)
2

In this case the reason is that innerf is not exported, but in my case I am concerned on making a wrapper for my outer function for an other general package that knows nothing about the inner function, and hence using symbols seems more flexible and safe against name collisions…

You would do outerfName(1, myFTest.innerf) to pass a non-exported function.

If the other package “knows nothing” about the inner function, why/how it passing that inner function as a parameter? This seems contradictory.

9 Likes