您使用鑽營多參數功能。這不是一種在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不是一種功能語言,它分解問題的方式不同,編寫函數不是它的自然狀態,所以語法的過度運用不應該讓我們震驚。)如何實現?應用多了一個f
到n
:
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的力量。當你試圖將教會的數字想象成「等等等等的功能」時,你很快就會迷失在咖喱和語法中。相反,將他們抽象爲「教會號碼」,並考慮每個職能應該採取和返回什麼。請記住,一個教會號碼是總是一個函數,它接受一個Int並返回一個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
歡迎來到斯威夫特的FP世界(通常令人沮喪),或者我們喜歡稱之爲:「在Swift的舒適區之外。」 :D但是,它仍然是一個很好的練習;祝你好運,歡迎來到俱樂部! –