- vừa được xem lúc

Tính bất biến trong Lập trình hàm: Cách tiếp cận và ứng dụng

0 0 3

Người đăng: Tom

Theo Viblo Asia

Giới thiệu

Tính bất biến (Immutability) là một trong những nguyên tắc cốt lõi của lập trình hàm. Nó đề cập đến việc một khi một đối tượng được tạo ra, trạng thái của nó không thể thay đổi. Trong bài viết này, chúng ta sẽ đi sâu vào khái niệm tính bất biến, cách áp dụng nó trong các ngôn ngữ lập trình khác nhau, và những lợi ích cũng như thách thức khi sử dụng nó.

Khái niệm tính bất biến

Tính bất biến là một thuộc tính của dữ liệu, trong đó dữ liệu không thể bị thay đổi sau khi được tạo ra. Thay vì thay đổi dữ liệu hiện có, chúng ta tạo ra một bản sao mới với những thay đổi cần thiết.

Ví dụ trong JavaScript:

// Cách tiếp cận có thể biến đổi (mutable)
let arr = [1, 2, 3];
arr.push(4); // arr giờ là [1, 2, 3, 4] // Cách tiếp cận bất biến (immutable)
const arr = [1, 2, 3];
const newArr = [...arr, 4]; // newArr là [1, 2, 3, 4], arr vẫn là [1, 2, 3]

Cách áp dụng tính bất biến trong các ngôn ngữ lập trình

1. JavaScript

JavaScript không có tính bất biến bẩm sinh, nhưng chúng ta có thể áp dụng nó bằng cách sử dụng các phương thức và toán tử không làm thay đổi.

// 1. Sử dụng Object.freeze() để tạo đối tượng bất biến
const immutableObj = Object.freeze({x: 1, y: 2}); console.log(immutableObj); // {x: 1, y: 2} // Cố gắng thay đổi giá trị sẽ không có tác dụng trong strict mode
// và sẽ ném ra TypeError trong non-strict mode
immutableObj.x = 3; console.log(immutableObj); // {x: 1, y: 2} // 2. Sử dụng spread operator để tạo bản sao mới
const newObj = {...immutableObj, z: 3}; console.log(newObj); // {x: 1, y: 2, z: 3}
console.log(immutableObj); // {x: 1, y: 2} - không bị thay đổi // 3. Làm việc với mảng bất biến
const immutableArray = Object.freeze([1, 2, 3]); // Thay vì push(), chúng ta tạo một mảng mới
const newArray = [...immutableArray, 4]; console.log(newArray); // [1, 2, 3, 4]
console.log(immutableArray); // [1, 2, 3] - không bị thay đổi // 4. Cập nhật đối tượng lồng nhau
const complexObj = Object.freeze({ name: "John", age: 30, address: { city: "New York", country: "USA" }
}); // Object.freeze() chỉ hoạt động ở mức nông, 
// nên chúng ta cần tạo một bản sao sâu để cập nhật đối tượng lồng nhau
const updatedComplexObj = { ...complexObj, address: { ...complexObj.address, city: "San Francisco" }
}; console.log(updatedComplexObj);
// {
// name: "John",
// age: 30,
// address: {
// city: "San Francisco",
// country: "USA"
// }
// } console.log(complexObj);
// {
// name: "John",
// age: 30,
// address: {
// city: "New York",
// country: "USA"
// }
// } // 5. Sử dụng const không đảm bảo tính bất biến cho đối tượng và mảng
const mutableObj = {x: 1};
mutableObj.x = 2; // Điều này vẫn hoạt động console.log(mutableObj); // {x: 2} // Để đảm bảo tính bất biến, luôn sử dụng Object.freeze()

2. Python

Python cung cấp một số cấu trúc dữ liệu bất biến như tuple và frozenset.

# Tuple là bất biến
immutable_tuple = (1, 2, 3) # Tạo tuple mới thay vì thay đổi tuple cũ
new_tuple = immutable_tuple + (4,)

3. Haskell

Haskell là một ngôn ngữ lập trình hàm thuần túy, trong đó mọi thứ đều bất biến theo mặc định.

-- Định nghĩa một danh sách
list = [1, 2, 3] -- Tạo danh sách mới bằng cách thêm phần tử
newList = 4 : list -- newList sẽ là [4, 1, 2, 3], list vẫn là [1, 2, 3]

