13: 喬喬 Erasure

weak self podcast

線上收聽與節目筆記

又是歡樂的一集!今天聊起 #weakself挑戰賽的 Type Erasure 與 Opaque Return Type。不過,怎麼某人的聲音到後半段就 erased 了!?

開場

我恨 PAT

  • PAT 指的是 Protocol with Associated Type,如下:
// 有 associatedtype 的 protocol
public protocol IteratorProtocol {
  associatedtype Element
  mutating func next() -> Element?
}

// 有 Self 的 protocol, Hashable 的 Self 來自所繼承的 Equatable
public protocol Hashable: Equatable {
  var hashValue: Int { get }
  func hash(into hasher: inout Hasher)
  func _rawHashValue(seed: Int) -> Int
}

public protocol Equatable {
  static func == (lhs: Self, rhs: Self) -> Bool
}

你是忘記還是害怕想起?

compile error

// 以下寫法都會被 compiler 抱怨

// 有 Self
func generateDictionaryKey() -> Hashable {
  return 8
}

// 有 associatedtype
func getIterator() -> IteratorProtocol {
    return ["1", "2", "3"].makeIterator()
}
 
  • PAT 三不能:
    • 不能做為變數型別用
    • 不能做為 function 的 return
    • 不能放在 Collection 裡

Type Erasure 為何而生

PAT 難搞的地方有兩種:

  1. Self 的 Protocol:Hashable
  2. associatedtype 的 Protocol:IteratorProtocol

對應的做法:

  • AnyHashable:黑魔法 C++
  • AnyHashable 有興趣請右轉
  • AnyIterator 用 generic struct 解決,如下:
// 利用 init with closure 來自動代入 `Element` 真實的型別,而不是直接用 <某型別> 的方式來指定
// 先宣告一個 generic struct
struct MyAnyIterator<Element> {
    // 內部使用的 Box ,裡面就是把 closure 存起來,本身也滿足  IteratorProtocol
    private class AnyIteratorBox<Element>: IteratorProtocol {
        typealias Base = () -> Element?
        private var _base: Base

        init(_ base: @escaping Base) {
            self._base = base
        }

        func next() -> Element? {
            return _base()
        }
    }

    // 把 closure 包起來的 box ( 簡單一點也可讓 box 就是個 closure type 直接存 closure)
    private let box: AnyIteratorBox<Element>
    init(_ body: @escaping () -> Element?) {
        self.box = AnyIteratorBox(body)
    }
}

// 再讓本身滿足 IteratorProtocol
extension MyAnyIterator: IteratorProtocol {
    mutating func next() -> Element? {
        return box.next()
    }
}

// Fibonacci iterator
// 可以看到我們仍然要在外部定義好 closure 與它的 return type 來決定 Element
var state = (0, 1)
var myIterator = MyAnyIterator { () -> Int in
    let upcomingNumber = state.0
    state = (state.1, state.0 + state.1)
    return upcomingNumber
}

// Xcode 按 option + 左鍵會看到 myIterator 是 MyAnyIterator<Int>
print(myIterator.next()) // 0
print(myIterator.next()) // 1
print(myIterator.next()) // 1
print(myIterator.next()) // 2
print(myIterator.next()) // 3
print(myIterator.next()) // 5

Opaque Return Type

  • weakself 的中文名稱是個 Opaque Type (Caller 決定 generic 是什麼)
  • Opaque Return Type 則由 Callee 決定 generic 是什麼
  • 避免 generiception
public typealias LazyCompactMapCollection<Elements, ElementOfResult>
  -> <C: Collection> C where C.Element == ElementOfResult
  = LazyMapSequence<LazyFilterSequence<LazyMapSequence<Elements, ElementOfResult?>>, ElementOfResult>
  • 也是對於 Type Erasure 的一種根本性解決手法
// 想像以下 function 定義在某一個 module 裡,外部無法決定 Element 是什麼
// 此 func 你可以呼叫但裡面實作看不到
func getIterator() -> some IteratorProtocol {
    var state = (0, 1)
    return MyAnyIterator { () -> Int in
        let upcomingNumber = state.0
        state = (state.1, state.0 + state.1)
        return upcomingNumber
    }
}

// 以下是我們在外部呼叫,在外面呼叫的人只知道它是 some IteratorProtocol
// 從 Xcode 的提示只知道 someIterator 型別是 `some IteratorProtocol`
var someIterator = getIterator()

print(someIterator.next())
print(someIterator.next())
print(someIterator.next())
print(someIterator.next())
print(someIterator.next())
print(someIterator.next())
 

那第三個問題,不能把 PAT 放入 collection 呢?

抱歉,還無法🤷‍♂️

更多 weak self


weak self podcast logo

weak self podcast

集結弱弱的我,也可以組成強大的社群。在 iOS 開發圈打滾的台灣工程師,專為 Apple & iOS 開發者而製作的 Podcast。主持人一三波肥。內容涵蓋了 iOS 與 Apple 開發圈的新知討論、踩坑經驗、人物採訪、社群活動、職場生活。

回首頁聽眾朋友怎麼說?weak self 歷史

線上收聽

可使用 Listen Notes 網站。

訂閱方式

weak self 支援任何 podcast 播放軟體,只要訂閱我們的 RSS 網址即可。

Listen on Apple Podcasts Listen on Pocket Casts Listen on Spotify

贊助 weak self

謝謝,請見說明