Suy nghĩ về Function Programming

Những suy nghĩ về pure function, immutability & declarative pattern,…

Functional Programming được cho là bắt nguồn từ khái niệm Lambda Calculus trong toán học. Không đi sâu vào lý thuyết toán học, những nguyên tắc của nó mang lại nhiều hiệu ứng tích cực trong việc phát triển phần mềm.

Ưu tiên Pure Function (Hàm thuần khiết)

Một function được gọi là thuần khiết nếu như

  • luôn trả về cùng một đầu ra cho một đầu vào
  • không có side effect

In other words, pure functions map inputs to outputs.

Chúng ta chỉ có thể thao tác với pure function thông qua các đối số (arguments) và giá trị trả về (return value).

// ❌ sử dụng side effect thay vì trả về một giá trị
function addTodo(arr) {
  arr.push("Go to school");
}

// ❌ sử dụng biến toàn cục
function addTaco() {
  return [...globalArray, "taco"];
}

// ✅ pure
function addTaco(array) {
  return [...array, "taco"];
}

Pure function rất dễ debug và unit test. Ngoài ra, nó rất rõ ràng về mặt tham chiếu nên rất dễ để áp dụng các kĩ thuật như Memoization.

Tuy nhiên, không phải tất cả mọi function đều có thể thuần khiết. Những tác vụ liên quan đến I/O, ngẫu nhiên hoá, kết nối mạng, xử lý database,… . Tuy nhiên, những khái niệm này có thể chuyển sang bên ngoài ở phạm vi lớn hơn, nó được gọi là **“function core, imperative shell”.**

Ưu tiên Immutability (tính bất biến)

Trong JavaScript, tất cả các primitives value (boolean, string, number, …) đều bất biến. Với object và array thì không như vậy, nhưng chúng ta có thể làm việc với chúng theo một cách như vậy.

When immutable, an object or array cannot be modified after it is created.

Nếu muốn cập nhật nó, chúng ta phải clone nó và gán cho một biến mới.

// ❌ mutable
const todos = ["Go to school", "Sleep"];
todos.push("Do homework");

// ✅ immutable
const todos = ["Go to school", "Sleep"];
const newTodos = [...todos, "Do homework"];

Immutable data cũng tương tự với thread safetyfailure atomic.

Bonus: với immutable object & array, chúng ta chỉ cần kiểm tra reference (tham chiếu) thay vì deep compare để phát hiện “thay đổi”, nó nhanh và đóng vai trò chính trong việc quản lý state của React.

Ưu tiên Declarative Patterns

  • Declarative programming là WHAT we want to achieve.
  • Imperative programming là HOW we want to achieve it.

Nói cách khác, imperative programming mô tả các hướng dẫn từng bước rõ ràng trong khi declarative programming bao gồm một chuỗi khai báo:

const names = ["Han", "Chewbacca", "Luke", "Leia"];

// ❌ imperative
const shortNames = [];
for (let i = 0; i < names.length; i++) {
  if (names[i].length < 5) {
    shortNames.push(names[i]);
  }
}

// ✅ declarative
const shortNames = names.filter((name) => name.length < 5);

A declarative pattern đặt trọng tâm vào vấn đề chứ không phải chi tiết triển khai. Code thường ngắn gọn hơn và ít xảy ra lỗi hơn nhiều. Ngoài ra, nó dễ đọc và dễ hiểu hơn nhiều.