Higher order Functions là gì? Tại sao nên sử dụng chúng?

Đây là một khái niệm rất quan trọng trong Functional Programming. Ở đây mình sẽ cho ví dụ dựa trên Javascript, cụ thể là TypeScript, do đó mình hi vọng các bạn đã có một số kiến thức nhất định về JS trước. Điều này sẽ giúp bạn nắm bắt nội dung bài viết dễ dàng hơn.
Higher order Functions là gì? Tại sao nên sử dụng chúng?
Higher order Functions là gì? Tại sao nên sử dụng chúng?

Higher order Function là gì

Higher order function không không phải là một khái niệm mới. Nó khá đặc thù và phổ biến trong Functional Programming.

Concept đơn giản ở đây là một Function nhận một hoặc nhiều function khác làm tham số, sau đó trả về giá trị hoặc một function mới.

Ví dụ, Javascript có hàm Array.filter. Đây là một higher order function, do nó nhận một function làm tham số.

const qrCodes: string[] = ['QR1', 'QR222', 'QR3333', 'QR4444', 'QR5555']

const qrCodesV3: string[] = qrCodes.filter((qrCode) => qrCode.length > 5)

console.log(qrCodesV3) //['QR3333', 'QR4444', 'QR5555']

Javascript cũng có một hàm khác Array.slice. Tuy nhiên, đây không phải là higher order function, do nó không nhận tham số là function, và giá trị trả về của nó cũng không phải một function nào cả.

const qrCodes: string[] = ['QR1', 'QR222', 'QR3333', 'QR4444', 'QR5555']

const firstTwoQrCodes: string[] = qrCodes.slice(0, 2)

console.log(firstTwoQrCodes) //['QR1', 'QR222']

 

Hữu dụng hay không?

Higher order Function giúp code của chúng ta ngắn gọn, linh hoạt, có thể dễ dàng maintenance.

Nhờ vào khả năng kết hợp giữa các function thông qua higher order function mà chúng được sử dụng rất phổ biến trong Functional Programming.

 

Ví dụ

Mình sẽ viết lại hàm một filter khác thực hiện công việc tương tự ở ví dụ đầu tiên:

const stringsLengthGreaterThan = (length: number, items: string[]): string[] => {
  const output = []

  for (const item of items) {
    if (item.length > length) {
      output.push(item)
    }
  }

  return output
}

const qrCodes: string[] = ['QR1', 'QR222', 'QR3333', 'QR4444', 'QR5555']

const qrCodesV3: string[] = stringsLengthGreaterThan(5, qrCodes)

console.log(qrCodesV3) //['QR3333', 'QR4444', 'QR5555']

Đoạn code trên có vẻ không được linh hoạt cho lắm. Thử tưởng tượng bạn cần lọc ra một danh sách các number với giá trị lớn hơn 5 chẳng hạn, bạn cần phải tạo một hàm khác, cập nhật thêm một mớ logic khác.

const numberGreaterThan = (value: number, items: number[]): number[] => {
  const output = []

  for (const item of items) {
    if (item > value) {
      output.push(item)
    }
  }

  return output
}

const numbers: number[] = [1, 3, 5, 7, 9, 11]

const highNumbers: number[] = numberGreaterThan(5, numbers)

console.log(highNumbers) //[7, 9, 11]

Hàm numberGreaterThan và stringsLengthGreaterThan hầu như giống nhau, chỉ khác nhau về điều kiện so sánh dữ liệu, điều này thì khá là tệ.

Mình theo phong cách viết càng ít logic càng tốt. Viết càng ít logic thì càng giảm thiểu số lượng unit tests cho các logic này. Rõ ràng là vừa đỡ mệt, vừa đỡ tốn thời gian, lại vừa dễ dàng bảo trì hơn, đúng không?


Giờ mình sẽ sửa lại đoạn code trên sử dụng higher order function:

function higherOrderFilter<T = any>(
  iteratee: (item: T) => boolean
): (items: T[]) => T[] {
  return (items) => {
    const output: T[] = []

    for (const item of items) {
      if (iteratee(item)) {
        output.push(item)
      }
    }

    return output
  }
}

const qrCodes: string[] = ['QR1', 'QR222', 'QR3333', 'QR4444', 'QR5555']
const numbers: number[] = [1, 3, 5, 7, 9, 11]

const qrCodesV3: string[] = higherOrderFilter(
  (item: string) => item.length > 5
)(qrCodes)
console.log(qrCodesV3) //['QR3333', 'QR4444', 'QR5555']

const highNumbers: number[] = higherOrderFilter(
  (item: number) => item > 5
)(numbers)
console.log(highNumbers) //[7, 9, 11]

Đấy, code đã clean hơn rất nhiều. Do logic filter đã được tách biệt khỏi điều kiện filter, nên bạn cũng tái sử dụng lại được logic từ hai hàm numberGreaterThan và stringsLengthGreaterThan.

Ở ví dụ trên, có thể có nhiều bạn sẽ thắc mắc tại sao mình lại đem iteratee function làm đối số đầu tiên, và array dữ liệu mẫu làm đối số thứ hai. Thực ra, chúng đều có dụng ý cả.


