2016-10-01 45 views
4

我試圖在斯威夫特3.實施Church Numerals目前,我有:非逃逸錯誤3

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)) 
     } 
    } 
} 

但是,即使進行更改後,我一直斥罵同樣的錯誤,並建議將陸續@escaping「F:」。我知道這與將函數參數標記爲@escaping有關,以告知編譯器參數可以存儲或捕獲以進行函數式編程。但我不明白爲什麼我不斷收到這個錯誤。

原非逃避問題解決

幫助與斯威夫特續理解教會編碼:

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)) 
      } 
     } 
    } 
+1

歡迎來到斯威夫特的FP世界(通常令人沮喪),或者我們喜歡稱之爲:「在Swift的舒適區之外。」 :D但是,它仍然是一個很好的練習;祝你好運,歡迎來到俱樂部! –

回答

7

您使用鑽營多參數功能。這不是一種在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也回答)。柯里只是稍微複雜斯威夫特:

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


爲了您還有其他疑問,使用typealias 大大簡化這個問題,並幫助你思考你的類型更清楚。

那麼零的參數是什麼?沒有。這是一個常數。那麼它的簽名應該是什麼?

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 { 

因爲這是斯威夫特,我們需要通過添加@escaping_使事情變得更加自然一點微調。 (Swift不是一種功能語言,它分解問題的方式不同,編寫函數不是它的自然狀態,所以語法的過度運用不應該讓我們震驚。)如何實現?應用多了一個fn

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

再次,什麼是sum性質是什麼?那麼,我們處於一種壓抑的情緒,所以這意味着這是一個功能,需要一個教會,並返回一個功能,需要一個教會,並返回一個教會。

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

再次,因爲Swift需要一些額外的語法。 (如上述,我增加了一個額外讓利結合只是爲了讓作品更加清楚一點。)

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 typealias的力量。當你試圖將教會的數字想象成「等等等等的功能」時,你很快就會迷失在咖喱和語法中。相反,將他們抽象爲「教會號碼」,並考慮每個職能應該採取和返回什麼。請記住,一個教會號碼是總是一個函數,它接受一個I​​nt並返回一個Int。無論它嵌套多少次,它永遠不會增長或縮小。


這是值得考慮的其他幾個方向的這個例子,因爲我們可以發揮出FP的一些深層次的想法,以及如何斯威夫特真的應該被寫入(這是不一樣的....)

首先,用尖頭的方式寫教會的數字是不雅的。它只是感覺不好。教堂的數字是根據功能組成來定義的,而不是應用,所以它們應該以無點式的IMO寫成。基本上,在任何你看到的地方​​,這只是醜陋的和過度的語法。所以我們想要功能組合。好的,我們可以挖掘一些實驗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需要結構和方法,而不是函數。它絕對不需要稱爲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 
+0

非常感謝。我在試圖瞭解斯威夫特的教會數字時感到非常灰心並迷失了方向。這非常有幫助。 – fayche

3

@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. 您的退貨類型爲numToChurch即使沒有@escaping部分也是錯誤的。您錯過了-> Int

  2. 我在numToChurch中加上了基數n == 0的情況,否則就是無限遞歸。

  3. 由於numToChurch的結果有一個轉義關閉,所以需要將相同的註釋添加到churchToNum

+0

非常感謝您和@ rob-napier的評論。您的編輯幫助解決了我的非轉義問題,並且現在我明白了爲什麼會發生這種情況。我也在試圖理解如何實現FP功能,如繼任者。我在原始文章中包含了更多代碼 - 如果您可以提供反饋意見並提供一些關於如何繼續的指導,我將非常感激! – fayche