简体   繁体   English

如何在Julia中指定函数的类型/签名?

[英]How do I specify a function's type/signature in Julia?

I'm implementing Newton's method for finding roots in a precompiled library. 我正在实现牛顿方法来在预编译的库中查找根 The common case will be to work with functions from Float64 to Float64 , and I want an optimized compiled version of it to exist in the library. 常见的情况是使用从Float64Float64函数,我希望它的优化编译版本存在于库中。 I will, of course, implement a type generic version, too, but Julia will need some way of differentiating the methods by signature so it knows which one to call at runtime. 当然,我也将实现类型的泛型版本,但是Julia需要通过签名区分方法的某种方式,以便它知道在运行时调用哪个方法。 Presently, the implementation is: 当前,实现为:

#Float64 optimized version
function findrootNewton( func, funcder, guess::Float64,
                        rtol::Float64=1e-12, abstol::Float64=1e-12, maxiter::Int=100 )
    #make sure tolerances are positive
    if rtol <= 0.0
        error("findrootNewton: rtol must be a positive number")
    end

    if abstol <= 0.0
        error("findrootNewton: abstol must be a positive number")
    end

    if maxiter <= 0.0
        error("findrootNewton: maxiter must be a positive integer")
    end

    converged::Bool = false

    oldx::Float64 = guess
    newx::Float64 = oldx - ((func(oldx) / funcder(oldx))::Float64)
    absdiff = abs(oldx - newx)

    iter = 2
    while (absdiff < abstol || absdiff < rtol * abs(newx)) && iter <= maxiter
        oldx = newx
        newx = oldx - func(oldx) / funcder(oldx)
        absdiff = abs(oldx - newx)

        iter += 1
    end #while (absdiff < abstol || absdiff < rtol * abs(newx)) && newxiter <= maxiter

    if iter <= maxiter
        converged = true
    end

    return (newx, converged)
end #findzeroNewton

#Generic version
function findrootNewton( func, funcder, guess::Number,
                        rtol::Real=1e-12, abstol::Real=1e-12, maxiter::Int=100 )
    #make sure tolerances are positive
    if rtol <= 0
        error("findrootNewton: rtol must be a positive number")
    end

    if abstol <= 0
        error("findrootNewton: abstol must be a positive number")
    end

    if maxiter <= 0
        error("findrootNewton: maxiter must be a positive integer")
    end

    converged::Bool = false

    newx = oldx - func(oldx) / funcder(oldx)
    oldx = convert(typeof(newx), guess)
    absdiff = abs(oldx - newx)

    iter = 2
    while (absdiff < abstol || absdiff < rtol * abs(newx)) && iter <= maxiter
        oldx = newx
        newx = oldx - func(oldx) / funcder(oldx)
        absdiff = abs(oldx - newx)

        iter += 1
    end #while (absdiff < abstol || absdiff < rtol * abs(newx)) && newxiter <= maxiter

    if iter <= maxiter
        converged = true
    end

    return (newx, converged)
end #findzeroNewton

This has not been used/debugged, yet. 尚未使用/调试。 For example, I'm not checking for the derivative hitting zero because the particular case I'm coding this for does not need it. 例如,我不检查导数是否为零,因为我为此编写的特殊情况不需要它。

Notice that if guess , rtol , and abstol arguments are given as Float64 , but the functions don't return Float64 but BigFloat , then the code will fail at the point of the type assertion in the definition of newx , even though there is a method appropriate to generic functions available. 请注意,如果将guessrtolabstol参数指定为Float64 ,但函数不会返回Float64而是BigFloat ,则即使在newx的定义中,即使有方法,代码也会在类型断言时失败适用于可用的通用功能。 Is there a way to avoid this problem? 有办法避免这个问题吗?

Edit: I can specify the type of data a variable stores in Julia, for example: 编辑:我可以指定变量存储在Julia中的数据类型,例如:

x::Float64 = 2.5

