1. 程式人生 > >Swift 柯裏化

Swift 柯裏化

調試 自己 修改 擴展性 log 初始 執行 proto all

前言

  • 由於柯裏化在業務層的應用較少,所以從 Swift 3.0 開始移除了柯裏化的用法,但是 Swift 的很多底層特性是使用柯裏化來表達的。

1、柯裏化

1.1 柯裏化簡介

  • 柯裏化(Currying),又稱部分求值(Partial Evaluation),是一種函數式編程思想,就是把接受多個參數的函數轉換成接收一個單一參數(最初函數的第一個參數)的函數,並且返回一個接受余下參數的新函數技術。

  • uncurried: 普通函數

    // 接收多個參數的函數(與類相關的函數,統稱為方法,但是這裏就直接說函數了,方便理解)
    func add(a: Int, b: Int, c: Int) -> Int {
    
        print("\(a) + \(b) + \(c)")
        return a + b + c
    }
  • curried: 柯裏化函數

    // 柯裏化函數,Swift 3.0 之前支持這樣的語法,可以直接寫
    func addCur(a: Int)(b: Int)(c: Int) -> Int {
    
        println("\(a) + \(b) + \(c)")
        return a + b + c
    }

1.2 如何定義柯裏化函數

  • 定義柯裏化函數

    func function name (parameters)(parameters) -> return type {
    
        statements
    }

1.3 柯裏化函數實現原理

  • uncurried: 普通函數

    class Currying {
    
        // 接收多個參數的函數
        func add(a: Int, b: Int, c: Int) -> Int {
    
            print("\(a) + \(b) + \(c)")
            return a + b + c
        }
    }
  • 系統自帶的柯裏化函數

    class Currying {
    
        func addCur(a: Int)(b: Int)(c: Int) -> Int {
    
            print("\(a) + \(b) + \(c)")
            return a + b + c
        }
    }
  • 手動實現柯裏化函數

    • 把上面的函數轉換為柯裏化函數,首先轉成接收第一個參數 a,並且返回接收余下第一個參數 b 的新函數(采用閉包).

    • 這裏為什麽能使用參數 a、b、c ?

      • 利用閉包的值捕獲特性,即使這些值作用域不在了,也可以捕獲到他們的值。
      • 閉包會自動判斷捕獲的值是值拷貝還是值引用,如果修改了,就是值引用,否則值拷貝。
      • 註意只有在閉包中才可以,a、b、c 都在閉包中。
      class Currying {
      
          // (a: Int)                    : 參數
          // (b: Int) -> (c: Int) -> Int : 函數返回值(一個接收參數 b 的函數,並且這個函數又返回一個接收參數 c,
          //                               返回值為 Int 類型的函數)
      
          // 定義一個接收參數 a,並且返回一個接收參數 b 的函數,並且這個函數又返回一個接收參數 c,返回值為 Int 類型的函數。
          func add(a: Int) -> (b: Int) -> (c: Int) -> Int {
      
              // 返回一個接收參數 b 的函數,並且這個函數又返回一個接收參數 c,返回值為 Int 類型的函數
              return { (b:Int) -> (c: Int) -> Int in
      
                  // 返回一個接收余下第一個參數 c,並且返回結果為 Int 類型的函數
                  return { (c: Int) -> Int in
      
                      return a + b + c;
                  }
              }
          }
      }

1.4 如何調用柯裏化函數

  • 創建柯裏化類的實例

    var curryInstance = Currying()
  • 手動實現的柯裏化函數調用

    var result: Int = curryInstance.add(a: 10)(b: 20)(c: 30)
    • 可能很多人都是第一次看這樣的調用,感覺有點不可思議。
    • 讓我們回顧下 OC 創建對象 [[Person alloc] init],這種寫法應該都見過吧,就是一下發送了兩個消息,alloc 返回一個實例,再用實例調用 init 初始化,上面也是一樣,一下調用多個函數,每次調用都會返回一個函數,然後再次調用這個返回的函數。
  • 手動實現的柯裏化函數拆解調用

    • curryInstance.add(a: 10) 調用一個接收參數 a,並且返回一個接收參數 b 的函數,並且這個函數又返回一個接收參數 c,返回值為 Int 類型的函數。

      // functionB: 一個接收參數 b 的函數,並且這個函數又返回一個接收參數 c,返回值為 Int 類型的函數
      let functionB = curryInstance.add(a: 10)
    • functionB(b: 20) 調用一個接收參數 b 的函數,並且這個函數又返回一個接收參數 c,返回值為 Int 類型的函數。

      // functionC: 一個接收參數 c,返回值為 Int 類型的函數
      let functionC = functionB(b: 20)
    • functionC(c: 30) 調用一個接收參數 c,返回值為 Int 類型的函數。

      // result: 函數的返回值
      var result: Int = functionC(c: 30);
  • 系統的柯裏化函數調用

    var result: Int = curryInstance.addCur(a: 10)(b: 20)(c: 30)
  • 系統的柯裏化函數拆解調用

    • curryInstance.addCur(a: 10) 調用一個接收參數 a,並且返回一個接收參數 b 的函數,並且這個函數又返回一個接收參數 c,返回值為 Int 類型的函數。

      // Swift是強類型語言,這裏沒有報錯,說明調用系統柯裏化函數返回的類型和手動的 functionB 類型一致
      // functionB: 一個接收參數 b 的函數,並且這個函數又返回一個接收參數 c,返回值為 Int 類型的函數
      functionB = curryInstance.addCur(a: 10)
    • functionB(b: 20) 調用一個接收參數 b 的函數,並且這個函數又返回一個接收參數 c,返回值為 Int 類型的函數。

      // functionC: 一個接收參數c,返回值為Int類型的函數
      functionC = functionB(b: 20)
    • functionC(c: 30) 調用一個接收參數 c,返回值為 Int 類型的函數。

      // result: 函數的返回值
      result = functionC(c: 30)
      
      // 打印 60,60,60 說明手動實現的柯裏化函數,和系統的一樣。
      print("\(r), \(res), \(result)")

