swift函數(shù)式編程-抽象計算過程

本文的目的是為了總結(jié)下我在開發(fā)者大會上看傅若愚做的演講。因為大會的時間比較短,所以,他并沒有足夠的時間來解釋清楚他是如何解決回調(diào)地獄的。我后續(xù)請教了他,他用下面非常清晰思維過程做了回復!
再此,非常感謝@傅若愚的耐心指導,大家有空也一定要看一看@swift開發(fā)者大會的相關視頻,一定會收貨頗多。

以下內(nèi)容基本出自他的講義,以及他對我提的問提“為什么使用這種方式解決回調(diào)地獄的思考過程”的回復。

典型的回調(diào)地獄

解決回調(diào)地獄之后的代碼

抽象這個過程

Hmmmm,為什么我們會想到用Async來簡化回調(diào)地獄呢?我們得回頭想想所謂的回調(diào)地獄到底是怎么一回事,首先一起來看一段相對簡單的“回調(diào)地獄”吧(為了簡單起見,我們先不考慮錯誤處理):

getFirstItem() {
 firstItem in getSecondItem(firstItem) { 
      secondItem in getThirdItem(secondItem){ 
          thirdItem in 
          //Here we get our third item 
      } 
  }
}

所以它到底是在干什么?如果我們將它按照人類的語言描述一遍,并盡量不遺漏掉任何的代碼執(zhí)行過程:

  • 1、異步地發(fā)出一個請求,在回調(diào)中獲得請求的數(shù)據(jù)。(即firstItem)然后根據(jù)上一步的數(shù)據(jù),在getSecondItem中計算出一個新的請求。

  • 2、異步地發(fā)出這個新的請求,并在回調(diào)中獲得請求的數(shù)據(jù)。(即secondItem)然后根據(jù)上一步的數(shù)據(jù),在getThirdItem中計算出一個新的請求。

  • 3、異步地發(fā)出這個新的請求,并在回調(diào)中獲得請求的數(shù)據(jù)。(即thirdItem)

分析這個過程

于是我們可以看到,所謂的“回調(diào)地獄”其實也不過是在重復地做一些計算的過程,如果我們能夠找到一種對類似的計算過程的封裝方法,那么我們就能解決“回調(diào)地獄”的問題。
這里如果直接去想,去找,真的是挺難的,原因在于我們平時寫代碼其實更多地是在對數(shù)據(jù)進行抽象:比如我們會去抽象一個用戶,一個商品——這些東西都是一個一個的對象(或者說封裝之后的數(shù)據(jù)),即便涉及到對象的方法,那本質(zhì)上也是在對數(shù)據(jù)的行為進行的抽象。而現(xiàn)在,我們卻是需要對一種計算的過程進行抽象,它需要的編程思維和我們平時所習慣的代碼寫法完全是不一樣的!

想想是否有現(xiàn)有的模式去解決這個問題

那么,有沒有類似的已經(jīng)做出來的其他過程抽象的例子可以給我們參考,幫助我們進行思考呢?答案當然是有的,并且不止一個!如果對Swift的標準庫比較熟悉,那么很容易想到Optional.flatMap這個函數(shù):

public enum Optional<Wrapped> : _Reflectable, NilLiteralConvertible {
    case None
    case Some(Wrapped)
    /// Construct a `nil` instance.
    public init()
    /// Construct a non-`nil` instance that stores `some`.
    public init(_ some: Wrapped)
    /// If `self == nil`, returns `nil`.  Otherwise, returns `f(self!)`.
    @warn_unused_result
    public func map<U>(@noescape f: (Wrapped) throws -> U) rethrows -> U?
    /// Returns `nil` if `self` is nil, `f(self!)` otherwise.
    @warn_unused_result
    public func flatMap<U>(@noescape f: (Wrapped) throws -> U?) rethrows -> U?
    /// Create an instance initialized with `nil`.
    public init(nilLiteral: ())
}

這個函數(shù)的具體用法如下:

someOptionalNumber
.flatMap(divide3) //{x in x == 0 ? nil : return 3/x} 
.flatMap(divide5) //{x in x == 0 ? nil : return 5/x} 
//我們還可以無限地鏈下去......

如果我們把上面這段代碼像之前那樣描述一遍:
1、對一個Optional數(shù)據(jù),確保這個數(shù)據(jù)的值不為nil,否則后面的步湊不執(zhí)行。然后根據(jù)上一步的數(shù)據(jù),在divide3中計算出一個新的Optional數(shù)據(jù)。

