Function Composition và Custom Operator

Custom operator

I. Giới thiệu

Chúng ta thường biết đến Swift là một ngôn ngữ hướng đối tượng, tuy nhiên trong một số trường hợp, ví dụ như xử lý logic, sẽ cần một cách viết khác để đoạn code nhìn clear hơn, và functional programming chính là một trong số đó.

Trong khuôn khổ bài viết này, tôi sẽ không đề cập đến tất cả tính chất của functional programming trong Swift mà chỉ nói về một phần khá quan trọng của nó là function composition và custom operator.

II. Nội dung

  1. Function Composition

Function composition được dùng khi chúng ta muốn gộp 2 hay nhiều function khác nhau vào thành một. Để hiểu rõ hơn, chúng ta hãy tìm hiểu ví dụ sau: Ta có một chuỗi a:

let a = "10,20,40,30,80,60"

Nhiệm vụ của chúng ta là từ a, ta có được một mảng các chuỗi có kí tự "$" đứng cuối cùng

Để làm điều này, ta cần thực hiện qua 2 bước:

  • Phân tách chuỗi a thành 1 mảng các chuỗi dạng số:
func extractElement(content: String) -> [String] {
    return content.componentsSeparatedByString(",")
}
  • Thêm dấu "$" vào sau từng phần tử của mảng
func formatWithCurrency(content: [String]) -> [String] {
    return content.flatMap{"\($0) $"}

Trong ví dụ trên, chúng ta dùng kết quả của hàm đầu tiên như là tham số cho hàm thứ hai, phương pháp này khá dài dòng. Chúng ta sẽ viết lại nó một cách "functional" như sau:

Đầu tiên sẽ định nghĩa lại loại hàm cho từng hàm trên:


typealias extractElement = (String) -> [String]

typealias formatWithCurrency = [String] -> [String]

Tiếp đến chúng ta sẽ viết một closure là tập hợp 2 functions trên:


let composedFunction = {data in return formatWithCurrency(extractElement(data))
}

Kết quả của chúng ta được tính như sau:

let results = composedFunction(a)
  1. Custom Operator

Ở phần trước, chúng ta đã tìm hiểu về function composition, tuy đã gọn gàng hơn cách viết riêng lẻ từng hàm, nhưng nếu nhìn vào đoạn code vẫn chưa thực sự clear. Sang phần này, chúng ta sẽ tìm hiểu về custom operator, là một cách clear code, giúp người đọc sau có thể hình dung dễ dàng flow của đoạn code chúng ta viết

Dạng tổng quát của việc custom operator như sau:

operatortype operator operatorName {}

Trong đó:

Operatortype gồm một trong 3 loại sau:

  • prefix( tiền tố)
  • infix(trung tố)
  • postfix(hậu tố)

Trong đó, chúng ta thường custom infix, ví dụ:

infix operator operatorName{ associativity left/right/none precedence}

Associativity được hiểu như thứ tự thực thi hàm. Ví dụ như trong số học, ta thực hiện các phép tính từ trái sang phải trong cùng một điều kiện về cấp bậc( nhân chia trước, cộng trừ sau)(precendence)

Áp dụng vào function composition ở trên, ta khởi tạo một custom operator và function như sau:


infix operator |> {associativity left}

func |> (T, V) (f: T -> V, g: V -> V)  -> T -> V {
    return {x in g(f(x)}
}

Hàm |> có hai tham số f và g, mỗi tham số là một function, trong đó f có tham số là T, trả về V, g lấy tham số là V, trả về V. Hàm |> trả về một closure dạng T -> V.

Sau khi đã define hàm |>, ta áp dụng vào function composition của chúng ta ở phần một:

let composeWithCustomOperator = extractElement |> formatWithCurrency

Vì operatortype có associativity là left nên ta hiểu thứ tự thực hiện theo chiều mũi tên từ trái sang phải, tức là thực hiện extractElement xong rồi mới thực hiện formatWithCurrency, và khi lấy kết quả ta chỉ cần gọi đơn giản như sau:

let result = composeWithCustomOperator(a)

Vậy là chỉ với một chút custom nhỏ của operator, chúng ta đã có thể clear được một đoạn code logic có yêu cầu về thứ tự, thuận tiện cho chính người viết code cũng như người đọc code về sau.

Hi vọng với bài viết này, các bạn sẽ có cái nhìn khác về ngôn ngữ Swift mà chúng ta code hàng ngày để đem đến những đoạn code gọn gàng, rành mạch và nhất quán.

Happy coding!