【问题标题】:Non-escaping error when implementing Church Numerals in Swift 3在 Swift 3 中实现教堂数字时出现非转义错误
【发布时间】:2017-02-10 03:56:43
【问题描述】:

我正在尝试在 Swift 3 中实现 Church Numerals。目前,我有:

func numToChurch(n: Int) -> ((Int) -> Int) -> Int {

    return { (f: (Int) -> Int) -> (Int) -> Int in
        return { (x : Int) -> Int in
            return f(numToChurch(n: n - 1)(f)(x))
        }
    }
}

func churchToNum(f: ((Int) -> Int) -> (Int)-> Int) -> Int {
    return f({ (i : Int) -> Int in
        return i + 1
    })(0)
}

在我的函数 numToChurch 的这一行:

return f(numToChurch(n: n - 1)(f)(x))

我不断收到一个编译时错误,“关闭非转义参数 'f' 可能允许它转义”。作为快速修复,我接受了包含@escaping 的建议更改:

func numToChurch(n: Int) -> ((Int) -> Int) -> Int {

    return { (f: @escaping (Int) -> Int) -> (Int) -> Int in
        return { (x : Int) -> Int in
            return f(numToChurch(n: n - 1)(f)(x))
        }
    }
}

但即使在进行了更改之后,我仍然会收到相同的错误消息,它建议在“f:”之后添加另一个 @escaping。我知道这与将函数参数标记为 @escaping 以告诉编译器可以存储或捕获参数以进行函数式编程有关。但我不明白为什么我不断收到此错误。

原始非转义问题已解决

帮助理解 Swift 中的教堂编码续:

func zero(_f: Int) -> (Int) -> Int {
    return { (x: Int) -> Int in
        return x
    }
}

func one(f: @escaping (Int) -> Int) -> (Int) -> Int {
    return { (x: Int) in
        return f(x)
    }
}

func two(f: @escaping (Int) -> Int) -> (Int) -> Int {
    return { (x: Int) in
        return f(f(x))
    }
}

func succ(_ f: Int) -> (@escaping (Int) -> Int) -> (Int) -> Int {
    return { (f : @escaping ((Int) -> Int)) -> Int in
        return { (x : Int) -> Int in
            return f(n(f)(x))
        }
    }
}


