Phong cách lập trình không tham số

Đã bao giờ bạn nghe đến cụm từ “Tacit programming” hay “Point-free style programming”? Đã bao giờ bạn nhìn thấy những đoạn code tương tự ví dụ dưới đây rồi thắc mắc rằng làm thế nào chúng có thể chạy, hay các đối số của chúng được truyền vào ở đâu?…

Point-free style sẽ giúp code chất lượng hơn, bằng cách cho phép bạn và những người bảo trì mã trong tương lai tập trung vào các hàm và ý nghĩa của chúng, thay vì chi tiết nhỏ. Trong bài viết này, chúng ta sẽ tập trung vào phong cách lập trình này, và các ưu điểm của nó — cũng như nhược điểm có thể có! Do đó, bài viết này sẽ đi theo xu hướng áp dụng các functional programming techniques để nâng cao chất lượng code của bạn. 

Mặc dù việc áp dụng phong cách này là không bắt buộc, tuy nhiên việc hiểu nó có thể hữu ích trong quá trình clean code cũng như duy trì, đọc hiểu code của người khác.

Point-free Style là gì?

Bạn có thể nghĩ về các “điểm” như là “các đối số của hàm được chỉ định một cách rõ ràng”. Phong cách không sử dụng điểm (point-free) là gì? Điều này có nghĩa là bạn đang lập trình mà không cần đặt tên cho các đối số hoặc tên bất kỳ giá trị trung gian nào của bạn. Khi bạn định nghĩa một hàm, thông thường, bạn phải đặt tên cho các đối số. Hầu hết các ngôn ngữ yêu cầu điều này, hoặc tạo ra một cách để định nghĩa hàm rất đơn giản.

Khi áp dụng phong cách này, bạn sẽ thu được hai loại hàm: những hàm ngắn với một nhiệm vụ duy nhất và tên rõ ràng, và những hàm dài hơn, phối hợp các hàm khác, sử dụng Point-free style để tăng tính đọc hiểu cho đoạn mã.

Ví dụ 

Tương tự, khi bạn đang ở trong thân của hàm, bạn đang viết về cái mà hàm này thực hiện. Thường thì cách đơn giản nhất để sử dụng kết quả của việc gọi một hàm là lưu nó vào một biến, sau đó truyền biến đó vào hàm tiếp theo. Sử dụng nó sau đó trong biểu thức.

Ví dụ đơn giản nhất để thực sự hiểu được điều này là, giả sử bạn đang gọi hàm `map` với một hàm gì đó, ví dụ như `toUpperCase`. Trên một danh sách các string, bạn đang mapping `toUpperCase` lên từng string đó.

Vậy nếu muốn áp dụng Point-free style thì làm thế nào? Trong mọi trường hợp, chúng ta không truyền đối số vào hàm; thay vì gọi .map(str => toUpperCase(str)) thì chúng ta sẽ chỉ sử dụng tên của function. Trong trường hợp này, biến “str” tại dòng số 7 được coi là một “point”.

Ở đây chúng ta đã tạo một inline function chấp nhận một đối số và chuyển nó cho toUpperCase, và bạn đặt tên tham số của nó là “str,” bạn đang giới thiệu một điểm trong mã của bạn. Thuật ngữ “điểm” ở đây đề cập đến việc rõ ràng đặt tên và tham chiếu đến các đối số của hàm hoặc biến, do đó “str” là một point.

Ưu điểm

Worth naming

Bạn không cần phải đặt tên cho bất kỳ giá trị trung gian nào cả. Chắc hẳn nhiều lúc bạn cũng không muốn suy nghĩ về việc như “Làm thế nào tôi đặt tên cho hàm này?” hoặc “Làm thế nào tôi đặt tên cho đối số này?” hay những điều tương tự như vậy. Bạn muốn suy nghĩ ở mức cao hơn, như “OK. Đây là sự kết hợp của điều này. Đây là ánh xạ của nó qua điều này.” Đó là điều bạn đang nghĩ đến. Nếu bạn có thể tránh nó mà không tốn nhiều chi phí thì việc tránh đặt tên là hoàn toàn hợp lý. Việc đặt tên chiếm nhiều không gian hơn, vì vậy đoạn code của bạn sẽ dài hơn.

Có những điều có thể dễ dàng trở nên không đồng bộ, có nghĩa là, hãy xem xét một ví dụ dưới đây, ban đầu bạn có một biến được đặt tên là fiveDays. Sau đó, hàm ban đầu trả về năm ngày được sửa đổi để trả về bảy ngày.

Sau đó, hàm được sửa đổi, nhưng tên biến vẫn giữ nguyên. Nếu bạn quên cập nhật tên biến mọi nơi mà nó được sử dụng, điều này có thể dẫn đến sự nhầm lẫn và lỗi trong code của bạn. Việc đặt lại tên đôi khi không dễ dàng, có thể bạn đã tránh được điều đó, không phải xử lý vấn đề này chút nào nếu áp dụng point-free style.

Debuggability

Việc chỉ rõ tham số của hàm thực sự có thể làm cho quá trình gỡ lỗi (debugging) trở nên dễ dàng hơn. Khi tất cả các tham số được log một cách rõ ràng, nó mang lại cái nhìn trực quan vào trạng thái của hệ thống tại một điểm cụ thể trong mã nguồn.

