Pure Function là gì? Tại sao phải là Pure function chứ

0
Dịch vụ dạy kèm gia sư lập trình

Khi làm việc với Javascript, đâu đó bạn đã từng nghe nói tới khái niệm Pure Function – dịch tạm là hàm “nguyên tem”.  Ví dụ, trong React Redux, các hàm thao tác với dữ liệu trong store bắt buộc phải là pure function.

Thực chất pure function không phải là một API, hay một hàm cụ thể nào, mà nó là một khái niệm về một loại hàm trong lập trình.

Vậy Pure function là gì? Ưu điểm và tại sao chúng ta lại cần tới nó?

Qua bài viết này, chúng ta cùng tìm hiểu nhé!

Pure function là gì?

Có nhiều cách hiểu và định nghĩa khác nhau về pure function. Nhưng theo mình, chúng ta có thể hiểu đơn giản như sau:

Pure function là hàm luôn trả về cùng một kết quả khi tham số đầu vào không thay đổi, cho dù hàm đó được thực thi bao nhiêu lần đi chăng nữa.

Như vậy, giá trị mà pure function trả về chỉ phụ thuộc duy nhất vào giá trị tham số truyền vào, nó không phụ thuộc vào bất cứ trạng thái, dữ liệu bên ngoài phạm vi của hàm.

Đặc biệt là pure function không tạo ra các side-effects nào tới chương trình.

Ồ, nói tới đây lại phát sinh khái niệm mới: Side-effect là gì vậy?

Side effects

Side-effect là những thao tác được thực hiện ở bên trong một hàm mà nó ảnh hưởng tới toàn chương trình.

Ví dụ một số side effects hay gặp:

  • Tạo HTTP request tới API (AJAX/fetch)
  • Ghi – Cập nhật dữ liệu vào file
  • Lưu dữ liệu vào Database, Local Storage
  • Hiển thị dữ liệu ra màn hình
  • Thay đổi nội dung của DOM
  • vân vân và mây mây…

Tóm lại, side effect là những việc bạn làm nhưng không chỉ ảnh hưởng tới chính bạn, mà còn ảnh hưởng cả tới những người khác nữa.

Ví dụ minh họa

Để dễ hình dung hơn, chúng ta sẽ minh họa bằng code.

Những hàm dưới đây được coi là pure function:

// Hàm tính tổng hai số
function sum(a, b) {
  return a + b;
}

console.log(sum(1, 2)) // output: 3
console.log(sum(1, 2)) // output: 3
console.log(sum(1, 2)) // output: 3

Như bạn cũng thấy, hàm sum() này luôn trả về cùng một kết quả khi chúng ta truyền tham số vào như nhau. Cho dù bạn có gọi thực thi hàm đó bao nhiêu lần đi chăng nữa. Kết quả trả về chỉ phụ thuộc vào tham số.

Một ví dụ khác mà không phải là pure function:

let tax = Math.random(100);

// Hàm tính tổng tiền sau thuế
function sumAfterTax(a, b) {
  return ((a + b) * (tax / 100)).toFixed(0)
}

console.log(sumAfterTax(1000, 9000)) // output: 43
console.log(sumAfterTax(1000, 9000)) // output: 27
console.log(sumAfterTax(1000, 9000)) // output: 35

Với hàm trên, giá trị mà nó trả về ngoài việc phụ thuộc vào hai tham số mà còn phụ thuộc vào giá trị tax từ bên ngoài. Do đó, mỗi khi hàm này thực thi, kết quả trả về có thể khác nhau, cho dù tham số truyền vào vẫn như cũ.

Một số API mà bạn gọi trong hàm có thể khiến cho một hàm không còn là pure function nữa. Ví dụ như dưới đây:

Math.random

const random = () => Math.random()

Hàm Date

const getDate = () => Date.now()

Call API

const getUsers = await fetch('/users')

Những ví dụ hàm trên được coi là không xác định vì cùng đầu vào mà đầu ra luôn khó lường, không biết trước được.

