Trước hết hàm thuần khiết hay hàm thuần túy là từ được mình dịch từ tiếng Anh ra, nguyên si của nó là Pure function
. Vì vậy trong suốt bài viết mình sẽ không dịch từ này ra nữa, mà giữ nguyên cái tên tiếng Anh của nó như vậy nhé (cá nhân mình không thích dịch những từ như vậy ra :)) một phần nó là từ chuyên ngành mà, giữ nguyên thôi).
Khi nói đến lập trình hàm (functional programming) người ta thường đề cập hoặc quan tâm đến một cái tên, đó là pure function. Pure function là những “viên gạch” giúp lập trình hàm trở nên ảo diệu (lập trình hàm đang là xu hướng hiện nay). Nói cách dễ hiểu pure function là một nguyên tắc trong lập trình hàm. Nó được người đời “ngưỡng mộ” vì tính đơn giản dễ đọc dễ hiểu và khả năng dễ kiểm thử thần thánh của nó (mấy đứa đơn giản thường thì dễ biết dễ đoán hơn những đứa mà trong đầu toàn mấy thứ… lung tung).
Vào thẳng vấn đề, bạn hãy xem bức hình dưới đây và ngẫm thử function mình viết ra đã thỏa tiêu chí của pure functions chưa nhé (ảnh lấy từ nguồn internet, chứa nội dung là tiếng anh).

