Tại sao Janet lại là ngôn ngữ lập trình bạn nên thử ngay bây giờ?

Công nghệ02 tháng 6, 2026·11 phút đọc

Janet là một phương ngữ Lisp hiện đại, đơn giản và dễ học nhưng lại cực kỳ mạnh mẽ. Bài viết này khám phá những ưu điểm nổi bật của Janet, từ khả năng biên dịch thành tệp thực thi nhỏ gọn, xử lý văn bản vượt trội cho đến hệ thống macro linh hoạt, khiến nó trở thành công cụ lý tưởng cho các dự án lập trình cá nhân và chuyên nghiệp.

Tại sao Janet lại là ngôn ngữ lập trình bạn nên thử ngay bây giờ?

Tôi chưa bao giờ nghĩ rằng mình sẽ rơi vào tình thế này. Nghĩa là, dùng dấu ngoặc đơn? Vào thời đại này? Nhưng trong vài năm qua, ngôn ngữ lập trình ưa thích của tôi cho các dự án phụ thú vị là một phương ngữ Lisp nhỏ bé tên là Janet.

Tôi yêu Janet đến mức đã viết một cuốn sách toàn tập về nó và đăng tải miễn phí lên Internet, với hy vọng thu hút thêm nhiều lập trình viên đến với ngôn ngữ này. Tôi nghĩ bạn nên đọc nó, nhưng tôi biết bạn sẽ không tin lời tôi nói ngay lập tức. Vì vậy, tôi sẽ cố gắng thuyết phục bạn. Đây là bài chào hàng của tôi: đây là lý do tại sao bạn — đúng là bạn — nên cho Janet một cơ hội.

Janet cực kỳ đơn giản

Janet là một ngôn ngữ mệnh lệnh (imperative) với các hàm hạng nhất (first-class functions), một không gian tên duy nhất cho các định danh và phạm vi khối từ vựng. Lõi của ngôn ngữ này rất nhỏ, chỉ bao gồm tám hướng dẫn: do, def, var, set, if, while, break, fn. Nhưng nhờ vào macro, có rất nhiều lớp bao bọc cấp cao mang lại cho bạn các luồng điều khiển mạnh mẽ hoặc tiện lợi hơn.

Bạn có thể "học" Janet trong một buổi chiều, vì ngữ nghĩa thời gian chạy cực kỳ quen thuộc: hãy tưởng tượng đến JavaScript, cộng thêm các kiểu giá trị, trừ đi những điều kỳ quặc (wats). Và phần còn lại của ngôn ngữ rất nhỏ: toàn bộ thư viện chuẩn vừa vặn trong một trang giấy. Chính sự dễ dàng bắt đầu này đã khiến tôi bị cuốn hút ngay từ đầu.

Dễ dàng phân phối và triển khai

Rất dễ dàng để biên dịch các chương trình Janet thành các tệp thực thi gốc (native executables) liên kết tĩnh với thời gian chạy Janet. Và bạn có thể chia sẻ các chương trình đó với người khác mà không yêu cầu họ cài đặt Janet trước — hay các phụ thuộc của dự án bạn, hay bất cứ thứ gì khác. Bạn thậm chí không cần phải nói với họ rằng nó được viết bằng Janet!

Cách Janet thực hiện điều này rất tinh tế: Janet tự biên dịch thành bytecode, sau đó ghi bytecode đó vào một tệp .c cũng khởi chạy thời gian chạy Janet. Sau đó nó biên dịch tệp C đó bằng trình biên dịch C của hệ thống bạn. Vì Janet được thiết kế để dễ dàng nhúng (embed), điều này hoàn toàn hợp lý: về cơ bản, nó đang tự nhúng vào một tệp thực thi C tầm thường.

Một chương trình "hello world" đơn giản của Janet được biên dịch thành binary gốc nặng dưới một megabyte (784K cho Janet 1.27.0 trên macOS aarch64, nhưng kết quả có thể khác nhau). Điều này bao gồm toàn bộ thời gian chạy Janet, bộ thu gom rác (garbage collector), và thậm chí cả trình biên dịch bytecode — vì vậy bạn có thể viết các chương trình đánh giá mã Janet tại thời gian chạy nếu muốn.

Điều này làm cho Janet trở thành lựa chọn tuyệt vời để viết các ứng dụng dòng lệnh nhỏ. Điều này đặc biệt đúng khi bạn xem xét rằng...

Janet có khả năng xử lý văn bản "phi thực tế"

