Cậu khiêm tốn quá
Để tớ thử giải thích cách sử dụng FP thế nào xem có giúp cậu hiểu được không nhé?
Problem
Không có gì tốt hơn để giải thích bằng việc đưa ra 1 ví dụ. Giờ cậu thử tưởng tượng cậu phải giải quyết 1 bài toán nho nhỏ thế này:
-
Input: List các số {1, 2, 3, 4, 5}
-
Output: danh sách mới, chứa các số gấp đôi của từng phần tử trong danh sách cũ: {2, 4, 6, 8, 10}
Rồi, giờ cậu biết FP - Functional programming rồi. Nó là 1 mô thức lập trình (paradigm - dịch ra tiếng Việt hơi củ chuối, cơ mà cứ dùng tạm thế đi ) mà trong đó, function là công dân hạng nhất (first-class citizen).
Okay, giờ để sử dụng FP, cậu sẽ cần 1 thứ trước.
Cậu cần phải định nghĩa ra 1 hệ thống phức tạp hơn, nơi mà cậu sẽ có thể dựa vào đó để gắn các hàm của cậu vào để xử lý dữ liệu. Hợp lý thôi, mô thức chúng ta đang sử dụng gọi là lập trình hàm mà
High order function
Hệ thống đó được gọi là high order function - hàm nhận đối số là hàm khác. Đúng rồi, trong FP, function là công dân hạng nhất, nhưng giữa các function với nhau cũng có sự phân biệt đối xử.
Việc định nghĩa các high order function khá khó, nên để tớ làm việc đó cho cậu
Tớ sẽ định nghĩa 1 hàm magic nào đó, tên là map
- ánh xạ. Hàm này được sử dụng trên List, có tác dụng ánh xạ miền dữ liệu này qua miền dữ liệu khác.
Input: Vì nó là high order function, nó sẽ nhận parameter là hàm f(x) nào đó. Hàm f(x) nhận vào có tác dụng chỉ ra cách cậu ánh xạ như thế nào.
Output: List mới sau khi áp dụng f(x) lên từng phần tử của List cũ.
Trong bài toán của chúng ta, implement sau khi đã có hàm map trên List sẽ như thế này:
var result = {1, 2, 3, 4, 5}.map(x => 2 * x)
x => 2x là 1 hàm, nhận đầu vào là x, và trả về 2 * x. Hàm này sẽ được áp dụng trên từng phần tử của list {1, 2, 3, 4, 5}.
Voila!
Đó là hình thái đơn giản nhất của FP. Tớ đã làm phần khó nhất cho cậu, đó là viết hàm map
để áp dụng 1 hàm f(x) lên tất cả các phần tử trong list, và để cho cậu phần dễ hơn, đó là viết ra cách ánh xạ. Hàm map
ở đây cũng đủ tổng quát để cậu có thể áp dụng với các bài toán khác, như:
- Từ danh sách các object Person lấy ra danh sách tên của từng người.
- Từ danh sách các số thập phân lấy ra danh sách các số nhị phân tương ứng.
Immutable - side effect
Và cậu cũng nên biết rõ, tớ với cậu không chỉnh sửa bất cứ thứ gì trong List cũ ({1, 2, 3, 4, 5} ấy) để ra List mới sau khi dùng map. Tớ với cậu chỉ áp dụng hàm x => 2 * x trên từng phần tử của List cũ, và đưa ra 1 list mới (hay ở ví dụ trên, chúng ta không sửa danh sách object Person, hay không sửa danh sách số thập phân). Điều này rất quan trọng khi cậu phải xây dựng hệ thống các high order functions, và ở đây cậu gọi List đó immutable (không thể chỉnh sửa - tạm dịch).
Tại sao cậu lại phải lo lắng việc chỉnh sửa dữ liệu như vậy? Lý do rất đơn giản, nó sẽ giúp cậu gặp ít lỗi hơn.
Thử tưởng tượng cậu có 1 danh sách chứa fullname của mọi người, và nhiệm vụ của cậu là lấy firstname của từng người. Cậu có 2 lựa chọn:
- Cậu sửa danh sách ban đầu, vứt hết lastname và middle name đi
- Cậu tạo danh sách mới, loop trên danh sách cũ, tách từng firstname và thêm vào danh sách mới.
Nếu cậu làm theo cách thứ nhất, danh sách của cậu ko còn chứa fullname nữa. Giờ nếu 1 ai đó dùng code của cậu, và dùng list fullname kia, họ sẽ bất ngờ khi nó chỉ chứa toàn firstname thôi! Việc đó gọi là “side effect” - hiệu ứng bên lề.
Đó là 1 nguyên tắc quan trọng trong FP: cậu không sửa đổi dữ liệu cũ -> không có side effect. No error, everybody’s happy!
Tổng kết
Về cơ bản, tớ đã chỉ cho cậu hình dung dùng FP trong thực tế như thế nào. Thường những ngôn ngữ/framework hỗ trợ FP sẽ implement high order function cho cậu, và từ đó cậu sẽ định nghĩa chiến lược phù hợp cho từng bài toán cụ thể.
Giờ tớ sẽ nói về việc FP dùng khi nào, và có thay thế được OOP không. Tớ nghĩ 1 chương trình nên sử dụng cả FP lẫn OOP. 1 cách dễ hiểu nhất cho người mới như cậu, OOP thường được dùng khi cậu muốn tổ chức, mô hình hoá chương trình của cậu. FP thường được dùng nếu cậu quan tâm tới trạng thái của dữ liệu đã thay đổi ra sao qua từng bước (nhớ là chúng ta không sửa đổi dữ liệu).
Rõ ràng, 2 mô thức này có thể được sử dụng cùng nhau trong 1 hệ thống, thế nên tớ nghĩ không có lý do gì để tranh luận cái nào tốt hơn. Tốt nhất là sử dụng cả 2 mô thức một cách hợp lý, để giải quyết vấn đề của cậu.
Hi vọng bài viết này sẽ giúp cậu hiểu được FP sử dụng như thế nào, bonus thêm lý do tại sao cần sử dụng, và sử dụng khi nào. Tớ nghĩ câu trả lời được mark solution hoàn toàn không giải thích được những vấn đề này cho cậu (Opps, câu trả lời của tớ được mark solution rồi. Cảm ơn cậu nhé! )