【问题标题】:How to handle closure recursivity如何处理闭包递归
【发布时间】:2014-09-26 00:47:11
【问题描述】:

这是一个非常简单的递归函数:

func lap (n: Int) -> Int {
    if n == 0 { return 0 }
   return lap (n - 1)
}

如果我想将其转换为闭包:

let lap = {
    (n: Int) -> Int in
    if n == 0 { return 0 }
    return lap (n - 1)
}

我收到一个编译器错误:“变量在其自己的初始值内使用”

【问题讨论】:

    标签: swift


    【解决方案1】:

    你可以通过两步分配来解决它

    var lap : (Int) -> Int!
    lap = {
        (n: Int) -> Int in
        if n == 0 { return 0 }
        return lap(n - 1)
    }
    

    或者你可以使用Ycombinator

    func Y<T, R>( f: (T -> R) -> (T -> R) ) -> (T -> R) {
        return { t in f(Y(f))(t) }
    }
    
    let lap = Y {
        (f : Int -> Int) -> (Int -> Int) in
        return { (n : Int) -> Int in return n == 0 ? 0 : f(n - 1) }
    }
    
    // with type inference 
    let lap2 = Y {
        f in { n in n == 0 ? 0 : f(n - 1) }
    }
    

    这是@zneak 发现的内存泄漏问题的解决方法(它没有内存泄漏但捕获了错误的值)

    func f(n: Int) {
        var f = Foo()
        var lap: @objc_block (Int)->Int = { $0 }
        var obj: NSObject = reinterpretCast(lap)
        lap = {
            [weak obj] (n: Int) -> Int in // unowned will cause crush
            if n == 0 { return 0 }
            println(f)
            var lap2 : @objc_block (Int)->Int = reinterpretCast(obj)
            return lap2 (n - 1)
        }
        lap(n)
    }
    
    for i in 0..<5 {
        f(i)
    }
    
    class Foo {
        init() {
            println("init");
        }
    
        deinit {
            println("deinit")
        }
    }
    

    【讨论】:

    • 谢谢。我发布了另一个解决方案。
    • 请注意,您的第一个示例应该是var lap: ((Int) -&gt; Int)!。它按原样工作,但如果你有 void 作为返回,编译器会抱怨在你的代码块中实际上没有返回 void
    【解决方案2】:

    EDIT Swift 2 使用嵌套函数解决了这个问题。 Apple 建议使用此代码:

    func f(n: Int) {
        func lap(n: Int) -> Int {
            if n == 0 { return 0 }
            print(n)
            return lap(n - 1)
        }
        lap(n)
    }
    
    for i in 0..<1000000 { f(i) }
    

    虽然这在当前示例中并不明显,但所谓的局部函数会捕获封闭范围的局部变量。

    使用定位函数不会泄漏,而闭包会。但是,很明显,lap 在这种情况下不能被重新分配。

    我收到了一封来自 Apple Joe Groff 的电子邮件,称他们仍计划在以后将闭包作为弱变量和可变变量捕获。然而,这确实证实了,除了使用本地函数之外,现在没有其他方法可以做到这一点。


    您当前的解决方案存在内存泄漏:lap 的闭包对自身有很强的引用,这意味着它永远无法释放。这可以通过启动以下附加了 Leaks 工具的程序来轻松验证:

    import Foundation
    
    func f(n: Int) {
        var lap: (Int)->Int = { $0 }
        lap = {
            (n: Int) -> Int in
            if n == 0 { return 0 }
            println(n)
            return lap (n - 1)
        }
        lap(n)
    }
    
    for i in 0..<1000000 {
        f(i)
    }
    

    不幸的是,由于explicit capture syntax 不能应用于闭包类型(您会收到一条错误消息,提示“'unowned' cannot be applied to non-class type '(Int) -> Int'”),似乎有没有简单的方法可以在不泄漏的情况下实现这一目标。我提交了一份关于它的错误报告。

    【讨论】:

    • 有趣,我从不认为它可能会导致内存泄漏。我想知道我的 Y-combinator 解决方案是否有同样的问题?
    • 我在my answer 中发现了一种解决显式捕获语法问题的方法。
    • 一个奇怪的小问题的优雅解决方案,谢谢!有谁知道是否有关于弱闭包的更新?
    【解决方案3】:

    这是对我自己问题的回答:

    var lap: (Int)->Int = { $0 }
    lap = {
        (n: Int) -> Int in
        if n == 0 { return 0 }
        println(n)
        return lap (n - 1)
    }
    

    【讨论】:

    • 其实{ $0 )是不必要的。出于某种原因,在编写实际的闭包块词之前声明类型。 var lap: (Int) -&gt; Int 应该可以正常工作
    • @BridgeTheGap 两年后发表迟到的评论,但这条评论对我来说似乎是错误的。事实上,编译器抱怨 var lap 没有初始化,只是声明了。
    【解决方案4】:

    这个呢:

    let lap = {(Void) -> ((Int) -> Int) in
       func f(n: Int) -> Int {
          print(n)
          return n == 0 ? 0 : f(n - 1)
       }
       return f
    }()
    

    这很简单,我刚刚在返回函数的闭包内定义了一个递归局部函数。

    但是,我不得不说@Bryan Chen 关于 Y 组合器 的回答非常棒。

    【讨论】:

      【解决方案5】:

      我遇到了同样的问题,并且对现有的任何东西都不满意,所以我创建了一个库并在 GitHub 上提供了它。

      使用这个库(使用 Swift 3.0),您的代码将如下所示:

      let lap = Recursion<Int, Int> { (n, f) in
          n == 0 ? 0 : f(n-1)
      }.closure
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-05-23
        • 1970-01-01
        • 1970-01-01
        • 2012-05-02
        • 2017-03-26
        • 1970-01-01
        • 1970-01-01
        • 2013-07-23
        相关资源
        最近更新 更多