Thay vì biểu thức chính quy (regular expressions), việc xử lý văn bản của Janet dựa trên ngữ pháp biểu thức phân tích (Parsing Expression Grammars - PEGs). PEGs đơn giản, mạnh mẽ và dễ dự đoán hơn biểu thức chính quy. Chúng không dựa trên dòng, nên có thể phân tích văn bản đa dòng mà không gặp vấn đề gì. Chúng cũng có thể phân tích HTML, JSON hoặc bất kỳ ngôn ngữ không chính quy nào khác. Chúng thậm chí có thể phân tích các định dạng tệp nhị phân — chúng không gặp vấn đề gì với các byte null tùy ý.

Chúng thực sự là các trình phân tích (parsers): có cấu trúc, có thể kết hợp (composable), và là các trình phân tích hạng nhất. Và chúng khá dễ học!

Janet có DSL xử lý tiến trình con tốt nhất trong mọi ngôn ngữ cấp cao

Có một thư viện bên thứ ba gọi là sh cung cấp một DSL (ngôn ngữ dành riêng cho lĩnh vực) kịch bản shell cho phép bạn diễn đạt các đường ống (pipes) và chuyển hướng (redirects) trực tiếp trong Janet. Như thế này:

($ find . -name *.janet | say)

Thật đáng kinh ngạc. Đó là một DSL tuyệt vời đến mức tôi đã dành một chương trọn trong cuốn "Janet for Mortals" cho nó — và cho những điều bạn có thể làm với nó. Nó nâng Janet từ một sự thay thế hợp lý cho Perl thành một sự thay thế hợp lý cho Bash cho một phạm vi chương trình surprisingly lớn.

Janet có thể nhúng (Embeddable)

Lua đã trở thành "ngôn ngữ nhúng" mặc định, điều đó thật đáng tiếc, vì... ừm, đây không phải là bài viết về Lua. Bạn có thể không quá bận tâm về điều này, nhưng có khả năng chỉ là bạn chưa thử nó yet: việc có thể viết các chương trình hiển thị giao diện kịch bản là một siêu năng lực khá thú vị.

Nhúng Janet rất dễ dàng: thời gian chạy Janet là một thư viện C nhỏ, và tất cả những gì bạn phải làm là liên kết nó vào sau đó gọi các hàm C thông thường để thao tác các giá trị Janet. Bạn thậm chí có thể nhúng nó vào các trang web và viết các trang tĩnh với các DSL tùy chỉnh có thể lập trình!

Janet hỗ trợ bộ sưu tập có thể thay đổi và bất biến

Các kiểu bộ sưu tập của Janet có cả phiên bản có thể thay đổi (mutable) và bất biến (immutable). Bộ sưu tập bất biến có ngữ nghĩa giá trị: vector bất biến [1 2] không thể phân biệt được với (take 2 [1 2 3]), mặc dù thực tế là chúng có địa chỉ bộ nhớ khác nhau. Mặt khác, bộ sưu tập có thể thay đổi có ngữ nghĩa tham chiếu: bảng băm @{:x 1 :y 2} chỉ bằng chính nó. Một bảng băm khác với cùng các khóa và giá trị là một đối tượng riêng biệt.

Không phải ngôn ngữ nào cũng có các giá trị tổng hợp bất biến được tích hợp ngay trong thư viện chuẩn!

Macro, macro, và macro

Tôi nghĩ đây là lý do thực sự bạn nên học Janet, nhưng tôi không muốn dẫn đầu bằng nó vì tôi không muốn làm bạn sợ hãi.

Bạn có thể viết Janet hoàn toàn tốt mà không cần bao giờ học cách viết macro. Nhưng bạn nên học cách, vì viết macro rất thú vị. Nó cảm thấy khác với bất kỳ loại lập trình nào tôi từng làm trước đây.

Viết macro đòi hỏi phải suy nghĩ hai lần cùng một lúc: bạn đang viết mã để viết mã, vì vậy bạn phải giữ hai luồng thực hiện thẳng thắn trong tâm trí: mã đang chạy ngay bây giờ, tại thời gian biên dịch, thao tác các giá trị và cây cú pháp trừu tượng, và mã mà bạn đang thao tác, mã ứng dụng mà bạn tạo ra, mã sẽ chạy trong tương lai.

Macro của Janet không vệ sinh (hygienic), và Janet không có không gian tên riêng cho các hàm. Nhưng bằng cách cho phép bạn bỏ trích (unquote) các hàm chữ, Janet giúp việc viết các macro hoàn toàn minh chứng tham chiếu (referentially transparent) trở nên khả thi. Đó là một giải pháp cực kỳ đơn giản và tinh tế cho một vấn đề rất nhạy cảm khác. Và việc có thể làm điều này trong Janet làm nổi bật tính năng yêu thích tiếp theo của tôi...

Janet cho phép chuyển giá trị từ thời gian biên dịch sang thời gian chạy