Lợi ích của tính bất biến

  1. Dễ dàng suy luận: Khi dữ liệu không thay đổi, việc hiểu và dự đoán hành vi của chương trình trở nên dễ dàng hơn.

  2. Tránh side effects: Tính bất biến giúp giảm thiểu các tác dụng phụ không mong muốn, làm cho mã nguồn ít lỗi hơn.

  3. Hỗ trợ xử lý đồng thời: Dữ liệu bất biến có thể được chia sẻ an toàn giữa các luồng mà không cần khóa phức tạp.

  4. Dễ dàng test: Các hàm làm việc với dữ liệu bất biến thường dễ test hơn vì chúng không phụ thuộc vào trạng thái bên ngoài.

Thách thức khi sử dụng tính bất biến

  1. Hiệu suất: Việc tạo bản sao mới của dữ liệu có thể tốn nhiều bộ nhớ và thời gian CPU hơn so với việc thay đổi trực tiếp.

  2. Thay đổi tư duy: Lập trình viên quen với lập trình mệnh lệnh có thể cần thời gian để thích nghi với cách tiếp cận bất biến.

  3. Phức tạp hóa mã nguồn: Trong một số trường hợp, việc duy trì tính bất biến có thể dẫn đến mã nguồn phức tạp hơn, đặc biệt là với các cấu trúc dữ liệu lồng nhau.

Kỹ thuật tối ưu hóa khi làm việc với dữ liệu bất biến

  1. Structural Sharing: Kỹ thuật này cho phép tái sử dụng các phần của cấu trúc dữ liệu cũ khi tạo ra cấu trúc mới, giúp tiết kiệm bộ nhớ.

  2. Lazy Evaluation: Trì hoãn việc tính toán cho đến khi thực sự cần thiết, giúp tăng hiệu suất trong nhiều trường hợp.

  3. Sử dụng thư viện hỗ trợ: Các thư viện như Immutable.js trong JavaScript hoặc pyrsistent trong Python cung cấp các cấu trúc dữ liệu bất biến hiệu quả.

Kết luận

Tính bất biến là một công cụ mạnh mẽ trong lập trình hàm, mang lại nhiều lợi ích như code dễ đọc, dễ bảo trì và ít lỗi hơn. Mặc dù có một số thách thức, nhưng với sự hiểu biết đúng đắn và áp dụng các kỹ thuật tối ưu hóa phù hợp, chúng ta có thể tận dụng tối đa sức mạnh của tính bất biến trong phát triển phần mềm.

Trong các bài viết tiếp theo, chúng ta sẽ khám phá sâu hơn về các khía cạnh khác của lập trình hàm như hàm thuần túy, đệ quy, và hàm bậc cao. Hãy theo dõi series của chúng tôi để không bỏ lỡ những kiến thức hữu ích này!

Bình luận

Bài viết tương tự

- vừa được xem lúc

[Functional Programming] Part 2 - Currying

Đây là phần thứ hai trong loạt bài viết về Functional Programming của mình. Nếu bạn đã bỏ lỡ bài viết trước về Immutability và Pure Functions, thì mình khuyên bạn nên đọc nó trước (trừ khi bạn đã biết về các khái niệm này).

0 0 27

- vừa được xem lúc

[Functional Programming] Part 1 - Immutability và Pure Functions

Functional Programming được nhiều người đánh giá là rất khó học. Tuy nhiên, bất kỳ kỹ năng nào mà bạn cố gắng master mình nghĩ cũng đều sẽ có những sự khó khăn.

0 0 27

- vừa được xem lúc

Functional Programming in Kolin - Chương 1: Bắt đầu

Mở đầu. Mình chuyển sang lập trình từ lúc học, tiếp cận máy tính đến giờ cũng được hơn 2 năm.

0 0 41

- vừa được xem lúc

Khởi tạo object trong Java - có thật sự dễ?

Giới thiệu. Khởi tạo object trong Java, một vấn đề cơ bản nhưng có khá nhiều khía cạnh để phân tích.

0 0 53

- vừa được xem lúc

Để code ngắn gọn và tinh tế hơn trong Java

Lập trình khai báo (declarative programming) là một kĩ thuật mang lại rất nhiều lợi ích: code ngắn gọn hơn, dễ thay đổi, dễ bảo trì và mở rộng. Trong bài viết này, mình sẽ giới thiệu một số kĩ thuật đ

0 0 36

- vừa được xem lúc

[Functional Programming + Haskell] Bài 1 - Giới Thiệu Ngôn Ngữ Haskell

Mô hình lập trình hàm Functional Programming và các ngôn ngữ hỗ trợ xuất hiện từ rất sớm ngay khi khái niệm Declarative xuất hiện trong lĩnh vực lập trình phần mềm nói chung. Tuy nhiên do mang nặng cá

0 0 24