Is it possible to similarly specify the signature of a variable that can store (pointers to) functions? 是否可以类似地指定可以存储(指向)函数的变量的签名?

For clarity I will write my comment as an answer: You don't need to specialize function signatures on types in Julia, unless it is to implement a specialized handling in the function body. 为了清楚起见,我将在评论中写我的评论:除非在函数体中实现专门的处理,否则无需在Julia中对类型的函数签名进行专门化。 Argument type assertion has no impact on code speed or compilability. 参数类型断言对代码速度或可编译性没有影响。 See http://docs.julialang.org/en/latest/manual/performance-tips/ 参见http://docs.julialang.org/en/latest/manual/performance-tips/

Type assertions in function arguments in julia are mainly used to control multiple dispatch, ie different function behaviour for different types of the input arguments. julia中函数参数中的类型断言主要用于控制多个分派,即对于不同类型的输入参数,不同的函数行为。 When not asserting a type, the compiler will automatically compile a type-specialized version for each combination of input arguments. 当不声明类型时,编译器将为输入参数的每种组合自动编译类型专用的版本。

If you need for another reason, eg to ensure type stability, to assert that the return type of a function is the same as the input, you can do 如果出于其他原因(例如,确保类型稳定性)需要断言函数的返回类型与输入相同,则可以执行

function foo(x::T)::T where T
...
end

This is not the way to write Julia code. 这不是编写Julia代码的方法。 You are writing Julia as if it was a statically typed language. 您正在编写Julia,就好像它是静态类型的语言一样。 It is an easy mistake to make because Julia "looks" a lot like a statically typed language. 这是一个容易犯的错误,因为Julia看起来很像静态类型的语言。 To get performance in Julia the important thing is not annotating with types, but achieving type stability. 为了在Julia中获得性能,重要的不是使用类型注释,而是实现类型稳定性。

That means writing the code so that upon executing a piece of code, the types of variables don't change. 这意味着编写代码,以便在执行一段代码时,变量的类型不会改变。 Julia gives you a number of functions and facilities to aid you in this, such as zero , iszero , similar etc. Julia提供了许多功能和设施来帮助您完成此任务,例如zeroiszerosimilar等等。

Just use your generic function, it will have the same performance as the "specialized" one. 只需使用您的通用函数,它将具有与“专用”函数相同的性能。 The point of specialized functions in Julia for particular types is only when you need a different algorithm. 在Julia中,针对特定类型的专用功能仅在您需要其他算法时才有意义。 For instance intersect(circle, triangle) requires different code from intersect(circle, circle) . 例如, intersect(circle, triangle)需要与intersect(circle, circle)不同的代码。 However you would not write a different method for circles using 32 bit floating point and those using 64 bit floating point numbers. 但是,对于使用32位浮点数和使用64位浮点数的圆,您不会编写其他方法。


To give give some concrete advice, let me comment on some of the code you have written. 为了给出一些具体的建议,让我对您编写的一些代码发表评论。

 if rtol <= 0.0
        error("findrootNewton: rtol must be a positive number")
 end

For the generic version it is better to write one of these versions in prioritized order: 对于通用版本,最好按优先顺序编写以下版本之一:

  1. if rtol <= zero(rtol)
  2. if rtol <= zero(T) where T is the type of rtol if rtol <= zero(T) ,其中Trtol的类型
  3. if rtol <= 0

Because that makes sure that you are comparing number of equal type, thus avoiding type conversions. 因为这样可以确保您比较的是相等类型的数目,从而避免了类型转换。 Integer 0 is better than floating point 0.0 because it will typically cause less type conversions/promotions. 整数0优于浮点0.0因为它通常会导致较少的类型转换/促销。

Just to nitpick you may want to use throw(DomainError(rtol, "findrootNewton: rtol must be a positive number")) to indicate one of the function arguments are outside of the domain. 仅仅为了nitpick,您可能想要使用throw(DomainError(rtol, "findrootNewton: rtol must be a positive number"))来指示函数参数之一在域之外。