Loại đầu ra gỡ lỗi như vậy đặc biệt hữu ích khi bạn cần theo dõi dòng dữ liệu qua một hàm và hiểu cách các tham số tác động vào kết quả. Nó mang lại tính minh bạch và giúp xác định bất kỳ vấn đề nào liên quan đến các tham số đầu vào.

Lưu ý

Việc viết lại hàm theo phong cách Point-free không phải lúc nào cũng dễ dàng — đôi khi để chuyển sang, chúng ta cần biến đổi hàm bên trong sao cho nó phù hợp với hàm bên ngoài. Điều này đôi khi yêu cầu chúng ta sử dụng các kỹ thuật phức tạp hơn.

unary

Hãy cùng nhìn vào ví dụ sau đây:

Tại sao lại xuất hiện sự khác biệt này? Vấn đề này xuất phát từ việc phương thức `Array.map()` gọi hàm được cung cấp với ba đối số: `currentValue`, `index`, và `array`. Trong ngữ cảnh sử dụng `parseInt` trong phương thức `map`, điều này có thể dẫn đến hành vi không mong muốn. `parseInt` nhận hai đối số: chuỗi cần được chuyển đổi thành số nguyên và một hệ cơ số (radix) tùy chọn. Khi `parseInt` được sử dụng trực tiếp làm hàm gọi lại cho `map`, đối số thứ hai được `map` truyền vào (là chỉ số) vô tình được hiểu là radix bởi `parseInt`, có thể dẫn đến chuyển đổi không như mong muốn. Để tránh vấn đề này, nên sử dụng một hàm vô danh trong callback của `map` để chỉ truyền đối số cần thiết cho `parseInt`.

Trong trường hợp này, sử dụng arrow function sẽ cho kết quả đúng:

Hoặc chúng ta có thể sử dụng một hàm bậc cao để chuyển đổi hàm đã cho thành một hàm unary (hàm chỉ nhận một đối số). Trong trường hợp này, chúng ta cần đảm bảo rằng “parseInt” chỉ nhận một đối số và bỏ qua các đối số còn lại:

compose, pipe

`Compose` và `pipe` là các khái niệm trong functional programming, giúp bạn biến đổi các hàm có nhiều biểu thức và phép gán thành các phiên bản ngắn gọn hơn, không sử dụng điểm (point-free).

Giả sử bạn có một hàm lấy tên từ đối tượng sau đó trả về tên đã được định dạng thành chữ in hoa:

Thay vì viết một hàm mới nhận x, gọi g(x), sau đó chuyển kết quả đó vào f và trả về, bạn chỉ cần viết Compose f g. Bạn đã loại bỏ đối số. Hàm composition được sử dụng nhiều trong phong cách Point-free. Một cách khác liên quan rất nhiều đến hàm composition là pipelining.

Bây giờ, hãy cùng sử dụng hàm `pipe` từ thư viện functional programming (Ví dụ như Ramda.js hay lodash) để tạo một phiên bản point-free của hàm này:

Nhờ phong cách point-free cho phép bạn có thể viết các biểu thức hợp thành hàm khá dài một cách từ trên xuống, từng dòng một. Giá trị trả về của một phép toán này được truyền sang phép toán tiếp theo, và cứ như vậy. Ưu điểm là bạn không cần phải đặt tên cho các giá trị trung gian. Đây là một cách để đạt được phong cách point-free. 

Tuy nhiên, một nhược điểm rõ ràng của phong cách không sử dụng điểm là khả năng đọc hiểu có thể giảm nếu bạn sử dụng nó với tần suất dày đặc. Đây là cách luyện tập tốt, nó giúp bạn nghĩ về cấp độ hàm cao hơn. Nhưng điều này không có nghĩa là code bạn viết là phù hợp để giữ trong hệ thống của bạn lâu dài. 

Phong cách này thường thực sự hữu ích khi kết hợp với một hệ thống tốt. Khi loại bỏ đối số và tên của các giá trị trung gian, bạn có thể mất nhiều thông tin quan trọng. Nếu không có một hệ thống tốt từ đầu, việc xác định tính đúng đắn của hàm hoặc phát hiện lỗi có thể trở nên khó khăn khi đọc mã. 

Kết luận 

Tôi tin rằng việc áp dụng point-free style giúp logic code của chúng ta trở nên clean hơn, rõ ràng hơn. Tuy nhiên ưu điểm này cũng có thể dẫn đến một vài rủi ro tiềm tàng. 

Việc quyết định có sử dụng point-free style hay không thực sự là một vấn đề về coding style cũng như sở thích cá nhân, và không bắt buộc trong quá trình viết code. Phong cách code này có thể có lợi ích về tính đọc và súc tích, nhưng nó có thể không phù hợp cho mọi tình huống hoặc mọi developers, và có thể gây khó chịu, khó hiểu cho người khác khi lạm dụng quá mức. 

Do đó, sử dụng phong cách code nào phụ thuộc vào sở thích của nhà phát triển và ngữ cảnh cụ thể của mã đang được viết. Điều quan trọng nhất là đạt được sự cân bằng giữa tính đọc và tính gọn gàng, để code có thể dễ dàng maintain và hiểu được. 

Tài liệu tham khảo 

What is point-free style? 

https://en.wikipedia.org/wiki/Tacit_programming#References 

Functional JS #7: Point-free style 

Forever Functional: Pointfree Style Programming

QuynhNN

Comments

Let’s make a great impact together

Be a part of BraveBits to unlock your full potential and be proud of the impact you make.