Thế nào thì được gọi là một pure function.
Để được gia nhập gia đình họ ‘pure’ thì function của bạn cần thỏa hai điều kiện:
- Đầu vào và đầu ra của function luôn luôn giống nhau, hay nói cách khác chúng luôn nhất quán.
- Không thay đổi trạng thái những biến hay những thành phần khác bên ngoài phạm vi của nó.
Cùng đi vào chi tiết để hiểu hơn hai nguyên tắc này nhé.
Đầu vào và đầu ra luôn luôn nhất quán với nhau
Đầu vào ở đây đang ám chỉ tham số của function, đầu ra là cái mà function trả về (return).
Ví dụ so sánh giữa hai cách tính tổng 2 số bên dưới đây.
Đầu tiên
1 | const add = function (x, y) { |
và
1 | let x = 2; |
Bạn có nhận ra cái nào là pure function hay không?
Sự nhất quán trong pure function
Pure function ở đây chính là ví dụ đầu tiên, kết quả trả về của ví dụ này luôn luôn nhất quán với tham số truyền vào. Kết quả trả về dựa trên tham số truyền vào, bất kể khi bạn gọi nó ở đâu và khi nào không quan trọng với tham số ấy nó luôn cho về cùng một kết quả.
Khi bạn truyền vào 2
và 4
hoặc đổi thứ tự lại 4
và 2
lúc đó, gọi hàm này ở chỗ nào trong chương trình, nó vẫn luôn trả về kết quả là 6
.
Chẳng có gì có thể ảnh hưởng được giá trị bạn trả về.
Vậy không nhất quán là sao?
Trái với pure function
ta có impure function
(hàm không thuần khiết). Impure function ở đây là ví dụ 2, bởi vì nó không trả về một cái gì cả, cái mà nó thực sự làm là thay đổi một cái biến, tạm gọi là biến toàn cục, biến này thuộc dạng shared state
. Hàm này dùng chung một biến được “chia sẻ” là x
và thay đổi giá trị của biến này (ban đầu x là 2), trong khi biến này không nằm trong phạm vi của nó. Lúc này sẽ xảy ra tính không nhất quán khi bạn gọi nó ở cách thời điểm hay chỗ khác trong chương trình. Ví dụ, ở lần đầu tiên kết quả in ra của x
là 6
, ở lần thứ hai là 10
và cứ thế. Chưa kể khi có nhiều chỗ gọi thì hàm này sẽ thay đổi x
với một giá trị mà chả ai đoán ra được. Vấn đề của shared state
là chỗ đấy.
Bởi vậy cách code như thế ở ví dụ hai thực sự không tốt tẹo nào.
Từ đó có thế thấy coding theo kiểu pure function dễ đọc code hơn, ít xảy ra bug hơn, nếu có thì sẽ xảy ra ở trường hợp hiếm hoi nhất. Lại thêm pure function có thể dễ dự đoán kết quả, dễ kiểm soát hơn nếu dùng trong đa luồng so với impure function.
Không ảnh hưởng hay thay đổi trạng thái bên ngoài phạm vi nó.

Vài ví dụ về việc vi phạm nguyên tắc này là:
- Dùng
console.log()
hoặc gì gì đó - Tạo một request ajax
- Thao tác thay đổi với file hệ thống
- Thay đổi DOM tree
Về cơ bản mà nói, bất kỳ công việc nào mà hàm thực hiện đều liên quan đến việc tính toán đầu ra. Có thể hiểu mục đích của một hàm thường là dùng để tính toán gì đó sau đó cho về một kết quả (còn nhiều mục đích khác).
Ví dụ code:
1 | let globalVariable = 10; // thành phần của chung không phải của hàm nào. |
Như đã đề cập ở trên, hàm calculateSum
đang dùng một biến được shared state
là globalVariable
khi hàm này chạy nó đã thay đổi giá trị biến “của chung”, thành ra lúc getGlobalVariable
thì lúc đầu là 10
sau lại ra 6
. Hãy tưởng tượng việc ảnh hưởng đến những thành phần bên ngoài của một hàm giống như việc bạn đạn xài đồ của công mà đi “chỉnh sửa” đồ đó vậy, vì là đồ của công nên bạn không có quyền được thay đổi lung tung khi chưa được phép.
Bạn có thể xem video dưới đây của bác Uncle Bob để hiểu ra hơn về việc vấn đề của state
nhé. Chỉ cần xem 15 phút đầu là hiểu chỗ này rồi.
Thêm một ví dụ nữa về hàm đã thỏa tiêu chí đầu tiên nhưng lại vi phạm nguyên tắc thứ hai:
1 | const impureDouble = (x) => { |
console.log
trong hàm impureDouble
thoạt nhìn thì chẳng có vẻ gì ảnh hưởng cho lắm, chúng ta đưa đầu vào và lấy được đầu ra đúng với ý muốn. Nhưng mà nó lại là cả một vấn đề với một hệ thống, khi mà hàm nào cũng console.log
thì nhìn vào thật kinh dị, dùng trong debug thì oke nhưng production thì không ổn tẹo nào (ngoài việc thực hiện công việc của nó, nó còn “in” ra console vài cái khác nữa).
Vậy chắc có bạn sẽ hỏi vậy nếu thao tác với file hệ thống hoặc bắt buộc phải thao tác với biến bên ngoài function thì làm thế nào?
Khi xem video trên thì có lẽ bạn sẽ tự trả lời được chỗ này thôi. Đối với file hệ thống hay những thành phần khác, chúng ta sẽ có thêm một function mới để “undo” lại hành động của function đó.
Ví dụ:
1 | function createFile() { |
Tóm lại
Một hàm được gọi là pure function
khi:
- Tham số truyền vào và kết quả trả về nhất quán với nhau (nhất quán với ý chúng ta mong muốn).
- Không thay đổi lung tung, không có tác dụng phụ lên thành phần nằm ngoài nó.
- Thực hiện đúng “công việc” mà cái tên hàm đã nêu ra.
Bonus: khi làm việc với object hoặc array (khi mà object hay array là biến toàn cục và chúng ta bắt buộc phải làm việc với chúng) chúng ta hay vi phạm nguyên tắc impurely
(tính không thuần khiết), nhất là điều kiện thứ hai, thay đổi object ở ngoài phạm vi của một hàm. Lúc này luôn nhớ cách an toàn để tránh thay đổi việc này là luôn clone object hoặc array đó ra rồi trả về (chỉ dùng và thay đổi đồ của mình, đồ của công thì không được sửa chữa lung tung).