Swift Generics

Bài dịch từ https://www.raywenderlich.com/154371/swift-generics-tutorial-getting-started

Mở đầu

Hãy nhìn vào 2 đoạn code dưới đây

func add(x: Int, y: Int) -> Int {
    return x + y
}
func add(x: Double, y: Double) -> Double {
    return x + y
}

Rõ ràng 2 function này đều làm chung 1 nhiệm vụ - cộng x và y, tuy nhiên, chỉ kiểu giá trị truyền vào của chúng khác nhau (kiểu giá trị trả về khác nhau) mà chúng ta phải tạo ra 2 function riêng biệt thì thật là lãng phí, và sau này, để support cho cả cộng Float, UInt ... bạn phải tạo thêm các function khác. Để support việc này, Swift cung cấp cho ta 1 công cụ, đó là Generics

Cùng tìm hiểu kỹ hơn về Generics qua các ví dụ dưới đây nhé

Viết một Generic Data Struct

struct Queue<Element> {
}

Đoạn code trên là 1 ví dụ về 1 Generic Data Struct và bạn có thể hiểu nó 1 cách đơn giản : Queue là 1 generic với type Element, ví dụ Queue<String>, Queue<Int>

struct Queue<Element> {
    fileprivate var elements: [Element] = []
    mutating func enqueue(newElement: Element) {
        elements.append(newElement)
    }
    
    mutating func dequeue() -> Element? {
        guard !elements.isEmpty else { return nil }
        return elements.remove(at: 0)
    }
}

Như bạn đã thấy, chúng ta có thể sử dụng Element như là 1 type và bạn có thể sử dụng nó thay thế cho Int, String, Double ... như ví dụ dưới đây

var q = Queue<Int>()

q.enqueue(newElement: 4)
q.enqueue(newElement: 2)

q.dequeue()
q.dequeue()
q.dequeue()
q.dequeue()

Viết một Generic Function

việc khai báo cho 1 function cũng đơn giản và tương tự như Struct, chúng ta có thể hình dung đơn giản nó qua ví dụ bên dưới

func pairs<Key, Value>(from dictionary: [Key: Value]) -> [(Key, Value)] {
    return Array(dictionary)
}
let somePairs = pairs(from: ["minimum": 199, "maximum": 299]) 
// result is [("maximum", 299), ("minimum", 199)]

let morePairs = pairs(from: [1: "Swift", 2: "Generics", 3: "Rule"]) 
// result is [(2, "Generics"), (3, "Rule"), (1, "Swift")]```

## Constraining a Generic Type ##

Cùng bắt đầu với ví dụ tìm giá trị giữa của array dưới đây
```Swift
func mid<T>(array: [T]) -> T? {
  guard !array.isEmpty else { return nil }
  return array.sorted()[(array.count - 1) / 2]
}

Compiler sẽ báo lỗi ở chỗ gọi sorted(). Để hàm sorted() có thể hoạt động, các phần tử của array phải Comparable. Rõ ràng, trong trường hợp này, các phần từ của array sẽ phải bị giới hạn (phải Comparable). Chúng ta sẽ sửa lại 1 chút

func mid<T: Comparable>(array: [T]) -> T? {
    guard !array.isEmpty else {
        return nil
    }
    return array.sorted()[(array.count - 1) / 2]
}

và bây giờ, chúng ta có thể thoải mái sử dụng function trên với array của các Comparable element

mid(array: ["abe", "abd", "cbd"])
mid(array: [1,3,4,2,6,3])

Quay trở lại bài toán ban đầu,

func add(x: Int, y: Int) -> Int {
    return x + y
}
func add(x: Double, y: Double) -> Double {
    return x + y
}

Khi chúng ta gộp chung 2 function trên và sử dụng Generic

func add<T>(x: T, y: T) -> T {
    return x + y
}

chúng ta sẽ nhận được 1 thông báo lỗi rằng toán tử + chỉ support cho 1 số kiểu dữ liệu nhất định chứ k phải tất cả. Để giải quyết vấn đề này, đơn giản chỉ cần sử dụng luôn cái mà chúng ta đã nói ở trên

protocol Sumable {
    static func + (lhs: Self, rhs: Self) -> Self
}

extension Int: Sumable {
}

extension Double: Sumable {
}

extension String: Sumable {
}

func add<T: Sumable>(x: T, y: T) -> T {
    return x + y
}

add(x: 1, y: 1)
add(x: 1.0, y: 1.0)
add(x: "Make it ", y: "Awesome")

Enumerations With Associated Values

Nếu đã sử dụng Alamofire chắc hẳn bạn đều biết đến những dòng code này

public struct DataResponse<Value> {
    public let result: Result<Value>
}

public enum Result<Value> {
    case success(Value)
    case failure(Error)
}

Đó là 1 cách biểu diễn thường gặp của “functional” error-handlingresult enum. Nó là 1 enum generic với 2 case trả về, trả về value hoặc thông báo lỗi.

Tương tự như ví dụ đó, chúng ta có thể làm 1 ví dụ

enum MathError: Error {
    case divisionByZero
}

func divide(_ x: Int, by y: Int) -> Result<Int> {
    guard y != 0 else {
        return .failure(MathError.divisionByZero)
    }
    return .success(x / y)
}

Kết

Trên đây là 1 số ví dụ đơn giản để bạn hiểu hơn về Generic trong Swift. Bạn có thể download phần source code ở đây và tìm hiểu thêm về tại phần Generic trong tài liệu của Apple