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

时间:2017-10-20 04:45:26

标签: julia

我们可以传递一个函数作为julia中另一个函数的参数吗?它是如何工作的?这是否意味着输入函数在调用函数之前运行,或者只有在调用函数专门调用它时才调用输入函数?

3 个答案:

答案 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.ff模块中的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 functionsclosures

最初,调用传递给其他函数的函数会带来性能损失。但是,从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利用许多本机函数来利用此功能,这些函数接受其他函数(包括匿名函数)作为输入,例如mapfilter等。

大多数读者都可以到此为止。以下内容仅与使用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