18 điểm bởi GN⁺ 2025-04-22 | 11 bình luận | Chia sẻ qua WhatsApp
  • Pipelining là một tính năng quan trọng trong ngôn ngữ lập trình, giúp tăng khả năng đọc và bảo trì của mã
  • Đây là cách giúp biểu diễn luồng dữ liệu một cách tự nhiên từ trái sang phải, từ trên xuống dưới
  • Trong các ngôn ngữ như Rust, pipelining làm rõ luồng mã và nâng cao năng suất phát triển nhờ tính năng tự động hoàn thành của IDE
  • Nó được áp dụng trong nhiều ngôn ngữ như Haskell, Elm, SQL; builder pattern hay method chaining cũng được xem là một dạng pipelining
  • Tác động tích cực đến khả năng đọc, sự thuận tiện khi chỉnh sửa, hỗ trợ IDE, và cả công cụ quản lý phiên bản (diff, blame)
  • So với cách lồng hàm, nó giúp viết mã ngắn gọn và rõ ràng hơn, nên cũng có lợi cho cộng tác và bảo trì

Cú pháp lập trình tôi thích nhất: pipelining

Pipelining là gì?

  • Là tính năng cho phép truyền giá trị trước đó để có thể lược bỏ một đối số trong danh sách tham số
  • Giúp tăng khả năng đọc của mã và khiến việc thêm chú thích trở nên dễ dàng hơn
  • Là phong cách cú pháp tập trung vào dữ liệu, trong đó các bước xử lý liên tiếp được áp dụng tuần tự
  • Trong mã theo phong cách hàm, nó thường xuất hiện dưới dạng method chaining như .map().filter().collect()
  • Trong Rust, đoạn mã sau là ví dụ tiêu biểu:
    data.iter()  
        .filter(|w| w.alive)  
        .map(|w| w.id)  
        .collect()  
    
  • Ngược lại, nếu lồng toàn bộ hàm vào nhau thì sẽ thành cấu trúc phải đọc từ trong ra ngoài như sau:
    collect(map(filter(iter(data), |w| w.alive), |w| w.id))  
    

Vì sao pipelining lại tốt?

  • 1. Khả năng đọc và bảo trì

    • Dễ đọc từ trên xuống dưới → luồng dữ liệu trùng với thứ tự con người đọc
    • Dễ thêm chú thích cho từng dòng
    • Ngắn gọn và rõ ràng mà không cần lồng ngoặc trên những dòng dài
  • 2. Sự thuận tiện khi chỉnh sửa

    • Có thể dễ dàng thêm một hàm mới trên một dòng, như .map(), vào giữa chuỗi xử lý
    • Trong git diff hay git blame, việc theo dõi thay đổi cũng hiện ra gọn gàng, rõ ràng hơn
  • 3. Hỗ trợ IDE / LSP

    • Rất phù hợp với cấu trúc mà khi nhấn phím . sẽ hiện ra danh sách tự động hoàn thành
    • Có lợi cho phân tích tĩnh, vốn yêu cầu phải biết rõ kiểu dữ liệu
    • Để tính năng này hoạt động tốt, ngôn ngữ cần dựa trên kiểu tĩnh (e.g. Rust, TypeScript)

SQL cũng có pipelining?

  • Có những đề xuất chuyển các truy vấn SELECT lồng nhau trong SQL sang phong cách pipeline
  • Ví dụ:
    FROM customer  
    |> LEFT OUTER JOIN orders ON ...  
    |> AGGREGATE COUNT(...) GROUP BY ...  
    |> ORDER BY ...  
    
  • So với SQL hiện tại, cách này cho luồng rõ ràng hơntăng khả năng đọc
  • Nhược điểm: khi mệnh đề SELECT bị đẩy lên trên, có thể khó nắm được kiểu trả về hơn → nhưng có thể giải quyết

Liên hệ với builder pattern

  • Dạng như Builder::new().option().option().build() trong Rust là một cấu trúc pipeline điển hình
  • Khi các thiết lập tùy chọn được cấu thành bằng method, việc theo dõi mã và quản lý thay đổi trở nên dễ dàng

Cải thiện pipelining trong Haskell

  • Các toán tử như $, &, |> trong Haskell cho phép dùng pipeline thay cho hợp thành hàm
  • So sánh trước và sau:
    -- 기존  
    checkPalindromes content = unlines $ map (show . isPalindrome) $ lines $ map toLower content  
    
    -- 개선  
    checkPalindromes content =  
      content  
        & map toLower  
        & lines  
        & map (show . isPalindrome)  
        & unlines  
    

Ưu điểm của pipelining trong Rust

  • Method chaining, suy luận kiểu, và khả năng mở rộng có cấu trúc dựa trên trait đều kết hợp rất tốt với pipelining
  • Rust có cấu trúc như thể chỉ chọn lấy ưu điểm của cả cú pháp hàm lẫn hướng đối tượng, nên việc dùng pipelining trở nên tự nhiên nhất

Kết luận

  • Pipelining không chỉ là một cú pháp đơn thuần mà là một tính năng cốt lõi ảnh hưởng đến luồng mã, khả năng chỉnh sửa và cả cộng tác
  • Thay vì kiểu lồng như f(g(h(x))), cấu trúc x |> h |> g |> f thân thiện với con người hơn
  • Dưới quy tắc đơn giản “mỗi dòng một thao tác”, pipelining là cách tốt nhất để biểu đạt một luồng xử lý tự nhiên