Một số lỗi khiến một hàm không còn là pure function

Về nguyên tắc, để chuyển về pure function, chúng ta nên chuyển tất cả những yếu tố ảnh hưởng tới kết quả đầu ra vào bộ tham số.

Tuy nhiên, cách chúng ta truyền tham số vào hàm cũng có thể khiến một hàm không còn là pure function nữa.

Dưới đây là một số ví dụ minh họa và cách sửa:

1. Sử dụng biến bên ngoài hàm

// Impure
let min = 60;
const isLessThanMin = value => value < min;

Giải pháp: sử dụng dependency injection. Sửa lại thành:

// pure
const isLessThanMin = (min, value) => value > min

2. Truyền địa chỉ tham chiếu

// Impure
const squares = (nums) => {
   for(let i = 0; i < nums.length; i++) nums[i] **= 2;
}

Lý do: Liên quan tới khái niệm địa tham chiếu và tham trị. Khi truyền tham số nums như trên là truyền địa chỉ tham chiếu và khi trong hàm sử dụng biến nums cũng sẽ tác động tới vùng nhớ mà biến nums trỏ tới.

Giải pháp: sử dụng hàm map(), tạo mảng mới thay vì sửa trực tiếp.

// pure
const squares = (nums) => nums.map(num => num * num)

3. Truyền tham chiếu của Object

// Impure
const updateUserAge = (user, age) => {
  user.age = age
}

Lý do: Tương tự như trường hợp trên, chúng ta sửa trực tiếp trên tham chiếu được truyền vào. Nên vô tình nó cũng ảnh hưởng tới vùng nhớ mà biến đó trỏ tới. Nên nhớ, một biến thực chất chỉ là lưu địa chỉ của vùng nhớ mà thôi.

Giải pháp: Tạo một đối tượng mới.

// Pure
const updateUserAge = (user, age) => ({ ...user, age })

Lợi thế của Pure function

Có rất nhiều lý do mà chúng ta nên viết một hàm theo kiểu pure function. Nếu kể ra thì có mà cả đống.

Nhưng tóm gọn lại thì chỉ có một lý do lớn nhất: Pure function khiến cho một hàm trở thành một đơn vị logic nhỏ nhất trong ứng dụng, nó độc lập hoàn toàn với các hàm khác, độc lập với ứng dụng. Giống như tế bào trong cơ thể vậy.

Nhờ vậy, chúng ta có thể thực hiện unit test dễ dàng, chỉnh sửa nội dung hàm mà không cần quá lo ngại tới các phần khác. Chỉ cần test kỹ kết quả đầu ra nó chính xác với yêu cầu của hàm là được.

Tuy nhiên

Không phải tất cả các hàm trong chương trình đều là Pure function. Nếu không có các side effects thì làm sao ta có thể thao tác với DOM, lưu dữ liệu vào database, gọi API… Lúc này ứng dụng sẽ thành cái gì đây 😊

Quan trọng là phải kết hợp hài hòa, lúc cương lúc nhu, kết hợp sao cho hợp lý là được.

💦 Đọc thêm về Javascript:

Dịch vụ phát triển ứng dụng mobile giá rẻ - chất lượng
Bài trướcHọc lập trình online có hiệu quả không? (Trả lời: Có!)
Bài tiếp theoCách tạo Custom React Hook để lưu State vào LocalStorage
Sơn Dương
Tên đầy đủ là Dương Anh Sơn. Tốt nghiệp ĐH Bách Khoa Hà Nội. Mình bắt đầu nghiệp coder khi mà ra trường chẳng xin được việc đúng chuyên ngành. Mình tin rằng chỉ có chia sẻ kiến thức mới là cách học tập nhanh nhất. Các bạn góp ý bài viết của mình bằng cách comment bên dưới nhé !

Bình luận. Cùng nhau thảo luận nhé!

avatar
  Theo dõi bình luận  
Thông báo