Ví dụ bên dưới đây sẽ sử dụng thêm thư viện lodash/fp.

Mình sẽ thực hiện việc loại bỏ các giá trị thừa và lặp lại thông qua hàm _.compact và _.uniq, sau đó sẽ trả về các QR codes có length lớn hơn 6.

import _ from 'lodash/fp'

const qrCodes: string[] = [
  '',
  'QR222',
  'QR3333',
  'QR4444',
  'QR5555',
  'QR5555',
  'QR5555',
  'QR66666',
  'QR66666',
  'QR66666',
  'QR77777',
  'QR77777',
  'QR77777',
]

const qrCodesV4: string[] = _.pipe(
  _.compact,
  _.uniq,
  higherOrderFilter((item: string) => item.length > 6)
)(qrCodes)
console.log(qrCodesV4) //['QR66666', 'QR77777']

Bây giờ mình sẽ viết lại hàm higherOrderFilter và đảo ngược thứ tự hai đối số để xem sự khác biệt nhé.

import _ from 'lodash/fp'

const qrCodes: string[] = [
  '',
  'QR222',
  'QR3333',
  'QR4444',
  'QR5555',
  'QR5555',
  'QR5555',
  'QR66666',
  'QR66666',
  'QR66666',
  'QR77777',
  'QR77777',
  'QR77777',
]

function higherOrderFilterReverse<T = any>(
  items: T[]
): (iteratee: (item: T) => boolean) => T[] {
  return (iteratee) => {
    const output: T[] = []

    for (const item of items) {
      if (iteratee(item)) {
        output.push(item)
      }
    }

    return output
  }
}

const qrCodesV5: string[] = _.pipe(
  _.compact, 
  _.uniq, 
  (items: string[]) => higherOrderFilterReverse(items)((item: string) => item.length > 6)
)(qrCodes)
console.log(qrCodesV5) //['QR66666', 'QR77777']

Kết quả vẫn giống như ví dụ trước, nhưng do đối số đầu tiên của hàm higherOrderFilterReverse là một string array (tức kết quả trả về của hàm _.uniq trước đó), chúng ta bắt buộc phải tạo một hàm mới để có thể khai báo iteratee function.

Mình luôn cố gắng viết các đối số cuối cùng của higher order function sao cho có thể nhận giá trị trả về từ các function trước. Lúc đó, code của bạn sẽ ngắn gọn hơn rất nhiều.

 

Kết

Higher order Function làm một phần cực kỳ quan trọng trong Functional Programming. Nắm rõ chúng sẽ giúp các bạn viết code tốt hơn, ngắn gọn, clean hơn, ứng dụng của bạn cũng sẽ dễ dàng bảo trì hơn.

Mình hi vọng các bạn thích bài viết này. Nếu bạn có câu hỏi, đừng ngại inbox mình hoặc để lại comment nhé.

Cám ơn các bạn rất nhiều.

Comments

Bài viết nổi bật

Dạo gần đây đi đâu cũng nghe nói về microservices, người người nhà nhà rục rịch chuyển dịch hệ thống sang microservices. Trước khi đưa ra sự so sánh, mình sẽ khái quát một chút về Monolith Application và MicroServices một chút cho các bạn chưa biết nắm rõ hơn nhé.
PHP là ngôn ngữ được sử dụng rộng rãi nhất trên thế giới trong lập trình web. Nó cũng bị ghét nhất. Nhưng tại sao nhiều developer lại ghét nó đến vậy? Hôm nay chúng ta hãy cùng tìm hiểu lý do xem chúng có thuyết phục không nhé ^_^
Có khá nhiều bạn đã yêu cầu mình một bài viết về Repository Design Pattern. Vậy mục đích của nó là gì? Nó có thực sự cần thiết cho ứng dụng của bạn hay không? Những điểm mạnh, điểm yếu của nó là gì? Chúng ta cùng đi sâu tìm hiểu qua bài viết này nhé.
Lúc trước mình hay sử dụng cách này trên laptop phụ của mình, giờ mua license luôn rồi. Hôm nay mình xin chia sẻ cho bạn nào cần nhé.
Ở bài viết này mình sẽ hướng dẫn bạn bắt đầu xây dựng một ứng dụng HMVC với Laravel, và tận dụng sức mạnh của Composer khi quản lí modules.

Mục lục

Related posts

