Khi nào nên sử dụng lập trình hàm?

6 tháng 5, 2021 By DEVERA ACADEMY

Lập trình hàm (Functional Programming) không phải là một khái niệm gì quá mới mẻ thậm chí nó đã được vận dụng rất sớm từ những năm 1930.

Không giống như mô hình lập trình theo mệnh lệnh, nơi mà chúng ta cần theo dõi và lưu trữ các trạng thái trong quá trình chạy các lệnh. Với lập trình hàm chúng ta không cần quan tâm đến các trạng thái trung gian bởi nó hoạt động dựa trên một chuỗi các hàm kết hợp với nhau để đạt kết quả mong muốn, kết quả của hàm này sẽ là đầu vào của hàm kia.

Mô hình lập trình hàm cũng có một sức ảnh hưởng mạnh mẽ và thu hút được nhiều lập trình viên. Ngày nay có rất nhiều các ngôn ngữ lập trình hiện đại vẫn hỗ trợ lập trình hàm, thậm chí còn có các ngôn ngữ thuần lập trình hàm như Clojurs, Haskell, F#.

Điều gì khiến lập trình hàm trở nên mạnh mẽ như vậy? Liệu nó có thay thế được cả ngôn ngữ lập trình mệnh lệnh không?

Đặc điểm nổi bật của Lập trình hàm

Thế mạnh đến từ mô hình lập trình hàm thể hiện rõ rệt khi chúng ta cần thực hiện các việc lọc dữ liệu, sắp xấp dữ liệu hay chọn ra một phần dữ liệu theo một yêu cầu. Ví dụ sau đây là một minh chứng:

city_companies = {
apple["Boston", "Seattle", "San Francisco"],
uniliver["London", "Sussex"],
microsoft["Seattle", "Austin", "Denver"],
amazon["Portland", "Denver", "Washington"],
starbucks["Beijing", "Seattle", "Munich"]
}
tech_giants = [:apple, :microsoft, :amazon]
tech_giants_cities = city_companies
.select {|company, citiestech_giants.include?(company)}
.map {|company, citiescities}
.flatten
.uniq

Chúng ta sẽ có một bảng băm (hash) với khóa là tên công ty, giá trị là các thành phố. Bây giờ, chúng ta cần lọc ra tên các thành phố nơi tọa lạc của các công ty lớn bao gồm Apple, Microsoft và Amazon. 

Với lập trình hướng hàm chúng ta chỉ cần lọc các hash và chỉ chọn ra (select) các công ty công nghệ lớn để duyệt đến tên các thành phố rồi sau đó tiến hành loại bỏ các bản sao. Chúng ta có thể thực hiện điều này một cách đơn giản chỉ với 5 dòng code (11-15), hơn nữa giúp code của chúng ta đơn giản và dễ đọc.

Cũng với ví dụ này, các bạn thử nghĩ xem trong lập trình dùng lệnh chúng ta giải quyết bài này như thế nào?

Đầu tiên chúng ta cần có một vòng lặp để duyệt qua tất cả các phần tử. Bên trong vòng lặp đó, chúng ta sẽ phải dùng một câu lệnh if để kiểm tra chắc chắn rằng dữ liệu chọn được là phù hợp. Kéo theo đó, chúng ta cần thêm một vài các biến tạm thời làm trung gian cho quá trình duyệt và chọn này. Khi thực hiện hết quá trình này chắc chắn bạn phải dùng hơn 5 dòng code.

Đôi khi viết code theo mô hình hướng chức năng này có thể gây nghiện cho bạn bởi tính đơn giản hóa nhiều thứ, chính vì thế mà nó cũng trở lại mạnh mẽ trong những năm gần đây. Bạn có thể dễ dàng bắt gặp lập trình hàm ngay cả trong các ngôn ngữ hiện đại như Kotlin, Swift hay Ruby.

Một vài điểm chưa thực sự hiệu quả

Việc nối chuỗi các hàm liên tiếp với nhau cũng gây ra một vài điểm hạn chế. Ví dụ như việc nối chuỗi các hàm nâng cao khác với filter hay map sẽ khiến cho code của chúng ta không còn gọn nhẹ nữa, thậm chí có thể làm giảm đi tính dễ hiểu vốn có. Ví dụ như:


Nối 4 hàm này lại với nhau hoàn toàn không phải là một điều khó khăn, vấn đề ở đây là bạn có hiểu mỗi hàm đó có chức năng gì hay không? Nếu bạn không hiểu được 1 trong số các chức năng đó thì việc debug được dòng code này đối với bạn là khó nhằn lắm đấy.

Đây là ví dụ từ ngôn ngữ Ruby và dòng code này thực hiện việc lấy thuộc tính duration từ bộ events, sau đó hàm compact sẽ xóa hết các phần tử null. Còn lại sum và round dùng để tính tổng các duration và sau đó làm tròn đến số nguyên gần nhất.

Một ngôn ngữ lập trình luôn có các hàm chuyên dụng để xử lý các cấu trúc dữ liệu, có thể kể đến như reduce(), collect(), fold() và nó có thể kết hợp với nhiều hàm khác nữa. Nhưng hãy cẩn thận trong việc lựa chọn và kết hợp các hàm sẽ sử dụng để tiết kiệm thời gian hoàn thành dự án. Teammate của bạn chắc chắn sẽ chẳng cảm thấy vui vẻ gì nếu phải đối mặt với một chuỗi hơn 10 hàm nối lại với nhau mà trong đó còn có vài hàm nâng cao.


Tránh xa những cái bẫy

Khi lập trình hàm bạn nhất định cần chú ý điều quan trọng này, đó là trật tự sử dụng hàm. Nếu bạn phải sử dụng filter (phép lọc) và map (các phép ánh xạ) trên một mảng thì cách tốt hơn (nếu có thể) bạn nên hoàn thành việc filter trước và sau đó mới thực hiện các phép tính toán. Bởi lọc mảng trước sẽ giúp bạn có giảm bớt bộ dữ liệu cần thực hiện các thao tác xử lí phía sau.


Trái lại, nếu bạn thực hiện ngược với thứ tự trên thì các tài nguyên sẽ bị lãng phí vào việc tính toán các dữ liệu sẽ bị loại trừ ở phép lọc. Như bạn có thể thấy ở trường hợp thứ 2 phép toán sắp xếp phải thực hiện trên 7 phần tử, còn trường hợp 1 chỉ cần sắp xếp trên 3 phần tử để cho ra kết quả. Một điều hiển nhiên đó là việc xử lý các bộ dữ liệu lớn sẽ làm giảm performance của hệ thống.

Kết luận

Lập trình hàm không phải là một mô hình hoàn hảo, một software engineer không thể nào hoàn thành một hệ thống phức tạp mà trong đó chỉ sử dụng phương pháp lập trình này. Hãy biết được bạn cần gì? Nếu ứng dụng của bạn không cần quan tâm đến sự trạng thái mà chỉ cần tập trung giải quyết một vấn đề cụ thể thì lập trình hàm là một giải pháp bạn nên lựa chọn, nhưng nếu thứ bạn cần là một chương trình có quản lý sự thay đổi của các thành phần bên trong thì áp dụng phương pháp này không phải là một điều đúng đắn. Chọn đúng cách tiếp cận để đạt được mục đích cũng chính là một kỹ năng!

Tác giả Better Programming

Dịch bởi Devera Academy