1.5 柯裏化函數使用註意

  • 必須按照參數的定義順序來調用柯裏化函數,否則就會報錯。

  • 柯裏化函數的函數體只會執行一次,只會在調用完最後一個參數的時候執行柯裏化函數體。

    • 以下調用 functionC(c: 30) 才會執行函數體,這個可以自己斷點調試。

      // curried: 柯裏化函數
      func addCur(a: Int)(b: Int)(c: Int) -> Int {
      
          println("\(a) + \(b) + \(c)")
          return a + b + c
      }
      
      // 創建柯裏化類的實例
      var curryInstance = Currying()
      
      // 不會執行柯裏化函數體
      functionB = curryInstance.addCur(a: 10)
      
      // 不會執行柯裏化函數體
      functionC = functionB(b: 20)
      
      // 執行柯裏化函數體
      result = functionC(c: 30)

1.6 柯裏化函數的好處

  • 這裏就需要了解函數式編程思想了,柯裏化函數就是運用了函數式編程思想,推薦看這篇文章函數式編程初探。

  • 特點

    • 1)只用 “表達式” (表達式: 單純的運算過程,總是有返回值),不用 “語句” (語句: 執行某種操作,沒有返回值)。
    • 2)不修改值,只返回新值。
  • 好處

    • 1)代碼簡潔。
    • 2)提高代碼復用性。
    • 3)代碼管理方便,相互之間不依賴,每個函數都是一個獨立的模塊,很容易進行單元測試。
    • 4)易於 “並發編程”,因為不修改變量的值,都是返回新值。

1.7 柯裏化函數的實用性

  • 實用性一:復用性

    • 需求 1:地圖類產品,很多界面都有相同的功能模塊,比如搜索框。

      • 我們可以利用柯裏化函數,來組裝界面,把界面分成一個個小模塊,這樣其他界面有相同的模塊,直接運用模塊代碼,去重新組裝下就好了。
  • 實用性二:延遲性

    • 柯裏化函數代碼需要前面的方法調用完成之後,才會來到柯裏化函數代碼中。

    • 需求 2:閱讀類產品,一個界面的顯示,依賴於數據,需要加載完數據之後,才能判斷界面顯示。

      • 這時候也可以利用柯裏化函數,來組裝界面,把各個模塊加載數據的方法抽出來,等全部加載完成,再去執行柯裏化函數,柯裏化函數主要實現界面的組裝。
  • 舉例說明

    // 組合接口
    // 為什麽要定義接口,為了程序的擴展性,以後只需要在接口中添加對應的組合方法就好了。
    protocol CombineUI {
    
        func combine(top: () -> ())(bottom: () -> ())()
    }
    
    // 定義一個界面類,遵守組合接口
    class UI: CombineUI {
    
        func combine(top: () -> ())(bottom: () -> ())() {
    
            // 搭建頂部
            top()
    
            // 搭建底部
            bottom()
        }
    }

2、Swift 中實例方法的柯裏化

  • Swift 中實例方法就是一個柯裏化函數。

2.1 Swift 中實例方法的柯裏化調用

  • Swift 中實例方法的柯裏化調用

    • 示例結構體

      struct Example {
      
          var internalStr = ""
      
          func combine(externalStr: String) {
              print(internalStr + " " + externalStr)
          }
      }
    • 調用實例方法的常用格式

      let example = Example(internalStr: "hello")
      
      example.combine(externalStr: "word")            // hello word
    • 調用實例方法的柯裏化格式

      let example = Example(internalStr: "hello")
      
      Example.combine(example)(externalStr: "word")   // hello word

Swift 柯裏化