Higher-order Function
Bài đăng này đã không được cập nhật trong 9 năm
Higher-order Function
Background
Dynamic Dispatch là quá trình chọn lựa để gọi đơn vị thực thi của một hàm đa hình tại runtime, trái ngược với Static Dispatch là chọn đơn vị thực thi tại thời điểm biên dịch.
Mục đích của dynamic dispatch là hỗ trợ những trường hợp không thể xác định được đơn vị thực thi function thích hợp tại thời điểm biên dịch vì điều đó phụ thuộc vào kiểu của một hay nhiều các đối số của function đó trong thời gian chạy. Vì lẽ đó dynamic dispatch thường được sử dụng trong các ngôn ngữ hướng đối tượng nơi mà các class khác nhau nhưng có chung các hàm giống nhau dựa trên sự kế thừa. Giả dụ có 3 class A
, B
và C
, trong đó cả B
và C
đều kế thừa hàm foo()
từ A
. Giờ x
là một biến của lớp A
. Tại runtime, x
có thể mang giá trị của kiểu B
hay C
và tất nhiên ta cũng không biết nó chính xác là gì tại thời điểm biên dịch.
Với static dispatch, một phương thức gọi x.foo()
sẽ luôn luôn trỏ tới thực thi của lớp A
vì A
là lớp khai báo là kiểu của object này. Tuy nhiên với dynamic dispatch, ngôn ngữ sẽ tự động xác định kiểu của giá trị x
trong thời điểm chạy và gọi đến phiên bản foo
phù hợp với kiểu đó, có thể là A
, B
hay C
.
Higher order function
Một hàm higher-order, ngược lại với first-order, có thể có một trong ba dạng sau:
- Một hay nhiều đối số của nó là hàm và giá trị trả ra không phải là hàm
- Nó trả ra một hàm khác nhưng không có đối số nào là hàm.
- Đối số là hàm và giá trị trả về cũng là hàm.
Hãy cùng đến với 1 ví dụ đơn giản
def square(x: Int) = x * x
def cube(x: Int) = x * x * x
def sum(f: Int => Int, a: Int, b: Int) : Int =
if (a == b) f(a) else f(a) + sum(f, a+1, b)
println(sum(square, 1, 10)) // => 385
println(sum(cube, 1)) // => 3025
sum
giờ là một "higher-order" function với đối số đầu tiên được truyền vào là một hàm có đầu vào và đầu ra đều là kiểu Int
.
Scala cũng cho phép bạn sử dụng anonymous function như là một đối số
sum(square, 1, 10) <=> sum(x=>x*x, 1, 10)
Trong phần lớn các ngôn ngữ như Ruby hay JavaScript, higher-order chính là ý tưởng đã tạo nên các hàm thông dụng như apply
, map
, hay filter
và tận dụng nó chính là một cách tuyệt vời để chúng ta áp dụng được các nguyên lý DRY - sử dụng tối đa các hàm sẵn có trong một context mới.
//Filter
Set[A].filter(p: A => Boolean):Set[A]
//Map
map[B](f: A => B):List[B]
Hãy thử ứng dụng higher-order function ở mức cao nhất, tạo ra một function có cả đầu vào và đầu ra đều là hàm
def say(something: String) = (somebody: String) => {
println("Hi" + somebody + something)
}
val sayHi = say(".I'm feeling high")
sayHi("Karl")
Chạy những hàm này trên console chúng ta có thể thấy các bước diễn ra như sau
scala> def say(something: String) = (somebody: String) => {
| println("Hi " + somebody + ", " + something)
| }
say: (something: String)String => Unit
//Hàm say nhận vào một String và trả ra một function, do đó ta có thể gán nó cho biến sayHi dưới đây
scala> val sayHi = say("I'm feeling high.")
sayHi: String => Unit = <function1>
//Như có thể thấy sayHi là hàm có đầu vào là một String và không trả ra giá trị cụ thể nào nên nó sẽ có kiểu là Unit, đồng thời nó cũng là một function
scala> sayHi("Karl")
Hi Karl, I'm feeling high.
Summary
Vậy là chúng ta đã có thể phần nào lợi ích mà higher-order function đem lại, nó cho phép developer có thể sử dụng lại những hàm đã có sẵn trong những bối cảnh mới hoặc lường trước được, đồng thời cũng khiến function của bạn trở nên linh hoạt hơn. Một điều chắc chắn rằng các ngôn ngữ OOP đã rất khôn ngoan tận dụng higher-order function từ Functional Programming để tạo nên nhiều hàm tiện ích tuyệt vời. Hãy thử bắt đầu từ những ví dụ đơn giản như ở trên trong Scala hoặc JavaScript(strong recommend) và đi dần tới những function sử dụng callback phức tạp bạn sẽ thấy số lượng đáng kể code được rút gọn.
All rights reserved