为什么递归 swift 函数定义不会为其初始 'values' 创建问题?

Why don't recursive swift function definitions create issues with their initial 'values'?

这里有多个关于尝试在 Swift 中定义递归闭包的有趣问题。我认为回答最清楚的是这个 post 问 why one can't declare and define a recursive closure in the same line in Swift 的问题。但是,他们不会问相反的问题。也就是说,如果我不能创建这样的代码:

var countdown = { i in
    print(i)
    if i > 1 {
        countdown(i - 1)
    }
}
// error: Variable used within its own initial value

那为什么允许我写这样的代码:

func countdown(_ i: Int) {
    print(i)
    if i > 1 {
        countdown(i - 1)
    }
}

函数有什么特别之处,使得它们 不会 在尝试在自己的声明中调用自己时遇到任何问题?我理解闭包背后的问题:它试图捕获一个尚不存在的值(闭包本身)。但是,我不明白的是为什么函数没有同样的问题。查看 Swift Book,我们看到:

Global functions are closures that have a name and don’t capture any values.

这是半个答案:上面的函数不会导致问题,因为函数不捕获值。但是,如果函数不捕获值(尤其是递归值),它们将如何工作?

一个是函数,另一个是存储的 属性,从语义上讲,存储的属性(和属性)是状态,函数是动作。这就是 Swift 编译器如何解释您的代码以及为什么它不起作用。

在您的示例中,countdown 被编译器编译为存储的 属性。您可以将存储的 属性 转换为计算的 属性 以使其表现得更像一个函数,但您不仅会失去传递参数的能力,而且 属性 仍然不会能够在它自己的 getter 中访问它自己(因为它仍然是一个 属性 而不是一个函数)。

New Dev 的回答是 语法解决方法 countdown 从存储的 属性 更改为一个函数——他将其声明为一个函数并且后来给它分配了一个闭包。您试图将一个函数塞进 属性 中,并想知道为什么 Swift 对一个函数给予特殊对待而不是另一个 — 它没有,您只是将它们变成了两种不同的类型。我特别指出这一点,因为您的问题(在我看来)是为什么函数与属性的处理方式不同。这是因为函数和属性是两个本质上不同的东西。

/* This syntax tells the compiler that
   countdown is a stored property, hence
   the error of accessing self in the
   property's own getter. */
var countdown = { i in
    print(i)
    if i > 1 {
        countdown(i - 1)
    }
}

/* This syntax tells the compiler that
   countdown is a computed property, but
   this will still not compile because
   computed properties cannot take arguments
   or access self in the getter. */
var countdown: (Int) -> () { i in
    print(i)
    if i > 1 {
        countdown(i - 1)
    }
}

/* All this does is tell the compiler
   that countdown is of type function.
   This variable will no longer be compiled
   as a stored property. We've changed its
   type to function which is why it works. */
var countdown: ((Int) -> Void)!

不太确定您要寻找的答案是什么。我的意思是,一个简单的答案是 Swift 编译器就是这样工作的。

一个原因可能是 func 表达式,例如func countdown(...) {},编译器可以安全地假定该函数将在它调用自身时定义。

而对于变量定义(即变量获取其值的表达式),它通常不会做出这样的假设,因为像

var c = c + 1

由于相同的“在其自身初始值内使用的变量”错误,显然无法正常工作。

话虽如此,编译器可能会对闭包类型变量的定义进行特殊处理,因为与非闭包变量不同,它们在定义时不需要实际值。

这就是为什么一个解决方案是定义变量(或者至少向编译器保证它将用 ! 定义),这样编译器就不会抱怨:

var countdown: ((Int) -> Void)!
countdown = { i in
    print(i)
    if i > 1 {
        countdown(i - 1)
    }
}