Phân trang - một thành phần không thể thiếu trong các ứng dụng có lượng dữ liệu lớn. Tuy nhiên, bạn hiểu được bao nhiêu về nó?
Javascript là một thành phần không thể thiếu đối với frontend developers. Tuy nhiên, ngay từ lúc ra đời, nó đã tồn tại khá nhiều vấn đề cần khắc phục. Đó là lý do tại sao từ 2015 (ES6) tới 2021 (ES12) ra đời nhằm giúp Javascript trở nên tốt hơn.
Dạo này mình làm việc với mấy bạn trên github, thấy hay xài mấy từ viết tắt mà mình không hiểu lắm. Thôi thì tổng hợp lại một list các từ viết tắt hay dùng trong github luôn cho ai cần :D
Dạo gần đây đi đâu cũng nghe nói về microservices, người người nhà nhà rục rịch chuyển dịch hệ thống sang microservices. Trước khi đưa ra sự so sánh, mình sẽ khái quát một chút về Monolith Application và MicroServices một chút cho các bạn chưa biết nắm rõ hơn nhé.
Cách bỏ qua câu lệnh --set-upstream quen thuộc cho các con lười
Mình sẽ giới thiệu 2 cách để xóa một property trong Javascript Object. Một cách sử dụng mutable - toán tử delete, một cách còn lại là immutable - tính năng Object Restructuring.
NestJS - Providers
1433
Providers là thành phần cơ bản và cực kỳ quan trọng trong Nest để thực hiện Dependency Injection.
Một số design patterns chính đang được sử dụng bởi NestJS mà bạn cần nắm rõ để làm việc với NestJS hiệu quả hơn.
Một số design patterns chính đang được sử dụng bởi NestJS mà bạn cần nắm rõ để làm việc với NestJS hiệu quả hơn.
NestJS - Controllers
1756
Trách nhiệm chính của controllers là xử lý các requests và phản hồi lại cho phía client.
Lúc trước mình hay sử dụng cách này trên laptop phụ của mình, giờ mua license luôn rồi. Hôm nay mình xin chia sẻ cho bạn nào cần nhé.
NestJS là một NodeJS framework tiến bộ để xây dựng các ứng dụng server-side hiệu quả, đáng tin cậy cùng với khả năng mở rộng dễ dàng.

Tin mới nhất

Phân trang - một thành phần không thể thiếu trong các ứng dụng có lượng dữ liệu lớn. Tuy nhiên, bạn hiểu được bao nhiêu về nó?
Javascript là một thành phần không thể thiếu đối với frontend developers. Tuy nhiên, ngay từ lúc ra đời, nó đã tồn tại khá nhiều vấn đề cần khắc phục. Đó là lý do tại sao từ 2015 (ES6) tới 2021 (ES12) ra đời nhằm giúp Javascript trở nên tốt hơn.
Dạo này mình làm việc với mấy bạn trên github, thấy hay xài mấy từ viết tắt mà mình không hiểu lắm. Thôi thì tổng hợp lại một list các từ viết tắt hay dùng trong github luôn cho ai cần :D
Dạo gần đây đi đâu cũng nghe nói về microservices, người người nhà nhà rục rịch chuyển dịch hệ thống sang microservices. Trước khi đưa ra sự so sánh, mình sẽ khái quát một chút về Monolith Application và MicroServices một chút cho các bạn chưa biết nắm rõ hơn nhé.
Cách bỏ qua câu lệnh --set-upstream quen thuộc cho các con lười
Bài viết này sẽ hướng dẫn các bạn cài đặt LEMP stack trên CentOS Stream 9 mới nhất trên Vultr Cloud VPS.
Mình sẽ giới thiệu 2 cách để xóa một property trong Javascript Object. Một cách sử dụng mutable - toán tử delete, một cách còn lại là immutable - tính năng Object Restructuring.
Đây là một khái niệm rất quan trọng trong Functional Programming. Ở đây mình sẽ cho ví dụ dựa trên Javascript, cụ thể là TypeScript, do đó mình hi vọng các bạn đã có một số kiến thức nhất định về JS trước. Điều này sẽ giúp bạn nắm bắt nội dung bài viết dễ dàng hơn.
Bài hướng dẫn này sẽ hướng dẫn các bạn cài đặt LEMP stack (Linux - Nginx - MySQL - PHP) trên CentOS 8.
NestJS - Providers
1433
Providers là thành phần cơ bản và cực kỳ quan trọng trong Nest để thực hiện Dependency Injection.
Một số design patterns chính đang được sử dụng bởi NestJS mà bạn cần nắm rõ để làm việc với NestJS hiệu quả hơn.
Một số design patterns chính đang được sử dụng bởi NestJS mà bạn cần nắm rõ để làm việc với NestJS hiệu quả hơn.
Javascript là một thành phần không thể thiếu đối với frontend developers. Tuy nhiên, ngay từ lúc ra đời, nó đã tồn tại khá nhiều vấn đề cần khắc phục. Đó là lý do tại sao từ 2015 (ES6) tới 2021 (ES12) ra đời nhằm giúp Javascript trở nên tốt hơn.
Mình sẽ giới thiệu 2 cách để xóa một property trong Javascript Object. Một cách sử dụng mutable - toán tử delete, một cách còn lại là immutable - tính năng Object Restructuring.
Đây là một khái niệm rất quan trọng trong Functional Programming. Ở đây mình sẽ cho ví dụ dựa trên Javascript, cụ thể là TypeScript, do đó mình hi vọng các bạn đã có một số kiến thức nhất định về JS trước. Điều này sẽ giúp bạn nắm bắt nội dung bài viết dễ dàng hơn.
NestJS - Providers
1433
Providers là thành phần cơ bản và cực kỳ quan trọng trong Nest để thực hiện Dependency Injection.
Một số design patterns chính đang được sử dụng bởi NestJS mà bạn cần nắm rõ để làm việc với NestJS hiệu quả hơn.