+3

"Got me chìm sâu" cùng Functional Programming

Mở đầu

Functional Programming (FP) đã thu hút được sự chú ý đáng kể trong thế giới phát triển phần mềm và các nhà phát triển JavaScript đang ngày càng chuyển sang mô hình này để giải quyết vấn đề hiệu quả hơn và ít lỗi hơn (có thể thấy rõ ràng nhất là với lập trình reactjs). Về cốt lõi, lập trình hàm nhấn mạnh việc sử dụng các hàm thuần túy, tính bất biến và các kỹ thuật nâng cao như curring, memozition và monads để cleaner code, dễ dự đoán hơn.

Trong bài biết này sẽ đề cập đến 1 số khái niệm quan trọng nhất của FP mà chúng ta cần phải ghi nhớ

1. Pure Functions:

  • Khái niệm: Pure Functions là hàm mà nhận input sẽ luôn trả về một output và không gây ra bất kỳ tác dụng phụ nào có thể quan sát được. Khái niệm này rất quan trọng trong lập trình chức năng vì nó cho phép các nhà phát triển viết mã dễ dự đoán và kiểm tra hơn.
  • Lợi ích:
    Predictibility: Vì các hàm pure không can thiệp đến các giá trị bên ngoài scope (chỉ nhận/return input,output và xử lý logic bên trong hàm) nên hoàn toàn dễ debug.
    Reusibility: Đơn giản rồi,bản chất là nhận input và trả về output và xử lý data nên bạn có thể nhận ra nó chính là những hàm helper hay utility hay dùng 😄
    Testability: Vì nó ko thay đổi state bên ngoài,vì vậy side effect là không tồn tại => dễ dàng viết test.
  • Ví dụ:
    image.png Ví dụ trên là 1 cách viết khác (thay vì dùng switch) để xử lý hàm calculate.Như bạn thấy nó không thay đổi bất cứ giá trị nào ngoài scope cả. => để nhận biết pure func chúng ta phải thoả mãn được 2 điều kiện: không tạo side effect(không thay đổi giá trị external variables), logic được xử lý bên trong thông qua input

2. Immutability:

  • Khái niệm: Tính bất biến đề cập đến nguyên tắc không bao giờ thay đổi dữ liệu sau khi nó được tạo. Thay vì sửa đổi một object hiện có, bạn tạo một object mới với những thay đổi mong muốn. Đây là nền tảng của lập trình chức năng vì nó giúp ngăn ngừa side effects và duy trì tính toàn vẹn của dữ liệu trong suốt vòng đời của ứng dụng.Vậy trong JS đã sử dụng những gì để xử lý:
  • Sử dụng const: ngăn chặn việc gán trực tiếp lại giá trị cho biến.Tuy nhiên vẫn có thể làm thay đổi giá trị của biến reference type(object,array,...) bằng các cách khác
  • Sử dụng Object.freeze(): để khắc phục nhược điểm trên của const thì js đẻ thêm cho chúng ta method này dùng để tránh việc modify các dạng reference type.Tuy nhiên, đây vẫn chỉ là giải pháp chưa thực sự hoàn hảo vì nó chỉ chặn được modify ở level đầu tiên của object (tìm hiểu thêm shallow copy để hiểu thêm)
  • Spread và destructuring: 2 cú pháp này có trong ES6 và là cách phổ biến nhất để copy object.Tuy nhiên nhược điểm của nó vẫn là shallow copy.Với việc xử lý data nhiều level sẽ khá phức tạp. image.png Vậy làm sao để chúng ta có thể xử lý được những trường hợp trên:
  • Cách 1: Sử dụng thư viện: Có 3 thư viện cho cta chọn: Immutable.JS, Lodash(với hàm _cloneDeep()) và ImmerJS (khá tiện ích khi phải làm với data phức tạp)
  • Cách 2: Thủ công hơn tý là chúng ta phải spread data đến tận level data cần sửa => khá dài dòng
  • Vì vậy tuỳ loại data mà chúng ta sẽ sử dụng thư viện hoặc không

3. Currying:

  • Khái niệm: Currying là một kỹ thuật biến đổi trong FP trong đó một hàm có nhiều đối số được chuyển đổi thành một chuỗi các hàm, mỗi hàm nhận một đối số duy nhất. Cách tiếp cận này không chỉ làm cho các chức năng của bạn trở nên mô-đun hơn mà còn nâng cao khả năng sử dụng lại và khả năng kết hợp mã của bạn.
  • Ứng dụng thực tế của currying:
    • Event handling: 1 tip để xử lý event dễ dàng hơn bằng ví dụ sau đây: image.png
    • API calls: Trong thực tế thường được các thư viện như axios,react query, RxJS (thường gặp trong xử lý API của angular) trong đó các function được define sẵn và dev sẽ nối tiếp các hàm với nhau để xử lý. Ví dụ: Với axios: axios.create().get(url), Với rxjs và HttpClient của angular: http.get().pipe().subcripbe()

4. Memoization:

  • Khái niệm: Ghi nhớ là một kỹ thuật tối ưu hóa được sử dụng trong FP để tăng tốc tính toán bằng cách lưu trữ kết quả vào cache (cache tuỳ thuộc trình độ xử lý của dev) và trả về kết quả được lưu trong bộ nhớ đệm khi thực hiện lại function. Nó đặc biệt hữu ích trong JavaScript để tối ưu hóa hiệu suất trong các ứng dụng liên quan đến các tác vụ tính toán nặng.
  • Lợi ích:
    • Efficiency: Giảm số lượng tính toán cần thiết cho các lệnh gọi hàm lặp lại có cùng argument.
    • Performance: Cải thiện khả năng phản hồi của ứng dụng bằng cách lưu vào bộ đệm kết quả của các hoạt động tốn thời gian.
    • Scalability: Giúp quản lý các tập dữ liệu lớn hơn hoặc các thuật toán phức tạp hơn bằng cách giảm thiểu chi phí tính toán.
  • Ví dụ:
    • Trong React chúng ta có 2 hook là useCallback và useMemo để giảm số lượng rerender lại phụ thuộc vào dependacy mỗi hooks
    • Sử dụng Service Worker trong lập trình web có thể giúp giảm việc tính toán trực tiếp trên mỗi component với những bài toán đòi hòi tính toán phức tạp gây lag trình duyệt
    • Trong các ô search như google, facebook,... hay đơn giản là các ô search trên các trang tmđt có thể sẽ lưu cache data đã search dưới dạng tree

Kết bài:

Việc hiểu và tích hợp các khái niệm lập trình chức năng này có thể nâng cao đáng kể chất lượng và khả năng bảo trì của mã JavaScript. Bằng cách sử dụng song song pure functions, immutability, carrying, memoization devs có thể xây dựng các ứng dụng rõ ràng, hiệu quả và đáng tin cậy hơn. Bằng cách áp dụng các nguyên tắc liên kết này, các nhà phát triển JavaScript có thể khai thác toàn bộ tiềm năng của lập trình chức năng để viết mã tốt hơn, bền vững hơn.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí