如何在julia中将一个函数作为另一个函数的参数传递?

ReD*_*ReD 4 julia

我们可以在julia中将一个函数作为另一个函数的参数传递吗?它是如何工作的?这是否意味着输入函数先于调用函数运行,还是仅在调用函数专门调用输入函数时才调用输入函数?

Gni*_*muc 5

朱莉娅最大的特点之一是,您可以自己找出这些问题的答案。与其进行实验然后观察表面上的行为,不如直接询问朱莉娅在幕后做过的事情,是获得答案的更方便,更简洁的方法。让我们从其他两个答案中借用这些示例,并通过@code_lowered以下方法询问Julia发生了什么:

julia> f() = println("hello world")
julia> g(any_func::Function) = any_func()

#Me: hi, Julia, what happened here?
julia> @code_lowered g(f)

#Julia: hi there, this is the lowered code, anything else you want to know?
CodeInfo(:(begin 
        nothing
        return (any_func)() 
    end))
#Me: it looks like function `g` just returned `(any_func)()` and did nothing else, 
#    is it equvienlent to write `f()`?
julia> @code_typed g(f)

#Julia: yes, the codes looks the same after type inference stage: 
CodeInfo(:(begin 
        $(Expr(:inbounds, false))
        # meta: location REPL[1] f 1
        # meta: location coreio.jl println 5
        SSAValue(0) = (Core.typeassert)(Base.STDOUT, Base.IO)::IO
        # meta: pop location
        # meta: pop location
        $(Expr(:inbounds, :pop))
        return (Base.print)(SSAValue(0), "hello world", $(QuoteNode('\n')))::Void
    end))=>Void

julia> @code_typed f()
CodeInfo(:(begin 
        $(Expr(:inbounds, false))
        # meta: location coreio.jl println 5
        SSAValue(0) = (Core.typeassert)(Base.STDOUT, Base.IO)::IO
        # meta: pop location
        $(Expr(:inbounds, :pop))
        return (Base.print)(SSAValue(0), "hello world", $(QuoteNode('\n')))::Void
    end))=>Void
Run Code Online (Sandbox Code Playgroud)

这是否意味着输入功能先于调用功能运行?

在这种特殊情况下,很难回答,g编译器在编译时对调用函数进行了优化,因此g在运行时没有。:P让我们添加一些额外的内容到g

julia> g(any_func::Function) = (println("I don't wanna be optimized out!"); any_func())
g (generic function with 1 method)
julia> @code_lowered g(f)
CodeInfo(:(begin 
        nothing
        (Main.println)("I don't wanna be optimized out!")
        return (any_func)()
    end))
julia> @code_typed g(f)
CodeInfo(:(begin 
        $(Expr(:inbounds, false))
        # meta: location coreio.jl println 5
        SSAValue(0) = (Core.typeassert)(Base.STDOUT, Base.IO)::IO
        # meta: pop location
        $(Expr(:inbounds, :pop))
        (Base.print)(SSAValue(0), "I don't wanna be optimized out!", $(QuoteNode('\n')))::Void
        $(Expr(:inbounds, false))
        # meta: location REPL[2] f 1
        # meta: location coreio.jl println 5
        SSAValue(1) = (Core.typeassert)(Base.STDOUT, Base.IO)::IO
        # meta: pop location
        # meta: pop location
        $(Expr(:inbounds, :pop))
        return (Base.print)(SSAValue(1), "hello world", $(QuoteNode('\n')))::Void
    end))=>Void
Run Code Online (Sandbox Code Playgroud)

此示例显示的内容g恰好在之前运行f,因此答案为是。遵循相同的模式,很容易查看Liso的示例:

julia> f = x->x+1
julia> fun(f::Function, a) = f(a)

# nothing new here, `f(9)` was called before `fun` 
# and the return value was passed to `fun` with a binding name `a`.
julia> @code_lowered fun(f, f(9))  
CodeInfo(:(begin 
        nothing
        return (f)(a)
    end))

# to verify your second question:
julia> foo(f, x) = x
foo (generic function with 1 method)

julia> @code_lowered foo(f, 1)
CodeInfo(:(begin 
        nothing
        return x
    end))
Run Code Online (Sandbox Code Playgroud)

输入函数仅在调用函数专门调用它时才被调用吗?

因此,是的,上面的示例表明,如果f调用函数未调用foo,则将直接对其进行优化。

与其他语言不同,Julia不仅是一个对用户不透明的神奇黑匣子,有时打开箱子并自学成才是有效的。顺便说一句,Julia中还有两个阶段(@ code_llvm,@ code_native),您可能需要转储这些低级代码以进行一些高级调查,请参阅这篇文章中Stefan的出色答案:@ code_native,@和有什么区别? Julia中的code_typed和@code_llvm?有关更多详细信息。

更新:

这些函数之间有什么不同:g1()= f(),g2(f :: Function)= f()。两者的结果都相同,所以它们之间有什么不同?

julia> @code_lowered g1()
CodeInfo(:(begin 
        nothing
        return (Main.f)()
    end))

julia> @code_lowered g2(f)
CodeInfo(:(begin 
        nothing
        return (f)()
    end))
Run Code Online (Sandbox Code Playgroud)

降低的代码告诉g1()总是返回Main.f(),这里Main.ffMain的模块,但g2返回f()这里f是你传递给它的功能。为了清楚起见,我们可以定义g2为:

julia> g2(argumentf::Function) = argumentf()
g2 (generic function with 1 method)

julia> @code_lowered g2(f)
CodeInfo(:(begin 
        nothing
        return (argumentf)()
    end))
Run Code Online (Sandbox Code Playgroud)

g2是“将一个函数作为另一个函数的参数传递”,g1可以视为的别名Main.f。你能理解这个吗?@红色