Contents
Chào mừng bạn đến với Fx Studio. Hôm nay, chúng ta đi tới một chủ đề cũ nhưng chắc đối với nhiều bạn thì sẽ là rất mới. Đó là Higher Order Function. Chúng ta sẽ từng bước tìm hiểu chúng là gì nhóe. Hãy giữ cho não bạn an toàn và …
Bắt đầu thôi!
Chuẩn bị
Đây là một chủ đề nâng cao trong Swift, nên điều bạn cần là đã thông não được hết các kiến thức cơ bản trong Swift. Và chúng sẽ liên quan nhiều tới Function trong Swift. Để giúp bạn hồi tưởng lại kiến thức cơ bản, thì có thể đọc qua các bài viết sau:
Vì đây là kiến thức Swift thuần túy, nên bạn chỉ cần dùng Playground cho demo là được rồi. Chúng ta sẽ in kết quả ra console là chính nhóe.
Như vậy, hành trang của bạn đã đủ rồi và ta tiếp tục hành trình thôi!
Higher order function
Đầu tiên, chúng ta sẽ tìm hiểu về khái niệm Higher Order Function là như thế nào.
Với Higher-Order Function, tham số kia có thể làm hàm, và kết quả trả về lại là một hàm khác.
Một Higher Order Function sẽ có những đặc tính cơ bản sau:
- Nhận một hoặc nhiều tham số
- Đối số truyền cho các tham số có thể là giá trị, đối tượng hoặc function khác
- Trả về một function dưới dạng kết quả của nó
Tạm chấp nhận cách hiểu như vậy nhóe. Hay giang hồ có khái niệm đơn giản hơn nữa là:
Hàm ăn hàm, rồi đẻ ra hàm.
Và tại sao chúng ta phải tìm hiểu về Higher order function làm gì? Để trả lời, thì mình lại liệt kê cho bạn vài lý do chính sau:
- Bạn đã sử dụng nó nhiều lần rồi, nhưng mà bạn không biết gọi chúng là gì thôi
- Một trong những thành phần cơ bản cấu thành nên kiểu lập trình Functional Programming
- Khi sử dụng chúng sẽ làm cho việc coding của bạn đơn giản đi rất nhiều, dễ đọc và dễ hiểu
Còn một lý do quan trọng nữa đó là:
Dùng nó để khè /làm le/nỗ … với đám bạn cùng team bạn. Ahihi!
Lối suy nghĩ cho coding
Chúng ta cùng nhau xem giữa 2 cách viết mã (truyền thống và gần với higher order function) sẽ khác nhau như thế nào. Rồi từ bạn sẽ thấy được ưu và nhược điểm của chúng.
Cách truyền thống
Chúng ta có một đoạn code ví dụ cho cách viết truyền thống nhóe.
protocol NumberFilterProtocol { func filter(_ number: Int) -> Int } class NumberPrinter { var filter: NumberFilterProtocol init(filter: NumberFilterProtocol) { self.filter = filter } func printNumbers(_ array: [Int]) { for number in array { let result = filter.filter(number) print(result) } } }
Bắt đầu, ta sẽ định nghĩa 1 class dùng cho việc in các số Int, tên là NumberPrinter. Với function printNumbers
sẽ in lần lượt các phần tử trong array. Tuy nhiên, đối tượng NumberPrinter lại cần 1 đối tượng filter
để biến đổi các phần tử trước khi in ra. filter
là kiểu NumberFilterProtocol.
Để sử dụng, bạn cần phải triển khai Protocol NumberFilterProtocol trước đã. Xem ví dụ nhóe!
class DoubleFilter: NumberFilterProtocol { func filter(_ number: Int) -> Int { number * 2 } }
Lớp DoubleFilter kế thừa lại NumberFilterProtocol, với một quy luật đơn giản là nhân đôi number
mà thôi. Chúng ta ví dụ cách sử dụng tiếp nhóe.
let filter = DoubleFilter() let numberPrinter = NumberPrinter(filter: filter) let array = [1, 8, 6, 34, 99, 0, 186] numberPrinter.printNumbers(array)
Trong đó:
- Tạo các đối tượng cần thiết
filter
vànumberPrinter
- Khai báo mãng array
- Sử dụng
numberPrinter.printNumbers(array)
in ra kết quả
Qua trên, bạn thấy được là là cách viết code truyền thống. Vẫn là kiểu hướng đối tượng được dùng làm tham số cho các hàm xử lý mà thôi.
First order function
Các hàm first class là các hàm được coi như một đối tượng (hoặc có thể gán cho một biến).
Vẫn chưa phải là Higher order function nhóe. Chúng ta sẽ xem nó là một bước đệm trước. Với cách truyền thống, bạn sẽ vẫn là cách dùng các đối tượng truyền vào để xử lý. Ta sẽ đổi mới cách viết một tí. Nó được gọi là First order function
Xem qua ví dụ tiếp nhóe!
extension Array where Element == Int { func printFilter(filter: NumberFilterProtocol) { let numberPrinter = NumberPrinter(filter: filter) numberPrinter.printNumbers(self) } }
Bạn sẽ tạo ra một extension cho kiểu Array, nhưng giới hạn với Int Array mà thôi. Sau đó, bạn sẽ tạo thêm một function printFilter
để in các phần tử của chính nó với filter
được cung cấp.
Cách sử dụng như sau:
array.printFilter(filter: filter)
Về bản chất, nó cũng không khác cách trên là bao nhiêu. Nhưng thay vì dùng array làm tham số, thì chúng ta sử dụng nó làm chủ thể để tương tác. Với các operation khác nhau, thì sẽ cho ra các kiểu format khác nhau.
Vẫn có một điều là chúng ta vẫn dùng 1 đối tượng filter
cho toàn bộ. Điều này thì không sai, nhưng mà cũng hơi khó chịu một tí. Ta tiếp tục tìm hiểu nữa nha.
Higher order function đầu tiên
Thay vì sử dụng các đối tượng để làm tham số. Lần này, ta sẽ áp dụng các đặc tính cơ bản của ở trên của Higher Order Function cho ví dụ vừa rồi. Mục đích tạo ra một Higher Order Function của riêng bạn nhóe.
Tiếp tục, bạn xem qua ví dụ sau:
func doubleFilter(_ number: Int) -> Int { number * 2 } extension Array where Element == Int { func printNumbers(filter: (Int) -> Int) { for number in self { let result = filter(number) print(result) } } }
Trong đó:
- Tạo một function là
doubleFitler
thay cho chức năng của một đối tượng DoubleFilter ở trên - Vẫn tạo ra một extension cho một Int Array
Bạn sẽ thấy trong function chính để in giá trị printNumber
, thì đã ra bỏ đi các đối tương như các ví dụ trên (numberPrinter & doubleFilter). Và đặc biệt, bạn có tham số của filter
cho hàm là một function (hay một closure).
Việc sử dụng thì sẽ như thế này.
let array = [1, 8, 6, 34, 99, 0, 186] array.printNumbers(filter: doubleFilter(_:))
Như vậy, từ extension trên thì ta đã có một Higher Order Function đầu tiên rồi nhóe. Để cho nó hoạt động được, thì bạn cần truyền cho nó một function khác (không sử dụng các thuộc tính hay đối tượng). Miễn là function bạn truyền vào đúng theo kiểu (Int) -> Int là được.
Các hàm higher order là các hàm nhận ít nhất một hàm hạng nhất làm tham số hoặc trả về ít nhất một hàm first class.
Vai trò của Closure
Sử dụng
Để tham số của của một function là một function khác, thì lựa chọn tối ưu nhất khi khai báo function chính là Closure. Nó được sử dụng ở ví dụ trên rồi. Ta sẽ thực thi lại ví dụ trên với việc sử dụng Closure thay cho triệu hồi function nhóe.
array.printNumbers { number in number * 2 }
Vẫn áp dụng quy luật nhân đôi vào. Bạn sẽ có kết quả tương tự như ví dụ trên. Nhưng sẽ không cần dùng tới một function khác cho phần tham số. Đó cũng là thế mạnh của closure, khi bạn chỉ cần quan tâm tới kiểu của function/closure mà thôi. Với ví dụ trên sẽ là (Int) -> Int.
Ta lại có một cách sử dụng khác, là vẫn bao đóng { }
của closure, nhưng lại sử dụng một function khác trong đó.
array.printNumbers { doubleFilter($0) }
Lúc này, bạn đã thấy được những hình bóng quen thuộc chưa nào. Với các ký tự $0 hay $1 … Nhưng bạn cần nhớ, đây là bản chất là closure nhóe, không phải là function. Function chỉ được gọi trong closure (dùng làm tham số cho function lớn hơn).
Vài trò
Hoặc bạn suy nghĩ: “nếu như chúng ta triệu hồi một function trong closure có nhiều hơn 1 tham số thì sẽ như thế nào”. Ví dụ như sau:
aHigherOrderFunction { someOperation($0, "a constant") }
Bạn hãy yên tâm vì Swift sẽ hỗ trợ ta một thứ nữa, gọi là Currying. Mình sẽ đề cập ở phần sau của bài viết. Nhưng chỉ cần function bạn triệu hồi có cùng kiểu dữ liệu trả về của closure là oke. Cho dù bạn có tới n tham số đi chăn nữa.
Tư tưởng, Swift hay Higher Order Function thì sẽ không quan tâm tới cách bạn làm như thế nào. Chỉ quan tâm tới kiểu dữ liệu bạn trả về. Bạn sẽ thấy trong Swift với hàm sort, bạn sẽ không biết được cách sắp xếp nào thực thi. Nhưng sẽ luôn nhận được một kết quả duy nhất mà thôi.
Ví dụ tóm tắt
Có thể tới được đây, chắc bạn cũng sẽ khá rối rồi. Do đó, mình sẽ đưa ra ví dụ để bạn có thể tóm tắt lại tư tưởng và cách hiểu của bạn cho Higher Order Function nhóe.
Không có Higher order function (cách truyền thống)
let arr1 = [1, 2, 3] var arr2: [Int] = [] for i in 0..<arr1.count { arr2.append(arr1[i] * 2) } print(arr2)
Bạn sẽ biến đổi arr1
thành arr2
bằng cách duyệt lần lượt các phần tử arr1
. Và gán bằng tay các phần tử vào arr2
Có Higher Order Function
let array1 = [1, 2, 3] var array2: [Int] = array1.map { $0 * 2 } print(array2)
Bạn sẽ có được trải nghiệm như trên, số dòng code ít đi nhiều. Các dùng cũng khá đơn giản. Với map là một Higher Order Function của Swift cung cấp. Cơ bản về bản chất thì cũng tương tự như cách chúng ta đã demo ở trên nhóe.
Higher Order Function chuẩn được Swift cung cấp
Đây là những Higher Order Fucntion mà bạn đã sử dụng (nhưng không biết chúng gọi là gì) mà Swift cung cấp cho cúng ta. Ta sẽ có:
-
- map
- compactMap
- flatMap
- filter
- reduce
- sorted
- split
Ta sẽ lướt nhanh qua các ví dụ cho các Higher Order Function này nhóe.
map
map là function được sử dụng nhiều nhất trong các array. Công dụng của nó là biến đổi object này thành object kia. Ví dụ nhóe:
var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] let newNumbers2 = numbers.map { $0 + $0 } print(newNumbers2)
Bạn sẽ nhận được một kết quả là một mãng mới newNumber2
. Các giá trị trong numbers
vẫn không bị ảnh hưởng. Chỉ là nó biến đổi các giá trị trong numbers
và gán vào mãng mới mà thôi.
Không làm thay đổi giá trị đầu vào cũng là một đặc tính của Functional Programming nhóe.
Và bạn có thể truyền đối số cho nó 1 function nào đó cũng được thay vì là closure. Mình cũng đã đề cập ở trên rồi nhóe.
compactMap
Với compactMap thì cũng tương tự như map, nhưng chúng ta có thêm xử lý các phần tử Optional. compactMap cũng được sử dụng để lọc ra giá trị nil
.
let arrayWithNil: [String?] = ["eleven" , nil , "demogorgon" , nil , "max" , nil , "lucus" , nil , "dustin"] let filteredNilArray = arrayWithNil.compactMap{$0} print(filteredNilArray) print ("Array with nil = \(arrayWithNil.count) and with out nil count = \(filteredNilArray.count)")
Ví dụ trên, các phần tử nil
sẽ được loại bỏ trong array mới. Như vậy, compactMap sẽ giúp ích cho bạn khá nhiều trong việc đảm bảo giá trị và tính an toàn của dữ liệu.
flatMap
Bạn sẽ có một toán tử thừa hưởng sức mạnh của map & compactMap. Khi vừa:
- Biến đổi được giá trị các phần tử
- Xử lý thêm được cho các phần tử, như khử đi các Optional
- Biến đổi được kiểu dữ liệu
Bạn xem ví dụ nhóe!
let exampleList = [1, 2, 3, 4, 5] let exampleList2 = exampleList.flatMap { number -> String in "\(number)" } print(exampleList) print(exampleList2)
Kiểu dữ liệu ban đầu của exampleList
là Int Array và sau đó bạn có kiểu mới là String Array cho exampleList2
. Bằng với việc sử dụng toán tử flatMap bạn có thể thay đổi cả kiểu dữ liệu & giá trị của đầu ra khác với đầu vào.
filter
Sử dụng filter sẽ lọc các phần tử của mãng với điều kiện được cung cấp. Toán tử này khá đơn giản nhĩ. Xem ví dụ là hiểu liền.
let arr3 = [0, 3, 4, 5, 98, 17, 82, 10, 2] let arr4 = arr3.filter { $0 % 2 == 0 } print(arr4)
Ví dụ trên, sẽ lọc và tạo ra một mãng mới với các phần tử với điều kiện là số chẵn. EZ Game!
reduce
reduce được sử dụng để kết hợp tất cả các phần tử trong Array để tạo một giá trị duy nhất. Ví dụ, bạn sẽ cộng tất cả giá trị trong một mãng thành một giá trị duy nhất.
let sum = arr3.reduce(0, +) print(sum)
Ví dụ trên, bạn có 0 là giá trị ban đầu. Toán tử sử dụng để làm giảm đi số lượng các phần tử là phép +. Nghĩa là lấy phần tử hiện tại cộng với giá trị ban đầu. Sau đó, gán giá trị mới thành giá trị ban đầu và tiếp tục lặp cho phần tử tiếp theo. EZ game!
sorted
Cái tên sorted thì quá dễ hiểu rồi. Bạn sẽ có được một mãng mới với thứ tự các phần tử được sắp xếp lại. Xem ví dụ nhóe!
let arr5 = arr3.sorted() print(arr5)
Mặc định khi bạn không cung cấp tham số cho sorted thì sẽ sắp xếp theo thứ tự từ bé tới lớn. Còn muốn ngược lại hay theo tiêu chí gì khác, thì bạn tự tìm hiểu thêm nhóe. Ahihi!
Currying
Định nghĩa
Về định nghĩa cho Currying thì như sau:
Đây là 1 kỹ thuật cho phép chuyển đổi một function nhiều tham số thành những function liên tiếp có một tham số.
Cách này giúp cho các function được là giá trị trả về, nhưng chính nó lại nhiều tham số. Chúng ta sẽ làm giảm đi các tham số bằng việc tạo ra các Higher Order Function trong các Higher Order Function.
Khúc này hơi nỗ não rồi nhóe. Ahihi, khúc này mình không biết giải thích sao cho dễ hiểu nữa.
Bạn hãy xem lại ví dụ trên:
aHigherOrderFunction { someOperation($0, "a constant") }
Trong đó:
aHigherOrderFunction
là một Higher Order Function với tham số là 1 function- Bạn sử dụng closure là đối số cho tham số của
aHigherOrderFunction
- Trong closure bạn lại sử dụng 1 function
someOperation
để làm return type $0
là đối số của closure, còna constant
là một đối số khác
Vấn đề chính là tại đối số a constant
, và chúng ta tiếp tục loại bỏ đi closure thì sẽ trông như thế này.
aHigherOrderFunction { curried_SomeOperation("a constant")($0) }
Trong đó:
- Các tham số sẽ được gửi riêng biệt, mỗi tham số trong dấu ngoặc riêng
()
. - Thứ tự truyền các tham số được hoán đổi,
a constant
được chuyển trước.
Như vậy, Currying là chia nhỏ một function có nhiều tham số, thành 1 chuỗi function với 1 tham số duy nhất.
Ví dụ
Ta sẽ cụ thể thông qua việc triển khai các ví dụ trên nhóe, khi đó bạn sẽ dễ hiểu hơn một tí:
func aHigherOrderFunction(_ operation: (Int) -> ()) { let numbers = 1...100 numbers.forEach(operation) } func someOperation(_ p1: Int, _ p2: String) { print("number is: \(p1), and String is: \(p2)") } aHigherOrderFunction { someOperation($0, "a constant") }
Đó là cách bình thường với việc gọi someOperation
với 2 tham số.
$0
từ closure củaaHigherOrderFunction
a constant
được thêm vào cho tham số
Tiếp tục nhóe!
func curried_SomeOperation(_ p1: Int) -> (String) -> () { return { str in print("number is: \(p1), and String is: \(str)") } }
Với curried_SomeOperation
bạn chú ý return của nó lại là 1 closure. Mấu chốt vấn đề là tại đây. Bạn áp dụng nó vào aHigherOrderFunction
để xem ra sao.
aHigherOrderFunction { curried_SomeOperation($0)("a constant") }
Kết quả vẫn giống như ở trên với function có nhiều tham số. Với cách Currying bạn sẽ làm giảm đi các closure và đơn giản hóa cách truyền các tham số. Bạn chỉ cần nhìn vào kiểu return từng function:
aHigherOrderFunction
là kiểu return là ()curried_SomeOperation
là kiểu return là (String) -> ()
Mặc dù kiểu return là khác nhau, nhưng xét tới kiểu return trong closure currying,thì nó lại giống với Higher Order Function. Qua đó, bạn hãy bình tĩnh và thử suy nghĩ ví dụ của riêng bạn nhằm đúng mục đích Currying là sẽ ổn đó.
Lợi ích, cho phép chúng ta tạo hàm nhỏ hơn từ các hàm lớn hơn bằng cách giảm số lượng đối số cần thiết.
A generic currying function
Phần nãy lại tiếp tục hại não của bạn. Chúng ta vẫn có thể biến đổi 1 function có nhiều tham số thành 1 currying. Chứ không cần phải viết lại một function khác như ví dụ ở trên.
func curry<T1, T2, T3>( _ originalMethol: @escaping (T1, T2) -> T3 ) -> (T1) -> (T2) -> T3 { return { t1 in { t2 in originalMethol(t1, t2) } } }
Ta áp dụng các Generic để mô tả các bước return cần thiết. Bạn chỉ cần liên tưởng (Int) -> (String) -> () thành (T1) -> (T2) -> T3 là được. Có 2 điểm khác biệt sau đây:
- Bổ sụng thêm 1 closure ở tham số, là
originalMethol
- Sử dụng
@escaping
cho tham số, điều này là cần thiết để thực thioriginalMethol
sau khi curry kết thúc
Và ta có cách sử dụng như sau:
someOperation(1, "one") curry(someOperation)(1)("one")
Huyền bí phải không nào
Flipping
Tiếp tục, chúng ta sẽ chơi đùa với não của bạn nữa nha. Thử suy nghĩ cách đảo thứ tự các đối số của bạn, thì sẽ như thế nào. Bạn yên tâm là ta sẽ không thay đổi code của function hay thêm function, mà vẫn có được trải nghiệm Higher Order Function cao cấp hơn.
Xem ví dụ nhóe, bây giờ ta sẽ đổi thử tự trả về (T2) -> (T1) -> T3
func flip<T1, T2, T3>( _ originalMethod: @escaping (T1) -> (T2) -> T3 ) -> (T2) -> (T1) -> T3 { return { t2 in { t1 in originalMethod(t1)(t2) } } } someOperation(1, "one") curry(someOperation)(1)("one") flip(curry(someOperation))("a constant")(1) aHigherOrderFunction(flip(curry(someOperation))("a constant"))
Bạn sẽ nhận ra một điều là kiểu trả về flip(curry(someOperation))
sẽ là (String) -> (Int) -> ()
. Nó cũng như là kiểu của curried_SomeOperation
.
Khá là ảo diệu.
Qua trên, bạn thấy được 2 kỹ thuật cao cấp tiếp theo cho Higher Order Function với Curry & Flip giúp cho bạn thay đổi thứ thứ các đối số truyền vào. Chúng sẽ thường sử dụng nếu bạn là người thiết kế nên các API của bạn, thì cần dùng tới flip này là điều cần thiết.
Tuy nhiên, bản thân mình viết tới đây cũng chưa hiểu rõ lắm. Bạn cần cẩn trọng khi sử dụng nhóe!
Generated class methods
Bạn còn đủ sức chiến đấu tới đây không nào. Nếu bạn vẫn ổn, thì chúng ta tiếp tục hành trình nỗ não tiếp theo nhóe!
Định nghĩa
Bạn sẽ khám phát thêm các tính năng hay nữa, mà Higher Order Function mang lại cho bạn. Mục tiêu lần này chính là các function cho các method của class. Bạn có một ví dụ dưới đây cho method hoặc instance-function nhóe.
extension Int { func word() -> String? { let formatter = NumberFormatter() formatter.numberStyle = .spellOut return formatter.string(from: self as NSNumber) } }
Cũng khá đơn giản, bạn mở rộng Int với function word biến đổi nó thành một String Optional. Cách sử dụng như sau:
10.word() //ten 99.word() //ninety-nine
Ta có từ 1 Int sẽ thành 1 String?. Nhưng bạn được Swift cung cấp thêm cho một Higher Order Function bọc lại function của bạn. Với ví dụ trên thì nó là:
Int.word
Kiểu dữ liệu của nó sẽ là: (Int) -> () -> Optional<String>
Nghĩa là 1 Higher Order Function với tham số chính là 1 Int và kiểu return là closure () -> Optional<String>. Bạn xem tiếp cách sử dụng nhóe!
let a = Int.word(10) // closure a() // thực thi closure Int.word(10)() // gọi tắt
Cũng hơi là vi diệu chứ. Khi mục đích ban đầu là tạo các method cho đối tượng sử dụng. Bây giờ, được cung cấp thêm Higher Order Function với tham số chính là đối tượng mình sẽ truyền vào.
Swift bá quá!
Khử dấu ()
Tiếp tục, ta sẽ áp dụng kĩ thuật flip ở trên vào với mục đích thay đổi thử tự nhóe.
func flip<T1, T2> ( _ originalMethod: @escaping (T1) -> () -> T2 ) -> () -> (T1) -> T2 { return { {t1 in originalMethod(t1)() } } } flip(Int.word)() // (Int) -> Optional<String> flip(Int.word)()(1) // String
Để hạn chế nhầm lẫn, thì bạn nên tạo ra các biến cho các flip để dễ sử dụng, biến ở đây giống như định danh mà thôi. Chúng sẽ sử dụng như sau:
var flippedWord = flip(Int.word)() // closure flippedWord(99) // String [1, 2, 3, 4, 5].map(flippedWord) // array Sring
Khá là thú vị phải không nào. Khi:
flippedWord
xem như là 1 closureflippedWord(99)
sẽ thực thi với đối số là Int và kết quả là String- áp dụng thêm vào
map
thìflippedWord
trở thành đối số cho tham số củamap
. Vì nó cùng kiểu tham số cho phần closure của map. Từ đó, ta có một cách biến đổi kết hợp khá hay.
Nếu bạn thấy dấu () khá dư thừa trong ví dụ flip trên, thì có thể tiện tay xóa nó đi. Cập nhập lại closure ở trong thân hàm nhóe. Xem ví dụ ở dưới.
func flip2<T1, T2> ( _ originalMethod: @escaping (T1) -> () -> T2 ) -> (T1) -> T2 { return { t1 in originalMethod(t1)() } } flip2(Int.word)(2)
Merging Higher order functions
Vẫn chưa kết thúc nhóe bạn, hy vọng bạn sẽ trụ nỗi qua phần cuối này (tạm thời là cuối cùng rồi nhóe).
Khi bạn có nhiều Higher Order Function, thì sẽ như thế nào nếu chúng ta gọi liên tiếp chúng. Lấy lại ví dụ với word của Int, ta thêm 1 function nữa.
extension Int { func word() -> String? { let formatter = NumberFormatter() formatter.numberStyle = .spellOut return formatter.string(from: self as NSNumber) } func double() -> Int { self * 2 } func doubleAndWord() -> String? { self.double().word() } }
Code trông cũng khá là ổn nhĩ. Tiếp tục, chúng sẽ cũng áp dụng như cách tạo ra các Flip ở trên để nhóm các Higher Order Function này. Đại khái thì code trông như thế này.
func mergeFunctions<T1, T2, T3>( _ l: @escaping (T1) -> () -> T2, _ r: @escaping (T2) -> () -> T3 ) -> (T1) -> T3 { return { t1 in let lValue = l(t1)() return r(lValue)() } }
Còn sử dụng thì đại khái như thế này luôn.
let mergeFunction = mergeFunctions(Int.double, Int.word) // closure mergeFunction(4) // eight mergeFunctions(Int.double, Int.word)(10) // twenty
Trong ví dụ trên, bạn cần lưu ý:
- Function merge được thiết kế cho các Higher Order Function với 1 tham số.
- Mối quan hệ của các kiểu dữ liệu cần được chú ý kĩ. Đầu ra cảu cái này là đầu vào của cái kia, chúng phải hợp với nhau.
- Nếu chúng không khớp, thì hàm này không có ý nghĩa.
- Thứ tự truyền tham số cũng tương tự như vậy.
Tuy là đã đạt được mục đích rồi, nhưng chúng ta còn có thể nâng cấp ví dụ trên bằng function composition. Hay còn gọi với cái tên dân gian là “Đa năng hóa toán tử”. Xem ví dụ nhóe!
func +<T1, T2, T3>( _ left: @escaping (T1) -> () -> T2, _ right: @escaping (T2) -> () -> T3 ) -> (T1) -> T3 { return { t1 in let leftValue = left(t1)() return right(leftValue)() } } var newFunc = Int.double + Int.word newFunc(5) // ten (Int.double + Int.word)(9) // eighteen
Bạn sẽ có được một cách dùng mới cho toán tử + trong Swift nhóe. Cụ thể function composition như thế nào, thì hẹn bạn ở một bài viết khác. Bạn có thể tùy ý định nghĩa các toán tử mà bạn thích. Và cũng tránh lạm dụng nó quá mức, vì rất dễ gây khó hiểu cho người khác hoặc chính bạn. Ahihi!
Kết thúc nhóe!
Tạm kết
- Tìm hiểu định nghĩa của Higher Order Function trong Swift
- Tìm hiểu về các Higher Order Functions được Swift cung cấp
- Cách tư duy coding của bạn với các kiểu viết function khác nhau (cũ và mới)
- Vài trò của closure ảnh hưởng trong việc truyền tham số cho các Higher Order Function
- Sử dụng Currying để làm thu gọn các tham số của các function dùng làm tham số.
- Áp dụng các kỹ thuật Curry & Flip để thay đổi thư tự các đối số
- Tạo các Generated class methods trong các lớp và cách sử dụng chúng như là một Higher Order
- Merger các Higher Order với nhau và đơn giản hóa cách sử dụng chúng
Okay! Tới đây, mình xin kết thúc bài viết giới thiệu về Higher Order Function trong Swift. Nếu có gì thắc mắc hay góp ý cho mình thì bạn có thể để lại bình luận hoặc gởi email theo trang Contact.
Cảm ơn bạn đã đọc bài viết này!
Related Posts:
Written by chuotfx
Hãy ngồi xuống, uống miếng bánh và ăn miếng trà. Chúng ta cùng nhau đàm đạo về đời, về code nhóe!
Leave a Reply Cancel reply
Fan page
Tags
Recent Posts
- Charles Proxy – Phần 1 : Giới thiệu, cài đặt và cấu hình
- Complete Concurrency với Swift 6
- 300 Bài code thiếu nhi bằng Python – Ebook
- Builder Pattern trong 10 phút
- Observer Pattern trong 10 phút
- Memento Pattern trong 10 phút
- Strategy Pattern trong 10 phút
- Automatic Reference Counting (ARC) trong 10 phút
- Autoresizing Masks trong 10 phút
- Regular Expression (Regex) trong Swift
You may also like:
Archives
- September 2024 (1)
- July 2024 (1)
- June 2024 (1)
- May 2024 (4)
- April 2024 (2)
- March 2024 (5)
- January 2024 (4)
- February 2023 (1)
- January 2023 (2)
- November 2022 (2)
- October 2022 (1)
- September 2022 (5)
- August 2022 (6)
- July 2022 (7)
- June 2022 (8)
- May 2022 (5)
- April 2022 (1)
- March 2022 (3)
- February 2022 (5)
- January 2022 (4)
- December 2021 (6)
- November 2021 (8)
- October 2021 (8)
- September 2021 (8)
- August 2021 (8)
- July 2021 (9)
- June 2021 (8)
- May 2021 (7)
- April 2021 (11)
- March 2021 (12)
- February 2021 (3)
- January 2021 (3)
- December 2020 (3)
- November 2020 (9)
- October 2020 (7)
- September 2020 (17)
- August 2020 (1)
- July 2020 (3)
- June 2020 (1)
- May 2020 (2)
- April 2020 (3)
- March 2020 (20)
- February 2020 (5)
- January 2020 (2)
- December 2019 (12)
- November 2019 (12)
- October 2019 (19)
- September 2019 (17)
- August 2019 (10)