- Bài viết phân tích mã C của trình thông dịch ngôn ngữ K chỉ khoảng 50 dòng do Arthur Whitney tạo ra, đồng thời lý giải phong cách lập trình độc đáo của ông
- Mã nguồn chứa nhiều cấu trúc thử nghiệm khác với mã C thông thường như cú pháp nén dựa trên macro, mở rộng C phi tiêu chuẩn, và cách dùng đối số ngầm
- Tác giả tự mình diễn giải ý nghĩa của từng macro và hàm, đồng thời khám phá triết lý của các ngôn ngữ họ APL và ưu nhược điểm của mật độ mã cao
- Ưu điểm của đoạn mã là độ ngắn và khả năng cấu thành cao, còn nhược điểm là cú pháp phi tiêu chuẩn và độ dễ đọc suy giảm
- Kết luận, đoạn mã này được xem là một ví dụ cho thấy tầm quan trọng của tư duy viết mã sau khi đã hiểu trọn vẹn bài toán, hơn là chỉ học cách “viết cho ngắn”
Arthur Whitney và mã của ông
- Arthur Whitney là nhà khoa học máy tính đã thiết kế ngôn ngữ A, K, Q và các cơ sở dữ liệu kdb, Shakti
- kdb là cơ sở dữ liệu chuỗi thời gian siêu tốc được dùng trong lĩnh vực tài chính, còn Shakti là phiên bản nhanh hơn, được thiết kế để xử lý tập dữ liệu quy mô 1 nghìn tỷ dòng
- Các ngôn ngữ của ông là ngôn ngữ dựa trên mảng, chịu ảnh hưởng mạnh từ APL, đề cao tính cô đọng và sức biểu đạt toán học
- Trọng tâm của bài viết không phải là ứng dụng tài chính, mà là phân tích phong cách rất đặc biệt trong mã C do Whitney viết
Cấu trúc của trình thông dịch K 50 dòng
- Kho lưu trữ ksimple được công khai có chứa một trình thông dịch C khoảng 50 dòng mà Whitney viết chỉ trong vài ngày
- Cốt lõi của mã gồm hai tệp
a.h và a.c, nổi bật với việc rút gọn định nghĩa hàm bằng macro và cấu trúc dùng con trỏ như số nguyên
- Cú pháp
typedef char*s,c; định nghĩa s là con trỏ chuỗi và c là kiểu ký tự
s Q=(s)128; là ví dụ cho việc dùng con trỏ như số nguyên; trong toàn bộ mã, Q được dùng như một giá trị đặc biệt biểu thị trạng thái lỗi
- Nhiều cú pháp mở rộng của GCC được dùng, như statement expression dạng
({e;}) và toán tử ?:
Ý nghĩa của các macro và hàm chính
#define _(e...) ({e;}) : macro gói nhiều câu lệnh thành một biểu thức
#define i(n,e) : cách viết rút gọn cho vòng lặp, biểu diễn vòng for trên một dòng
#define Q(e) cùng các biến thể là macro xử lý lỗi; Qr, Qd, Qz lần lượt trả về lỗi rank, domain, và not-yet-implemented
- Các macro
_s, _i, f, F giúp đơn giản hóa khai báo hàm, đồng thời ngầm sử dụng các đối số x, a
ax, ix, nx... là các macro phân biệt kiểu dữ liệu và lập chỉ mục; ax dùng để xác định “x có phải atom hay không”
f(w,write(1,ax?&x:x,ax?1:strlen(x));x) là hàm xuất, in atom như ký tự, còn vector như chuỗi
Cách trình thông dịch hoạt động
- Hàm
m(x) thực hiện cấp phát bộ nhớ và tạo con trỏ kèm thông tin độ dài, với độ dài tối đa của vector là 255 byte
- Macro
g(a,v) hợp nhất xử lý phép toán trên atom/vector, và các hàm như not, sub, At, _A được định nghĩa dựa trên đó
- Macro
G(f,o) tự động sinh hàm toán tử nhị phân, hỗ trợ các phép như <, ==, +, *, &, |
cat, rev, cnt, Tak là các hàm thao tác vector; rev dùng hàm ind để tạo chỉ số đảo ngược
- Hàm
e() là bộ đánh giá đệ quy, đọc chuỗi từ phải sang trái và xử lý biến một ký tự, số, toán tử
main() nhận đầu vào, đánh giá bằng e(), rồi in kết quả theo dạng vòng lặp REPL
Đánh giá về phong cách mã
- Ưu điểm
- Tập phép toán nguyên thủy ngắn gọn được cấu thành từ các macro có thể kết hợp
- Nhờ chiều dài mã ngắn, có thể nắm toàn bộ logic trong một tầm nhìn mà không cần cuộn nhiều
- Biểu đạt mật độ cao giúp nén cấu trúc logic của mã
- Nhược điểm
- Xử lý kiểu thiếu ý nghĩa khi dùng
char* như số nguyên
- Độ dễ đọc giảm do dùng trực tiếp mã ASCII, toán tử ba ngôi phức tạp, và cú pháp phi tiêu chuẩn
- Đối số ngầm và tên biến ngắn khiến khó nắm được chủ ý
- Yếu tố trung tính
- Cú pháp dành riêng cho GCC (
?:, statement expression) khá thú vị nhưng làm giảm tính khả chuyển
- Cách dùng đối số ngầm hữu ích trong mã nhỏ, nhưng có thể gây rối trong mã lớn
- Tên ngắn có thể hiệu quả khi đã quen, nhưng sức truyền đạt ý nghĩa thì yếu
Kết luận và bài học
- Đoạn mã này không chỉ nói về “cách viết ngắn”, mà cho thấy một lối tư duy viết mã sau khi đã hiểu hoàn toàn bài toán
- Mã của Whitney là hình thức chuyển một mô hình toán học đã hoàn chỉnh vào mã, tức là “kết quả của việc biểu đạt suy nghĩ bằng code”
- Tác giả nhìn lại thói quen thường giải quyết bài toán ngay bên trong code của bản thân,
và nhấn mạnh rằng trong tương lai cần coi trọng hơn mô hình hóa khái niệm và sắp xếp tư duy trước khi viết code
- Cuối cùng, thí nghiệm này được tổng kết như một trải nghiệm rèn luyện năng lực đọc code, đồng thời khám phá sự cân bằng giữa mật độ mã và độ rõ ràng trong tư duy
Ý tưởng thử nghiệm tiếp theo
- Đề xuất các bài thực hành mở rộng trình thông dịch:
- Hỗ trợ vector số thực dấu chấm động
- Xử lý hơn 255 phần tử
- Số nhiều chữ số và tên biến nhiều ký tự
- Literal mảng và bỏ qua khoảng trắng
- Bổ sung quản lý bộ nhớ và chức năng hiển thị lỗi
- Hoàn thiện các hàm chưa được triển khai
- Những mở rộng này có thể trở thành một thử nghiệm nhằm phát triển thành một ngôn ngữ thực sự dùng được trong khi vẫn giữ phong cách mã kiểu Whitney
1 bình luận
Ý kiến Hacker News
Các macro trong đoạn mã này dùng để nén những thao tác chung Tôi đã đọc J Incunabulum trước rồi mới xem đoạn mã này, nên với các lập trình viên quen C mà bắt đầu đọc từ giữa thì phần định nghĩa macro ở đầu có thể gây bối rối Vì các macro được xây chồng lên nhau nên đoạn mã nhanh chóng leo lên chiếc thang trừu tượng hóa Tôi đặc biệt thích macro
Iterate(i), vì nó rút gọn những vòng lặp dài dòng xuống còn một ký tự Lý do kiểu mã đặc đặc như thế này khó đọc là vì nó tạo ra rất nhiều lớp trừu tượng chỉ trong vài chục dòng rồi dùng ngay lập tức Vì thế phải đọc thật chậm, từng ký tự một Từ góc nhìn của người từng làm việc với các codebase lớn gồm hàng trăm file mỏng, kiểu nén cực đoan này lại thấy khá mới mẻgrep *.[ch]mà không cần thư mục con Dự án Java đặc biệt có quá nhiều file nhỏ mà nội dung lại nghèo nàn nên rất khó tìm Có IDE thì đỡ hơn, nhưng tôi không dùng Whitney từng nói trong một cuộc phỏng vấn rằng ông muốn toàn bộ mã nằm trên một trang, và “IDE” của ông là Windows console và NotepadMuốn hiểu mã C của Arthur Whitney thì trước hết phải học các ngôn ngữ họ APL Nếu không thì nó chỉ trông như một kiểu viết C kỳ quái Whitney đang cố dùng C theo cách của APL Phong cách không có khoảng trắng, dùng tên một ký tự và tạo hàm một dòng cũng giống hệt trong APL Nó hơi giống việc một lập trình viên Pascal viết kiểu
#define begin {vậy, chỉ là Whitney còn độc đáo hơn nhiều#define begin {”, có người đùa đáp lại: “À, kiểu Stephen Bourne ấy mà”Khi tìm về Shakti tôi thấy liên kết Wikipedia chuyển hướng sang
k.nyc, mà trên trang chỉ có đúng một chữ ‘k’ Xem mã nguồn thì thật sự chỉ có mỗi `k` Nó giống như phiên bản HTML của chủ nghĩa tối giản kiểu Whitney — bỏ hết mọi thứ không cần thiết, như thể giao phần còn lại cho compiler tự ngầm xử lý
k
Bài blog này là một bài phân tích xuất sắc, bất kể đánh giá của người đọc về phong cách lập trình của Whitney ra sao Với việc tác giả viết nó chỉ trong 8 tiếng thì độ sâu rất ấn tượng, và phần kết luận đặc biệt đáng nhớ Liên kết bài gốc
Nó khiến tôi nhớ đến nỗ lực của Stephen Bourne nhằm biến C thành thứ gì đó giống Algol Nhìn vào ví dụ mac.h và ví dụ expand.c sẽ thấy cảm giác khá tương đồng
Mọi lĩnh vực đều có cái gọi là “best practice”, nhưng thứ đó chỉ thực sự hợp với trường hợp trung bình Trong một số hoàn cảnh cụ thể, đi ngược lại mới có khi là lựa chọn tốt hơn Cuối cùng thì trí tuệ tập thể nên được lấy làm mặc định, nhưng khi bắt đầu tự suy nghĩ thì bạn sẽ thấy những khe hở của nó
Tôi thích việc bài viết không tỏ ra công kích với đoạn mã mà vẫn giữ được góc nhìn cân bằng Đọc rất thú vị và tôi định sau này sẽ đọc lại
Tôi từng thắc mắc liệu kiểu lập trình này có phải là một paradigm cụ thể nào đó không Trong dự án thực tế tôi gần như chưa từng thấy mã như vậy, ngoài vài ngoại lệ kiểu “business card ray tracer” Mã nguồn của ngôn ngữ J do Whitney tạo ra cũng mang phong cách nén cực độ tương tự
Nhìn vào các định nghĩa macro sau