Swift 5 新特性:Result<Success, Failure> 類型闪水、Monad 和 Functor

在Swift 5 之前喉祭,拋出和處理錯誤的標準做法是使用 throws try catch, 異步錯誤使用的是 completion: @escaping (ResponseType?, ErrorType?) -> Void 的形式進行回調(diào)爆办。 然而一些第三方庫已經(jīng)發(fā)現(xiàn)了缺乏一個泛型 Result<Success,Failure> 類型的不方便难咕,紛紛實現(xiàn)了自己的 Result 類型以及相關的 Monad 和Functor 特性。

Swift 5 盡管仍正在開發(fā)中距辆,我們看到 Result<Success, Failure> 類型已經(jīng)被加入到標準庫中去余佃,實現(xiàn)這個類型并不需要 Swift 5 的其他特性,我們使用 Swift 4 就可以自己實現(xiàn)跨算,我們一起來學習一下爆土。

1. 類型定義

public enum Result<Success, Failure: Swift.Error> {
  case success(Success)  
  case failure(Failure)
}

以上是該類型的定義,首先它是個枚舉類型诸蚕,有兩種值分別代表成功和失敳绞啤氧猬;其次它有兩個泛型類型參數(shù),分別代表成功的值的類型以及錯誤類型坏瘩;錯誤類型有一個類型約束盅抚,它必須實現(xiàn) Swift.Error 協(xié)議。

盡管這個類型設計看起來很簡單倔矾,但它也是經(jīng)過慎重考慮的妄均,簡單討論一下其他兩種類似的設計。

public enum Result<Success, Failure> {
    case success(Success)
    case failure(Failure)
}

上面這個設計取消了錯誤類型的約束哪自,它有可能變相鼓勵用一個非 Swift.Error 的類型代表錯誤丰包,比如 String 類型,這與 Swift 的現(xiàn)有設計背道而馳壤巷。

public enum Result<Success> {
    case success(Success)
    case failure(Swift.Error)
}

第三種設計其實在很多第三方庫中出現(xiàn)邑彪,對于failure 的情況僅用了 Swift.Error 類型進行約束。它的缺點是在實例化 Result 類型時候若用的是強類型的類型胧华,會丟掉那個具體的強類型信息寄症。

2. 異步回調(diào)的應用

比如以下這個URLSession的 dataTask 方法

func dataTask(with url: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask

在 Swift 5 中可以考慮被設計成:

func dataTask(with url: URL, completionHandler: @escaping (Result<(URLResponse, Data), Error>) -> Void) -> URLSessionDataTask

可以如下應用:獲取到結果后,解包撑柔,根據(jù)成功或失敗走不同路徑瘸爽。

URLSession.shared.dataTask(with: url) { (result: Result<(URLResponse, Data), Error>) in
    case .success(let response):
        handleResponse(response.0, data: response.1)
    case .failure(let error):
        handleError(error)
    }
}

3. 同步 throws 函數(shù)的應用

在很多時候您访,我們并不喜歡在調(diào)用 throws 函數(shù)的時候直接處理 try catch铅忿,而是不打斷控制流地將結果默默記錄下來,因此這里包裝類型 Result 也能派上用處灵汪。它提供了如下這個初始化函數(shù)檀训。

extension Result where Failure == Swift.Error {
  public init(catching body: () throws -> Success) {
    do {
      self = .success(try body())
    } catch {
      self = .failure(error)
    }
  }
}

我們可以這樣使用:

let config = Result {try String(contentsOfFile: configuration) }
// do something with config later

說到這里,大家可能會有個疑問享言,Result 類型那么方便峻凫,在設計方法的時候直接返回 Result,而不使用 throws 可不可以览露?

簡單來說荧琼,不推薦。這是個設計問題差牛,用Result的形式也會有不方便的情況命锄。
第一個代價是:try catch 控制流不能直接使用了
第二個代價是:這跟 rethrows 函數(shù)設計也不默認匹配

throws 代表的是控制流語法糖,而 Result 代表的是狀態(tài)偏化。這兩者很多情況下是可以轉換的脐恩,上面說了 throws 轉成 Result,下面看一下 Result 如何轉成 throws侦讨,Resultget 方法:

  public func get() throws -> Success {
    switch self {
    case let .success(success):
      return success
    case let .failure(failure):
      throw failure
    }
  }