Đây là điều thú vị nhất về Janet, theo ý kiến của tôi. Nhưng có thể nó không nghe có vẻ thú vị lúc đầu — thực ra tất cả những gì nó có nghĩa là bất kỳ giá trị Janet nào cũng có thể được tuần tự hóa (serialize) vào đĩa và đọc lại sau đó.

Nhưng việc tuần tự hóa này là ngầm định: khi bạn biên dịch một chương trình Janet, nó chạy tất cả các hướng dẫn cấp cao nhất — các câu lệnh thông thường, khai báo hàm, bất cứ thứ gì — và sau đó, một khi thực hiện xong tất cả các giá trị cấp cao nhất, Janet ghi một ảnh chụp nhanh (snapshot) trạng thái chương trình của bạn xuống đĩa.

Và đó là một ảnh chụp nhanh đầy đủ trạng thái chương trình của bạn: các tham chiếu chia sẻ được bảo toàn, vì vậy các giá trị có thể thay đổi vẫn có thể được thay đổi sau khi bạn "tiếp tục" (resume) ảnh chụp nhanh. Các trình tạo (generators) nhớ chính xác hướng dẫn nào chúng cần chạy lần tiếp theo bạn tiếp tục chúng. Các bao đóng (closures) sẽ đóng lại.

Macro là một trường hợp đặc biệt của thực thi mã thời gian biên dịch — thao tác cây cú pháp trừu tượng để tạo các hàm mới — nhưng đây là một siêu năng lực bạn có thể thưởng thức mà không cần bất kỳ macro nào cả. Đang làm trò chơi? Reticulate các đường spline của bạn trước! Hoặc nhúng tài sản vào binary cuối cùng của bạn bằng cách đọc tệp tại thời gian biên dịch — bạn có thể thực hiện các tác dụng phụ tùy ý!

"Janet for Mortals" có một ví dụ về việc sử dụng điều này để tự tạo các ràng buộc cơ sở dữ liệu dựa trên tệp lược đồ SQL — một ví dụ hơi ngớ ngẩn, nhưng điều gì đó sẽ khá khó để thực hiện trong hầu hết các ngôn ngữ.

Janet cảm giác tốt khi cầm nắm

Điều này hoàn toàn chủ quan, nhưng tôi yêu cú pháp của Janet. Nó đạt sự cân bằng hoàn hảo giữa sự đơn giản, tính đồng nhất và sự đa dạng.

Nó sử dụng dấu ngoặc đơn lan tỏa, nhưng phá vỡ chúng bằng [] cho danh sách và {} cho bảng.

Ký tự có thể thay đổi luôn có tiền tố @: @"mutable string", {:immutable hash-table}, v.v.

Các hàm ẩn danh được viết (fn [x] (+ 1 x)), nhưng có một ký hiệu viết tắt để nâng bất kỳ biểu thức nào thành hàm với |: |(+ 1 $).

Janet hỗ trợ "splats" hoặc "spreads" với ;: (+ ;args).

Các chuỗi ký tự có thể được viết với bất kỳ số lượng backtick nào, và đóng với cùng số lượng backtick đó. Các chuỗi thoát như \n không áp dụng trong các chuỗi được trích dẫn bằng backtick, vì vậy bạn có thể tạo các chuỗi với bất kỳ nội dung nào mà không bao giờ phải nghĩ về cách thoát chúng — tất cả những gì bạn phải làm là bọc chúng trong một số lượng backtick đủ lớn.

Các tham số còn lại sử dụng & thay vì .: (defn foo [first & rest] ...).

Janet không hỗ trợ macro đọc (reader macros), vì vậy cú pháp chính thức là cố định. Nếu bạn biết cách đọc Janet, bạn có thể đọc tất cả các chương trình Janet. Điều này không có nghĩa là bạn có thể hiểu chúng...

Janet ưu tiên sự thoải mái hơn truyền thống

Janet không tuân theo các phong tục cổ xưa. CAR được gọi là first. PROGN được gọi là do. LAMBDA là fn, và SETQ là def. nil không phải là danh sách trống; nó là kiểu riêng của nó, và có các giá trị Boolean hạng nhất trong ngôn ngữ. Nó tránh xa EQ, EQL, EQUAL và EQUALP. Không có danh sách liên kết nào trong tầm nhìn.

Điều này thực sự không tốt cũng không xấu, nhưng tôi nghĩ đáng để nhắc đến: nếu bạn thấy dấu ngoặc đơn và giả định FORMAT không xa đâu, hãy thử nhìn Janet lần nữa.

Chia sẻ:FacebookX
Nội dung tổng hợp bằng AI, mang tính tham khảo. Xem bài gốc ↗