I can specify the type of data a variable stores in Julia, for example 例如,我可以指定变量存储在Julia中的数据类型

 x::Float64 = 2.5 

This is unnecessary and the wrong way to think about what you are doing. 这是不必要的,并且是错误的思考方式。 Remember Julia is not a statically typed language. 请记住,Julia不是静态类型的语言。 If you are a C/C++ developer you may think about variables as little memory boxes of different size, which can hold a float, integer or boolean. 如果您是C / C ++开发人员,则可以将变量视为大小不同的小内存盒,它们可以容纳浮点数,整数或布尔值。 This assignment then means putting 2.5 into a 64 bit floating point box. 然后,此分配意味着将2.5放入64位浮点数框中。

However that is not what is happening in a dynamic language like Julia. 但是,这不是动态语言(如Julia)中发生的情况。 Conceptually what happens is that you create a floating point object 2.5 and you stick a label x on it. 从概念上讲,您将创建一个浮点对象2.5并在其上粘贴标签x Assignment is in a way the reverse of what you may be thinking. 分配方式与您所想的相反。 You are not assigning a number to a box named x . 您没有将数字分配给名为x的框。 Rather you are sticking a label x onto a number 2.5 . 而是将标签x粘贴到数字2.5 So writing stuff like: 所以写这样的东西:

converged::Bool = false

is totally unnecessary. 完全没有必要。 You are not making sure false is put into a boolean sized box. 您不能确保将false放入布尔值大小的框中。 There is no box. 没有盒子。 Instead you are sticking the label converged on a false object and then afterwards you are asserting that the label converged is attached to a boolean object. 取而代之的是,您将会converged的标签粘贴在一个false对象上,然后再声明converged的标签已附加到布尔对象上。 This gives you no performance or memory advantage. 这不会给您带来性能或内存方面的优势。


You only do this line in your generic version, and presumable you assume this is less performant than what you do in your Float64 version. 您仅在常规版本中执行此行,并且可以假定与在Float64版本中相比,此性能较低。

oldx = convert(typeof(newx), guess)

However the call to convert and typeof does not make any difference. 但是,对converttypeof的调用没有任何区别。 In your ideal case where all the types match up, these calls just get optimized away. 在所有类型都匹配的理想情况下,这些调用会被优化掉。 Look at this simple example: 看这个简单的例子:

julia> foobar(a, b) = convert(typeof(a), a + b)
foobar

julia> @code_warntype foobar(1, 1)
Body::Int64
1 ─ %1 = (Base.add_int)(a, b)::Int64
└──      return %1

julia> @code_warntype foobar(1.0, 1.0)
Body::Float64
1 ─ %1 = (Base.add_float)(a, b)::Float64
└──      return %1

You can see when the types match, this just gets reduced to a simple integer or floating point add by the Julia JIT. 您可以看到类型匹配时,Julia JIT会将其简化为简单的整数或浮点加法。

If you are not sure about the performance implications of writing functions one way or another, I advice you to get used to using the @code_warntype , @code_llvm and @code_native macros. 如果您不确定以一种方式编写函数对性能的影响,建议您习惯使用@code_warntype@code_llvm@code_native宏。 They give you valuable information about how Julia transforms your code given particular sets of arguments. 它们为您提供了有关Julia在给定特定参数集的情况下如何转换代码的宝贵信息。

As for your question about whether you can create type assertions for function signatures. 至于您是否可以为函数签名创建类型断言的问题。 You cannot do that in Julia presently. 您目前无法在Julia中做到这一点。 In your case it is not needed. 在您的情况下,则不需要。

However workarounds typically involve using some trait based approach. 但是,解决方法通常涉及使用某些基于特征的方法。 You could consider turning the arguments of your Newton method into a type, and then have your input dispatch on that. 您可以考虑将Newton方法的参数转换为类型,然后在其上分配输入。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM