+1

Higher-order Function

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, BC, trong đó cả BC đề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 AA 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

Viblo
Let's register a Viblo Account to get more interesting posts.