2、對這個新的Optional數(shù)據(jù),確保這個數(shù)據(jù)的值不為nil,否則后面的步湊不執(zhí)行然后根據(jù)上一步的數(shù)據(jù),在divide5中計算出一個新的Optional數(shù)據(jù)。

3、對這個新的Optional數(shù)據(jù)……

終于找到了這個計算過程

我們發(fā)現(xiàn),這個過程竟然與我們此前的描述很多部分驚人的相似!

然后根據(jù)上一步的數(shù)據(jù),在XXX中計算出一個新的XXXX。

這句話似乎應該是一個pattern,如果它是一個pattern,肯定不止會在一個地方出現(xiàn)!于是我們再來看看這個我們可能更加熟悉的函數(shù)!

[1,2,3,4,5] 
.flatMap(duplicate) // { x in return [x, x] }
.flatMap(anotherFunction) // {x in return [x, x]}
//[1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5]

老辦法,把它真正做的事情翻譯一遍,并盡量不遺漏任何計算過程
遍歷一個Array,得到其中的每一個數(shù)據(jù)然后根據(jù)上一步的數(shù)據(jù),在duplicate中計算出一個新的Array

遍歷這個新的Array,得到其中的每一個數(shù)據(jù)然后根據(jù)上一步的數(shù)據(jù),在anotherFunction中計算出一個新的Array

......

果然出現(xiàn)了!如果我們現(xiàn)在再根據(jù)上下文回去看我們“回調(diào)地獄”的問題,我們可以確信,只要我們將這個步驟封裝好了,我們的問題也就解決了!并且我們所需要做的事情僅僅是定義一個針對回調(diào)函數(shù)的flatMap而已。首先,讓我們把一個異步函數(shù)封裝進一個結(jié)構(gòu)體,什么是異步函數(shù)呢?比如:

func async(callback: String->Void){}

這就是一個異步函數(shù),它的簽名是 (String->Void)->Void的形式。那么我們首先將形如
(T->Void)->Void的函數(shù)封裝起來:

struct Async<T>{ let asyncFunction:(T->Void)->Void}

接下來,我們觀察Optional和Array的flatMap函數(shù):
//Optional 的 flatMap函數(shù)定義

/// Returns `nil` if `self` is nil, `f(self!)` otherwise.
    @warn_unused_result
public func flatMap<U>(@noescape f: (Wrapped) throws -> U?) rethrows -> U?

如果不知道@noescape,throws,rethrows,去掉無妨閱讀,比如,這樣理解
public func flatMap<U>( f: (Wrapped) throws -> U?) -> U?

是不是更加清晰了
//Array 的 flatMap函數(shù)定義

extension SequenceType {
    /// Return an `Array` containing the non-nil results of mapping
    /// `transform` over `self`.
    ///
    /// - Complexity: O(*M* + *N*), where *M* is the length of `self`
    ///   and *N* is the length of the result.
    @warn_unused_result
    public func flatMap<T>(@noescape transform: (Self.Generator.Element) throws -> T?) rethrows -> [T]
}

同理:去掉@noescape等等干擾

public func flatMap<T>(transform: (item) -> T?) -> [T]
他們的函數(shù)簽名幾乎一模一樣:

flatMap<U>(transform:T -> M<U>) -> M<U>  //其中M<U>表示Array<U>或者Optional<U>

那么,我們的flatMap也應該是這個樣子!

struct Async<T>{ 
let asyncFunction:(T->Void)->Void 
func flatMap<U>(transform: T->Async<U>)->Async<U>{ 
//fill this function 
}}

剩下的事情,就只是將這個函數(shù)體按照前面整理出的“然后根據(jù)上一步的數(shù)據(jù),在transform中計算出新的Async,進行補玩就OK了。

具體補完的代碼我就不在這里寫出來了,如果實在還寫不出,可以去參考傅若愚GitHub上面找一個Demo,但我更建議你嘗試著自己將它補完。

------最后,誰提Monad我跟誰急!------ 這個是@傅若愚說的,monad只是一個概念而已,他的意思應該是指,我們不要丟下更加重要的抽象計算過程不顧,而去追逐一個對函數(shù)式編程思想不太重要的概念。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容