“Mỗi mảnh ống nhận dữ liệu chính, thực hiện đúng một công việc. Cuối cùng, nếu đặt cho nó một cái tên rõ ràng, đó sẽ là cấu trúc mã lý tưởng nhất.”

11 bình luận

 
progdesigner 2025-04-23

Có lẽ cũng theo ngữ cảnh tương tự như việc xuống dòng và thụt lề quan trọng với khả năng đọc của bất kỳ văn bản nào.

 
forgotdonkey456 2025-04-23

LINQ là số một!

 
bus710 2025-04-23

Gleam cũng hỗ trợ cái này nên có thể viết code khá gọn gàng.

Mà không biết có phải vì trong bài có khối code không, mà trên di động nó cũng hiện theo bố cục desktop.

 
bus710 2025-04-23

Nghĩ lại thì elm cũng làm được.

 
galadbran 2025-04-22

Với lượng dữ liệu nhỏ và mức độ mã đơn giản như ví dụ ở trên thì tôi nghĩ nhìn cũng ổn, không có gì tệ.

Nhưng khi code dần được nhét thêm vào trong map() ... thì nó có xu hướng khiến mã ngày càng phình to hơn,
và tuy còn tùy ngôn ngữ hay thư viện triển khai mà mức độ ảnh hưởng khác nhau, nhưng khi lượng dữ liệu tăng lên thì so với cách đơn giản là chất dữ liệu vào cấu trúc dữ liệu hoặc vừa thao tác vừa xử lý, nó cũng có thể dễ dàng chậm hơn đến hàng nghìn lần.

Và rồi lại có thêm một lý do mới khiến tôi không còn thích nó nữa: khi xem bài này trên điện thoại, chiều rộng kiểu như trên PC vẫn bị giữ nguyên nên cỡ chữ bé tí như hạt bụi, thành ra đọc bài quá khó T.T

Về cơ bản tôi không thích nó, và cũng không cố tình nỗ lực để viết theo kiểu đó.

 
bichi 2025-04-22

Cho js nữa đi |> cúi đầu nài nỉ

 
secret3056 2025-04-22

|> quá đẹp

 
howudoin 2025-04-22

Đây là cú pháp tôi ghét nhất
Chỉ cần stack trace hơi rối một chút thôi là việc debug đã tệ kinh khủng

 
cosine20 2025-04-25

Chuẩn luôn

 
GN⁺ 2025-04-22
Ý kiến trên Hacker News
  • Tác giả gọi đây là "pipelining", nhưng tôi nghĩ thuật ngữ đúng phải là "method chaining"

    • So sánh với pipeline đơn giản trong Bash: mỗi thành phần chạy song song và kết quả trung gian được stream
    • Trong Ruby, mỗi dòng được xử lý tuần tự và một mảng hoàn chỉnh được tạo ra giữa từng bước
    • Việc debug trở nên khó hơn, nên dạo này tôi viết mã tường minh hơn
    • Mã tường minh nhìn kém gọn gàng hơn, nhưng có thể dễ dàng kiểm tra trạng thái trung gian
  • Cá nhân tôi ủng hộ việc giữ bộ tính năng của ngôn ngữ nhỏ gọn và nhanh chóng đạt đến một bộ tính năng hoàn chỉnh

    • Tuy vậy, tôi vẫn mong mọi ngôn ngữ đều áp dụng cú pháp |> của Elixir
  • Macro Lisp cung cấp một lời giải tổng quát, không chỉ cho các toán tử thao tác trên collection theo chuỗi mà còn cho việc quyết định thứ tự của chuỗi lời gọi

    • Ví dụ, có thể viết (foo (bar (baz x))) thành (-> x baz bar foo)
    • Vẫn xử lý được cả khi có thêm đối số
    • Xem thêm hướng dẫn về threading macro của Clojure để biết chi tiết
  • Tôi học thuật ngữ này là fluent interface. Pipelining là thứ khác

  • Toán tử pipeline là một dạng partial application, cho phép bind nhiều đối số để tạo hàm mới rồi truyền đầu ra của nó sang hàm khác

    • Partial application rất hữu ích khi viết chương trình, và một ngày nào đó các ngôn ngữ (không phải Haskell) sẽ dùng nó làm nền tảng cho việc cấu thành chương trình
  • Người dùng tidyverse của R đã dùng nó từ lâu

  • Pipelining khó debug. Việc xử lý ngoại lệ khó khăn nên phải thêm phân nhánh vào pipeline

    • Pipeline chỉ hữu ích khi lập trình theo happy path
  • Cú pháp SQL phức tạp một cách không cần thiết

    • SQL vốn đã là một ngôn ngữ toán tử, nhưng bị ràng buộc nhiều vì lý do lịch sử
    • Nếu cho phép cú pháp mới thì có thể viết đơn giản hơn nhiều
    • Cú pháp |> thiếu tính biểu đạt và làm tăng nhiễu thị giác
  • Tác giả nói rằng "ngữ nghĩa thắng cú pháp", nhưng lại đang tập trung vào sở thích về cú pháp

    • Pipelining càng khó debug khi chuỗi càng dài
    • Có chỉ trích Python nhưng không đưa ra lý do cụ thể
    • Định nghĩa của "pipelining" không rõ ràng
  • effect-ts cho phép viết cả pipeline lẫn mã mệnh lệnh

    • Có tài liệu về cách viết pipeline và dùng generator
    • Phần lớn cộng đồng cuối cùng đã chuộng generator theo phong cách mệnh lệnh hơn
    • Có vẻ dễ debug và bảo trì hơn