throws 或者是 返回Result 這兩種方式都是可行的驶冒,所以標準庫可能才猶猶豫豫那么久才決定加進去苟翻,因為帶來的可能是設計風格的不一致的問題。

一般情況下:推薦設計的時候使用 throws骗污,在使用需要的時候轉成狀態(tài) Result崇猫。

4. Functor 和 Monad

Functor 和 Monad 都是函數(shù)式編程的概念。簡單來說身堡,Functor意味著實現(xiàn)了 map 方法邓尤,而Monad意味著實現(xiàn)了flatMap。因此 Optional 類型和 Array 類型都既是 Functor 又是 Monad贴谎,與Result一樣汞扎,它們都是一種復合類型,或者叫 Wrapper 類型擅这。

map 方法:傳入的 transform 函數(shù)的 入?yún)⑹?Wrapped 類型澈魄,返回的是 Wrapped 類型
flatMap 方法:傳入的 transform 函數(shù)的 入?yún)⑹?Wrapped 類型,返回的是 Wrapper 類型

我們可以在這篇文章中 Swift 4.1 新特性 (2) Sequence.compactMap 可以找到關于 OptionalArraymap 仲翎、flatMap 函數(shù)的討論痹扇。

Result作為 Functor 和 Monad 類型有 map, mapError, flatMap, flatMapError 四個方法,實現(xiàn)如下:

public func map<NewSuccess>(
    _ transform: (Success) -> NewSuccess
  ) -> Result<NewSuccess, Failure> {
    switch self {
    case let .success(success):
      return .success(transform(success))
    case let .failure(failure):
      return .failure(failure)
    }
  }
  
  public func mapError<NewFailure>(
    _ transform: (Failure) -> NewFailure
  ) -> Result<Success, NewFailure> {
    switch self {
    case let .success(success):
      return .success(success)
    case let .failure(failure):
      return .failure(transform(failure))
    }
  }
  

  public func flatMap<NewSuccess>(
    _ transform: (Success) -> Result<NewSuccess, Failure>
  ) -> Result<NewSuccess, Failure> {
    switch self {
    case let .success(success):
      return transform(success)
    case let .failure(failure):
      return .failure(failure)
    }
  }
  
  public func flatMapError<NewFailure>(
    _ transform: (Failure) -> Result<Success, NewFailure>
  ) -> Result<Success, NewFailure> {
    switch self {
    case let .success(success):
      return .success(success)
    case let .failure(failure):
      return transform(failure)
    }
  }
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末溯香,一起剝皮案震驚了整個濱河市鲫构,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌玫坛,老刑警劉巖结笨,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異湿镀,居然都是意外死亡炕吸,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門勉痴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赫模,“玉大人,你說我怎么就攤上這事蒸矛∑俾蓿” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵雏掠,是天一觀的道長斩祭。 經(jīng)常有香客問我,道長磁玉,這世上最難降的妖魔是什么停忿? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮蚊伞,結果婚禮上席赂,老公的妹妹穿的比我還像新娘吮铭。我一直安慰自己,他們只是感情好颅停,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布谓晌。 她就那樣靜靜地躺著,像睡著了一般癞揉。 火紅的嫁衣襯著肌膚如雪纸肉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天喊熟,我揣著相機與錄音柏肪,去河邊找鬼。 笑死芥牌,一個胖子當著我的面吹牛烦味,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播壁拉,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼谬俄,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了弃理?” 一聲冷哼從身側響起溃论,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎痘昌,沒想到半個月后钥勋,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡控汉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年笔诵,在試婚紗的時候發(fā)現(xiàn)自己被綠了返吻。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片姑子。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖测僵,靈堂內(nèi)的尸體忽然破棺而出街佑,到底是詐尸還是另有隱情,我是刑警寧澤捍靠,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布沐旨,位于F島的核電站,受9級特大地震影響榨婆,放射性物質(zhì)發(fā)生泄漏磁携。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一良风、第九天 我趴在偏房一處隱蔽的房頂上張望谊迄。 院中可真熱鬧,春花似錦、人聲如沸察藐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽粮呢。三九已至婿失,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間啄寡,已是汗流浹背豪硅。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留挺物,地道東北人舟误。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像姻乓,于是被迫代替她去往敵國和親嵌溢。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355