我们可以传递一个函数作为julia中另一个函数的参数吗?它是如何工作的?这是否意味着输入函数在调用函数之前运行,或者只有在调用函数专门调用它时才调用输入函数?
答案 0 :(得分: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
这是否意味着输入函数在调用函数之前运行?
在这种特殊情况下,很难回答,编译时编译器优化了调用函数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
此示例显示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))
如果调用函数专门调用它,是否只调用输入函数?
所以,上面的例子显示,如果调用函数f
未调用foo
,则会直接优化它。
与其他语言不同,Julia不仅仅是一个对用户不透明的魔术黑盒子,有时打开盒子并自学成才是有效率的。顺便说一句,在Julia中还有两个阶段(@code_llvm,@ code_native),您可能需要转储这些低级代码进行一些高级调查,请参阅Stefan在这篇文章中给出的最佳答案:What is the difference between @code_native, @code_typed and @code_llvm in Julia?有关详细信息。
这些函数之间有什么不同: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))
降低的代码告诉g1()
始终返回Main.f()
,此处Main.f
是f
模块中的Main
,但是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))
g2
是"传递函数作为另一个函数的参数",g1
可以被视为Main.f
的别名。你能理解这个吗? @ReD
答案 1 :(得分:3)
Julia始终支持first-class以及higher-order功能。这意味着是的,函数可以是其他函数的参数。此外,该语言也始终支持anonymous functions和closures。
最初,调用传递给其他函数的函数会带来性能损失。但是,从v0.5开始,这不再是一个问题,并且作为参数传递给其他函数的函数将像base julia中的函数一样快地运行。有关here的更多阅读。
传递函数的一个简单示例如下:
julia> f() = println("hello world") #Define a simple function f
f (generic function with 1 method)
julia> g(any_func::Function) = any_func() #Define a function g that accepts any function as input and then calls that function
g (generic function with 1 method)
julia> g(f) #Call g with f as the input
hello world
这里发生的是我在第一行中构建了一个函数f
,当通过f()
调用时,它将打印" hello world"。在第二行中,我构建了一个函数g
,它接受任何函数作为输入,然后调用该函数。在第三行中,我使用函数g
作为输入调用我的函数f
,然后g
运行函数f
,在这种情况下,将打印& #34;你好世界"。
上面的示例可能看起来很陈旧,但在更复杂的场景中,能够像这样传递函数是令人难以置信的有用。
正如另一位回答者指出的那样,基础julia利用许多本机函数来利用此功能,这些函数接受其他函数(包括匿名函数)作为输入,例如map
,filter
等。
大多数读者都可以到此为止。以下内容仅与使用eval
很多的人相关...
上面有一个例外,普通用户不太可能遇到过:如果通过在运行时解析和评估字符串来构建函数,并且在同一运行时调用此函数,则调用将失败有一个错误信息,可以说一些关于"世界时代"。这里实际发生的是因为函数是在运行时创建的,所以编译器没有机会推断函数的行为,例如输入和输出类型,因此它更喜欢"不要叫它。尽管如此,如果你真的想通过Base.invokelatest
方法调用它可以解决这个问题,但这意味着函数调用将不是类型稳定的。基本上,除非你真的知道你在做什么,否则你应该避免搞乱这个。
答案 2 :(得分:2)
map是将函数用作参数
的一个示例# you could define function
julia> f = x->x+1
# and use it (f mapped to array elements) ->
julia> map(f, [1, 2, 3])
3-element Array{Int64,1}:
2
3
4
在地图中使用之前未调用f!
编辑(对评论的反应):
到符号f是映射的函数对象(它是如何处理参数的方法),这个符号可以很有趣地调用。
# fun(ny) example
julia> function fun(f::Function, a)
return f(a)
end
fun (generic function with 1 method)
# using f defined above
julia> fun(f, 10)
11
# or anonymous function defined in place:
julia> fun(x->x^2, 10)
100
也许更复杂的例子也是可以理解的。 f在乐趣执行前调用一次(计算参数a),在乐趣函数体中调用一次:
julia> fun(f, f(9))
11