【问题标题】:Understanding recursion without base case in Julia在 Julia 中理解没有基本情况的递归
【发布时间】:2016-09-01 22:14:12
【问题描述】:

这个 sn-p 来自 Julia 中 Rational Numbers 的实现:

# Rational.jl
# ...
Rational{T<:Integer}(n::T, d::T) = Rational{T}(n,d)
Rational(n::Integer, d::Integer) = Rational(promote(n,d)...)
Rational(n::Integer) = Rational(n,one(n))

//(x::Rational, y::Integer) = x.num // (x.den*y) <--- HERE!

# ...

看看// 函数是如何实现的,然后与中缀表示法一起使用?这实际上是如何返回值的?

当我看到这段代码时,我是这样解释的:

  1. // 函数使用 Rational 和 Integer 调用。
  2. 但随后它会在没有其他参数的情况下进行递归调用。

#2 是真正让我感到困惑的一个。数据结构中的递归在哪里结束?如果// 不断地不求值,它如何返回一个值?

请帮助我理解这一点。

【问题讨论】:

    标签: recursion julia recursive-datastructures


    【解决方案1】:

    这是因为 Julia 最基本的特性之一:多重分派。在 Julia 中,函数可以有许多方法适用于参数类型的各种组合,当您调用函数时,Julia 会调用与您调用它的所有参数类型匹配的最具体的方法。您发布的方法定义中的// 调用根据整数// 定义了有理整数// - 所以它实际上不是递归的,因为该方法不会调用自身,它调用不同的方法是同一个“通用函数”的一部分。

    要了解在这种情况下多分派是如何工作的,让我们考虑表达式(3//4)//6 的评估。我们将使用@which 宏来查看每个函数调用调用的方法。

    julia> @which (3//4)//6
    //(x::Rational{T<:Integer}, y::Integer) at rational.jl:25
    

    由于3//4Rational{Int} &lt;: Rational6Int &lt;: Integer,并且没有其他更具体的方法适用,因此调用此方法:

    //(x::Rational, y::Integer) = x.num // (x.den*y)
    

    该方法的current version 实际上比您发布的稍微复杂一些,因为它已被修改以检查整数溢出 - 但它本质上是相同的,并且更容易理解旧的、更简单的版本,所以我会用那个。让我们将xy 分配给参数,看看定义调用了什么方法:

    julia> x, y = (3//4), 6
    (3//4,6)
    
    julia> x.num
    3
    
    julia> x.den*y
    24
    
    julia> x.num // (x.den*y)
    1//8
    
    julia> @which x.num // (x.den*y)
    //(n::Integer, d::Integer) at rational.jl:22
    

    如你所见,这个表达式并没有调用同一个方法,它调用了different method

    //(n::Integer,  d::Integer) = Rational(n,d)
    

    此方法只是调用Rational 构造函数,它将nd 的比率放入最低项并创建一个Rational 数字对象。

    在 Julia 中,根据同一个函数的另一个方法来定义一个函数的一个方法是很常见的。例如,这就是参数默认值的工作方式。考虑这个定义:

    julia> f(x, y=1) = 2x^y
    f (generic function with 2 methods)
    
    julia> methods(f)
    # 2 methods for generic function "f":
    f(x) at none:1
    f(x, y) at none:1
    
    julia> f(1)
    2
    
    julia> f(2)
    4
    
    julia> f(2,2)
    8
    

    默认参数语法只是生成第二个只有一个参数的方法,它调用具有默认值的双参数形式。所以f(x, y=1) = 2x^y 完全等同于定义两个方法,其中一元方法只是调用二元方法,为第二个参数提供默认值:

    julia> f(x, y) = 2x^y
    f (generic function with 1 method)
    
    julia> f(x) = f(x, 1)
    f (generic function with 2 methods)
    

    【讨论】:

    • 这非常清晰。谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-12-29
    • 2021-08-13
    • 2015-04-22
    • 2013-10-11
    • 1970-01-01
    • 1970-01-01
    • 2017-10-24
    相关资源
    最近更新 更多