func sum(m: @escaping ((Int) -> (Int) -> Int)) -> ((Int) -> (Int) -> Int) -> (Int) -> (Int) -> Int {
    return { (n: @escaping ((Int) -> Int)) -> (Int) -> (Int) -> Int in
        return { (f: Int) -> (Int) -> Int in
            return { (x: Int) -> Int in
                return m(f)(n(f)(x))
            }
        }
    }

【问题讨论】:

  • 欢迎来到 Swift 中的(通常令人沮丧的)FP 世界,或者我们喜欢称之为:“远离 Swift 的舒适区。” :D 但是,这仍然是一个很好的练习;祝你好运,欢迎加入俱乐部!

标签: swift functional-programming swift3 church-encoding church


【解决方案1】:

您正在对多参数函数使用柯里化。这不是在 Swift 中表达事物的一种非常自然的方式,它使事情变得复杂。 (Swift is not a functional programming language.)

正如您链接的文章所说,“所有教堂数字都是带有两个参数的函数。”就这样做吧。使其成为一个双参数函数。

typealias Church = (_ f: ((Int) -> Int), _ x: Int) -> Int

这是一个接受两个参数的函数,一个函数及其参数。

现在你想将参数包装在函数中 N 次:

// You could probably write this iteratively, but it is pretty elegant recursively 
func numToChurch(_ n: Int) -> Church {
    // Church(0) does not apply the function
    guard n > 0 else { return { (_, n) in n } }

    // Otherwise, recursively apply the function
    return { (f, x) in
        numToChurch(n - 1)(f, f(x))
    }
}

返回只是应用函数:

func churchToNum(_ church: Church) -> Int {
    return church({$0 + 1}, 0)
}

只是在此基础上建立起来,你可以咖喱它(我想我只是在说@kennytm 也回答了)。 Currying 在 Swift 中稍微复杂一些:

typealias Church = (@escaping (Int) -> Int) -> (Int) -> Int

func numToChurch(_ n: Int) -> Church {
    // Church(0) does not apply the function
    guard n > 0 else { return { _ in { n in n } } }

    return { f in { x in
        numToChurch(n - 1)(f)(f(x))
        }
    }
}

func churchToNum(_ church: Church) -> Int {
    return church({$0 + 1})(0)
}

有一个非常合理的问题:“为什么我在第二种情况下需要@escaping,而在第一种情况下不需要?”答案是当你在一个元组中传递函数时,你已经将它转义了(通过将它存储在另一个数据结构中),所以你不需要再次标记它@escaping


对于您的其他问题,使用类型别名显着简化了这个问题,并帮助您更清楚地思考您的类型。

那么零的参数是什么?没有。这是一个常数。那么它的签名应该是什么?

func zero() -> Church

我们如何实现它?我们申请f 零次

func zero() -> Church {
    return { f in { x in
        x
        } }
}

一和二几乎相同:

func one() -> Church {
    return { f in { x in
        f(x)
        } }
}

func two() -> Church {
    return { f in { x in
        f(f(x))
        } }
}

succ 的签名是什么?它需要一个教堂并返回一个教堂:

func succ(_ n: @escaping Church) -> Church {

因为这是 Swift,我们需要通过添加 @escaping_ 来让事情变得更自然。 (Swift 不是函数式语言;它分解问题的方式不同。组合函数不是它的自然状态,所以语法的过度丰富不应该让我们感到震惊。)如何实现?再申请一个fn

func succ(_ n: @escaping Church) -> Church {
    return { f in { x in
        let nValue = n(f)(x)
        return f(nValue)
        } }
}

再说一次,sum 的本质是什么?好吧,我们心情很不好,所以这意味着它是一个接受 Church 并返回一个接受 Church 并返回 Church 的函数的函数。

func sum(_ n: @escaping Church) -> (@escaping Church) -> Church

同样,因为 Swift.js 需要一些额外的语法。 (如上所述,我添加了一个额外的 let 绑定,只是为了让这些片段更清晰。)

func sum(_ n: @escaping Church) -> (@escaping Church) -> Church {
    return { m in { f in { x in
        let nValue = n(f)(x)
        return m(f)(nValue)
        } } }
}

这里的深刻教训是Church 类型别名的强大功能。当您尝试将教堂数字视为“诸如此类的功能”时,您很快就会迷失在咖喱和语法中。取而代之的是,将它们抽象为“教会编号”,并考虑每个函数应该采用和返回什么。请记住,Church 编号始终是一个接受 Int 并返回 Int 的函数。无论嵌套多少次,它都不会因此而增长或收缩。


这个例子值得从其他几个方向看,因为我们可以发挥一些关于 FP 的更深层次的想法,以及真正应该如何编写 Swift(它们不一样......)

首先,以尖刻的方式书写教堂编号是……不雅的。只是感觉很糟糕。教会编号是根据功能组成而不是应用来定义的,因此它们应该以 IMO 的无点风格编写。基本上,在任何你看到{ f in { x in ...} } 的地方,这都是丑陋和过度的语法。所以我们想要功能组合。好的,我们可以挖掘一些实验性的stdlib features 并得到它

infix operator ∘ : CompositionPrecedence

precedencegroup CompositionPrecedence {
    associativity: left
    higherThan: TernaryPrecedence
}

public func ∘<T, U, V>(g: @escaping (U) -> V, f: @escaping (T) -> U) -> ((T) -> V) {
    return { g(f($0)) }
}

现在,这对我们有什么用?

func numToChurch(_ n: Int) -> Church {
    // Church(0) does not apply the function
    guard n > 0 else { return zero() }
    return { f in f ∘ numToChurch(n - 1)(f) }
}

func succ(_ n: @escaping Church) -> Church {
    return { f in f ∘ n(f) }
}

func sum(_ n: @escaping Church) -> (@escaping Church) -> Church {
    return { m in { f in
        n(f) ∘ m(f)
        } }
}

所以我们不需要再谈论x了。 IMO,我们更加有力地抓住了教会数字的本质。将它们相加相当于功能组合。

但话虽如此,IMO 这不是很棒的 Swift。 Swift 需要的是结构和方法,而不是函数。它绝对不想要一个名为zero() 的顶级函数。那是可怕的斯威夫特。那么我们如何在 Swift 中实现教堂编号呢?通过提升为类型。

struct Church {
    typealias F = (@escaping (Int) -> Int) -> (Int) -> Int
    let applying: F

    static let zero: Church = Church{ _ in { $0 } }

    func successor() -> Church {
        return Church{ f in f ∘ self.applying(f) }
    }

    static func + (lhs: Church, rhs: Church) -> Church {
        return Church{ f in lhs.applying(f) ∘ rhs.applying(f) }
    }
}

extension Church {
    init(_ n: Int) {
        if n <= 0 { self = .zero }
        else { applying = { f in f ∘ Church(n - 1).applying(f) } }
    }
}

extension Int {
    init(_ church: Church) {
        self = church.applying{ $0 + 1 }(0)
    }
}

Int(Church(3) + Church(7).successor() + Church.zero) // 11

【讨论】:

  • 非常感谢。在试图理解 Swift 中的教堂数字时,我感到非常沮丧和迷失。这很有帮助。
【解决方案2】:

@escaping 是参数类型的一部分,所以你需要这样做:

func numToChurch(n: Int) -> (@escaping (Int) -> Int) -> (Int) -> Int {
//                           ^~~~~~~~~

完整的工作代码:

func numToChurch(n: Int) -> (@escaping (Int) -> Int) -> (Int) -> Int {
//                           ^~~~~~~~~                        ^~~~~~
    return { (f: @escaping (Int) -> Int) -> (Int) -> Int in
//               ^~~~~~~~~
        if n == 0 {
            return { x in x }
        } else {
            return { (x : Int) -> Int in
                return f(numToChurch(n: n - 1)(f)(x))
            }
        }
    }
}

func churchToNum(f: (@escaping (Int) -> Int) -> (Int) -> Int) -> Int {
//                   ^~~~~~~~~
    return f({ (i : Int) -> Int in
        return i + 1
    })(0)
}

let church = numToChurch(n: 4)
let num = churchToNum(f: church)

注意:

  1. 即使没有 @escaping 部分,您的返回类型 numToChurch 也是错误的。您缺少-&gt; Int

  2. 我在numToChurch中添加了基础n == 0 case,否则会无限递归。

  3. 由于numToChurch的结果有转义闭包,所以churchToNum的结果也需要添加相同的注解。

【讨论】:

  • 非常感谢您和 @rob-napier 对您的 cmets。您的编辑帮助解决了我无法逃避的问题,我现在明白为什么会发生这种情况。我也在尝试了解如何实现继任者等 FP 功能。我在我的原始帖子中包含了更多代码——如果你能提供反馈和一些关于如何继续的指导,我将非常感激!
猜你喜欢
  • 2019-07-14
  • 1970-01-01
  • 2015-05-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多