- Janet là một phương ngữ Lisp nhỏ, nhưng có ngôn ngữ mệnh lệnh, hàm hạng nhất, không gian tên định danh đơn và phạm vi khối từ vựng, nên có thể bắt đầu một cách đơn giản
- Ngôn ngữ lõi chỉ gồm 8 lệnh
do, def, var, set, if, while, break, fn, còn macro tạo ra các lớp bao điều khiển luồng mạnh hơn hoặc tiện hơn
- Khả năng phân phối được đảm bảo bằng cách biên dịch chương trình Janet thành tệp thực thi native được liên kết tĩnh với Janet runtime, nên không yêu cầu người dùng cài Janet hay phụ thuộc nào
- Parsing Expression Grammar (PEG) đơn giản, mạnh mẽ và dễ dự đoán hơn regex, còn sh cho phép biểu diễn pipe và redirect ngay trong Janet, mở rộng phạm vi viết CLI
- Thực thi ở thời điểm biên dịch chạy trước các lệnh top-level rồi lưu snapshot trạng thái chương trình xuống đĩa, nhờ đó có thể chuyển giá trị, tham chiếu dùng chung, generator và trạng thái closure sang runtime mà không cần macro
Lõi đơn giản
- Janet là một ngôn ngữ mệnh lệnh với hàm hạng nhất, không gian tên định danh đơn và phạm vi khối từ vựng
- Phần lõi của ngôn ngữ được giữ nhỏ gọn với 8 lệnh
do, def, var, set, if, while, break, fn
- Macro cho phép tạo các lớp bao điều khiển luồng cấp cao mạnh hơn hoặc tiện hơn
- Ngữ nghĩa runtime quen thuộc, và phần còn lại của ngôn ngữ cũng nhỏ đến mức toàn bộ thư viện chuẩn nằm gọn trong một trang
Phân phối native và nhúng
- Chương trình Janet có thể dễ dàng được biên dịch thành tệp thực thi native liên kết tĩnh Janet runtime
- Người dùng nhận bản phân phối không cần cài Janet, phụ thuộc của dự án hay bất kỳ thành phần bổ sung nào khác
- Janet biên dịch chính nó thành bytecode, sau đó ghi bytecode đó vào một tệp
.c khởi động Janet runtime, rồi biên dịch tệp C đó bằng trình biên dịch C của hệ thống
- Một binary native “hello world” đơn giản nhỏ hơn 1MB; với Janet 1.27.0 trên macOS aarch64, kích thước là 784K
- Binary này còn chứa toàn bộ Janet runtime, garbage collector, và cả bytecode compiler, nên cũng có thể tạo chương trình đánh giá mã Janet tại runtime
- Janet runtime là một thư viện C nhỏ, nên sau khi liên kết có thể thao tác giá trị Janet bằng cách gọi các hàm C thông thường
- Cũng có thể nhúng vào website để tạo các trang tĩnh với DSL lập trình tùy biến cho người dùng như Toodle
Phân tích cú pháp văn bản và DSL cho tiến trình con
- Xử lý văn bản của Janet dựa trên parsing expression grammar thay vì regex
- Parsing expression grammar đơn giản, mạnh mẽ và dễ dự đoán hơn regex, đồng thời không bị bó buộc theo dòng nên có thể phân tích văn bản nhiều dòng
- Có thể phân tích HTML, JSON và các ngôn ngữ không chính quy khác, cũng như xử lý định dạng tệp nhị phân có chứa các null byte tùy ý
- sh là một DSL shell scripting của bên thứ ba cho phép biểu diễn trực tiếp pipe và redirect trong mã Janet
($ find . -name *.janet | say)
- DSL này nâng Janet từ một lựa chọn thay thế hợp lý cho Perl thành một lựa chọn thay thế hợp lý cho Bash trong phạm vi chương trình khá rộng
Collection và cảm giác cú pháp
- Các kiểu collection của Janet đều có cả dạng mutable và immutable
- Collection immutable có ngữ nghĩa giá trị, nên vector immutable
[1 2] không khác với (take 2 [1 2 3]) dù địa chỉ bộ nhớ khác nhau
- Collection mutable có ngữ nghĩa tham chiếu, nên hash table `@{:x 1 :y 2}`` chỉ bằng chính nó và một hash table khác có cùng khóa và giá trị vẫn là một đối tượng riêng biệt
- Cú pháp dùng ngoặc rộng rãi, nhưng dùng
[] cho list và {} cho table để phân biệt hình dạng
- Literal mutable luôn có tiền tố
@, như @"mutable string"
- Hàm ẩn danh được viết là
(fn [x] (+ 1 x)), và cũng có cú pháp rút gọn nâng biểu thức thành hàm bằng |, như |(+ 1 $)
- Splat hoặc spread dùng
;, như (+ ;args)
- Chuỗi backtick có thể mở bằng số lượng backtick tùy ý và phải đóng bằng đúng số lượng đó; bên trong chuỗi backtick, các escape sequence như
\n không có hiệu lực
- Tham số còn lại dùng
& thay vì ., nên viết như (defn foo [first & rest] ...)
- Janet không hỗ trợ reader macro, nên bản thân cú pháp là cố định; nếu đã biết đọc Janet thì có thể đọc mọi chương trình Janet
Macro và trạng thái thời điểm biên dịch
- Macro Janet là mã dùng để viết mã, và ở thời điểm biên dịch nó đồng thời xử lý các giá trị cùng cây cú pháp trừu tượng của luồng thực thi hiện tại và luồng mã ứng dụng sẽ chạy trong tương lai
- Macro Janet không hygienic, và cũng không có không gian tên riêng cho hàm
- Tuy vậy, có thể unquote literal function để viết macro hoàn toàn trong suốt tham chiếu
- Khi biên dịch chương trình Janet, trước tiên nó thực thi các lệnh top-level, câu lệnh thông thường, khai báo hàm... rồi ghi một snapshot trạng thái chương trình ra đĩa
- Snapshot này giữ nguyên các tham chiếu dùng chung, nên sau khi khởi động lại vẫn có thể tiếp tục thay đổi các giá trị mutable
- Generator ghi nhớ lệnh cần chạy ở lần resume tiếp theo, còn closure cũng giữ lại các giá trị đã đóng
- Macro là một dạng đặc biệt của thực thi mã ở thời điểm biên dịch, nhưng khả năng này vẫn dùng được ngay cả khi không có macro
- Trong game có thể tiền xử lý spline, có thể đọc tệp ở thời điểm biên dịch để đưa asset vào binary cuối cùng, và cũng có thể thực hiện các side effect tùy ý
- Janet for Mortals cho thấy ví dụ tự động sinh database binding dựa trên tệp schema SQL, và cho rằng kiểu công việc này khá khó trong phần lớn ngôn ngữ khác
Thoải mái hơn truyền thống Lisp
- Janet không giữ nguyên các quy ước Lisp cũ
CAR được đặt tên là first, PROGN là do, LAMBDA là fn, SETQ là def
nil không phải danh sách rỗng mà là một kiểu độc lập, còn boolean là giá trị hạng nhất
- Janet tránh họ
EQ, EQL, EQUAL, EQUALP, và linked list cũng hầu như không xuất hiện
2 bình luận
Ý kiến trên Hacker News
Janet có một số điểm còn thiếu. Chủ yếu là quản lý gói và chỉ định phiên bản còn yếu, và nhìn chung còn thiếu các thư viện như định tuyến HTTP nâng cao
Dù vậy, tôi rất thích việc có thể tạo binary và script bằng JPM, và tính di động của nó cũng rất tốt. Trước đây tôi từng thử đưa ngôn ngữ lập trình Janet lên máy chơi game Playdate như một bản proof of concept
Tôi thích viết code bằng Janet, nhưng mỗi lần như vậy thì việc mọi người tưởng tôi là người tạo ra ngôn ngữ này cũng hơi khó xử
Sẽ hay nếu có một phiên bản “Janet viết về Janet”
Nó cho phép vendor dependency và dễ dàng cài đặt các bundle Janet hiện đại mà không cần jpm
Nếu bạn cởi mở với phát triển LLM, có thể để LLM viết wrapper còn logic thực tế thì viết bằng Janet
Cùng tác giả đó còn có một ngôn ngữ tương tự ra đời sớm hơn là Fennel. Nó biên dịch sang Lua và phần triển khai cũng hoàn toàn viết bằng Lua
Nó không có thư viện chuẩn riêng, nên thiếu khá nhiều thứ hay ho như thư viện parser của Janet, nhưng lại rất phù hợp để viết script trong các môi trường nhúng Lua
https://fennel-lang.org/
Kết nối giữa Fennel và Lua VM rất mong manh, và chất lượng còn chưa bằng một nửa debugger và REPL của Janet. Fennel có tính di động tốt hơn nhiều, và nhờ LuaJIT mà thậm chí có thể vượt xa SBCL, nên điều này càng đáng tiếc
Nhưng tôi nghĩ trải nghiệm transpile thật sự kéo nó lại. Có cách lách, nhưng ngay cả khi triển khai
debug.setinfothì bạn vẫn sẽ gặp những ca biên như khốimatchkhá khó chịuTôi cho rằng sẽ rất đáng giá nếu fork LuaJIT2 để sửa phần debug và cấu trúc lỗi cho phù hợp hơn với tính minh bạch ở cấp ngôn ngữ. Khi đó những ngôn ngữ như Fennel sẽ hấp dẫn hơn rất nhiều
Tác giả bài viết trước đây đã làm những công cụ này bằng Janet, từng được nhắc tới trên HN
https://bauble.studio
https://toodle.studio
Nhờ hai công cụ nghệ thuật thú vị này mà tôi đã kỳ vọng khá nhiều vào Janet trong một thời gian
Tôi luôn thấy vui khi Janet nhận được sự chú ý. Một trong những tính năng hiện đại mà tôi muốn nhắc tới là sandbox
“Vô hiệu hóa các tập tính năng để trình thông dịch không thể sử dụng một số tài nguyên hệ thống nhất định. Một khi đã vô hiệu hóa thì không có cách nào bật lại các tính năng đó.”
https://janet-lang.org/api/misc.html#sandbox
Nhìn thấy “SETQ is def” thì ban đầu tôi đã buột miệng “gì cơ?”. Vì SETQ không tạo binding mà chỉ cập nhật thôi
Đọc tài liệu (https://janet-lang.org/docs/bindings.html) thì có vẻ tác giả thực sự đã nhầm, vì ở đó ghi rằng “binding được tạo bằng def là bất biến”. Có lẽ ý họ là “SETQ is set”
Tôi rất muốn thích Janet vì nó trông như điểm cân bằng hợp lý giữa Guile, Tcl và CL, nhưng tôi có phản xạ bài xích bản năng với việc dùng vector ngoặc vuông cho lambda và toán tử điều khiển luồng. Với Clojure tôi cũng vậy, rất khó để vượt qua, dù có lẽ nếu cố đủ nhiều thì vẫn được
Ngoài ra tôi cũng tò mò tình trạng hiện tại của LSP/SLIME ra sao. Dạo này đó là thứ khá quan trọng
Khi dùng ngoặc tròn, phần tử đầu tiên của danh sách sẽ quyết định cách diễn giải phần còn lại. Ví dụ
(func a b c)là gọi hàm,(macro x y z)là mở rộng macro, còn([p q r] …)là phần thân hàm “trần”, bắt đầu bằng một vector tham số rồi theo sau là các biểu thức thực thiNgoặc vuông được dùng khi các phần tử đều cùng một “loại” và phần tử đầu tiên không có gì đặc biệt. Ví dụ
(defn f [a b c] …)là một nhóm tham số cùng loại và tham số đầu tiên không đặc biệt, còn(let [a 1 b 2] …)cũng là một nhóm binding và binding đầu tiên không đặc biệtNgoại lệ duy nhất tôi nghĩ ra là khi
casedùng để nhóm nhiều phần tử khớp, nhưng đó là vì tính tiện dụng. Sau khi hiểu được logic này thì tôi đã đổi ý, và từ đó thấy nó khá đẹp[1 2 3]có thể viết(array 1 2 3), và thay vì(fn [x] (+ 1 x))có thể viết(f (x) (+ 1 x))Nó không phải bắt buộc
Với các script hệ thống vượt quá một độ dài nhất định, Janet đã thay thế sh, Python, awk... đối với tôi
Thời gian khởi chạy script rất nhanh, trên hệ thống của tôi theo hyperfine là 1.4ms, gần bằng 1ms của dash. Đây là tính theo script chứ không phải file thực thi đã biên dịch
Nhờ mô-đun
sh-dsl, có thể viết lệnh shell rất thanh lịch như($ cmda w x | cmdb y z). Khả năng nạp image để debug cũng cực kỳ hữu íchTôi chỉ mới bắt đầu dùng rất gần đây, nhưng có cảm giác nó đã trở thành một trong những ngôn ngữ yêu thích của tôi rồi; Lisp khác duy nhất tôi từng dùng trước đó là MIT Scheme cho SICP
Bài này thật mới mẻ. Nó có mùi của những cuộc thảo luận trước thời AI trên Internet
Có ngôn ngữ mới, cú pháp mới, và những cuộc tranh luận nảy lửa giữa những người đã viết code nhiều năm. Tôi ước ai đó sẽ khởi động một cộng đồng trực tuyến không cho phép AI
Thực ra chắc phải nói là đợt tái ra mắt ngay trước đó, và hình như bây giờ lại có một trang chủ mới nữa. Người đầu tiên tìm ra cách chặn AI một cách ổn định trong cộng đồng trực tuyến có lẽ sẽ trở nên rất giàu
https://www.techspot.com/news/111698-digg-relaunch-fails-two...
Một dạng “bằng chứng về tính người” là một bài toán rất khó giải
Quy tắc chính xác do ban quản trị đặt ra là “sự chấp bút đáng kể của con người”, nhưng đừng để bị đánh lừa. Có rất nhiều người trên lobsters phản đối LLM về mặt ý thức hệ. Công nghệ đã được dùng “một cách đáng kể” đến mức nào thực ra không quan trọng lắm
Công việc của tôi bị xếp vào loại rác chỉ vì có AI chạm vào, và cũng có người gọi tôi là kẻ phô bày bản thân hay kẻ có fetish khi tôi nói mình dùng AI. Tôi chỉ muốn báo trước cho ai đang cân nhắc tham gia
Những câu như “bằng cách cho phép unquote các hàm literal, Janet cho phép viết macro hoàn toàn trong suốt tham chiếu” khiến người làm Lisp có vẻ thật sự phấn khích với những thứ cực kỳ trừu tượng
Nếu nói như vậy với một người bình thường ngoài đường, chắc họ sẽ muốn bỏ chạy
#define MULTIPLY(x, y) x * yint result = MULTIPLY(2 + 3, 4); // 14Không biết nghĩa của một thuật ngữ không có nghĩa là nó xấu. Nhìn cách diễn đạt thì có lẽ ý người ta là vậy
Có một ngôn ngữ chung để nói về các mẫu và vấn đề lặp đi lặp lại trong lập trình là điều tốt. Chế nhạo thuật ngữ kiểu “đúng là đám lập trình viên kỳ quặc” thì vô nghĩa và chỉ phản tác dụng
Tôi đang nghĩ đến chuyện bắt đầu lại, nhưng phân vân liệu các tính năng ngách có đáng để triển khai không. Với tôi thì chúng cũng khó triển khai. Có lẽ tốt hơn là bỏ qua
dynamic-unwind, thậm chí có thể bỏ luôncall/cc, rồi tập trung vào khả năng debug, hệ sinh thái, hiệu năng và quản lý góiVì thế tôi thường trả lời rất mơ hồ kiểu “Tôi làm việc liên quan đến máy tính”, hoặc nói “Cũng không phải việc gì thú vị lắm đâu” rồi chuyển chủ đề. Chỉ cần cụ thể hơn một chút là mọi người bắt đầu tìm lối thoát
Thành thật mà nói tôi nghĩ các cộng đồng họ Lisp đã được hưởng lợi phần nào vì quy mô nhỏ. Ví dụ, ngay cả cuốn Design Patterns rất cũ cũng đã cảnh báo nên ưu tiên composition hơn inheritance, thế mà lập trình viên hướng đối tượng vẫn cứ tạo ra những hệ phân cấp sâu 15 tầng
Khi mới làm quen với Janet, các tài liệu này đã thực sự giúp ích cho tôi
https://janetdocs.org/tutorials
https://janet.guide/ do tác giả bài viết tạo ra
Tôi từng bị thu hút bởi các bài về Janet thỉnh thoảng xuất hiện trên HN, nhưng cuốn Janet for Mortals mà ai cũng đánh giá cao lại hoàn toàn không cho tôi cảm giác là sách dành cho người trần
So với các ngôn ngữ khác thì Janet thực sự thuộc loại dễ học, nên việc cuốn sách đó khó hiểu làm tôi ngạc nhiên. Tôi chưa đọc sách, nhưng cũng khá quen với ngôn ngữ này, và thành thật mà nói tôi chỉ có thể khen nó
Janet trông giống Lisp 2.0 nên cú pháp cũng mang phong cách Lisp
Ý kiến trên Lobste.rs
Chỉ sau 10 tháng bắt đầu dùng Janet, tôi đã mê đến mức gần như quên hết các ngôn ngữ ngoài họ APL, đồng thời đang vận hành trang tài liệu của cộng đồng và cũng đang viết hướng dẫn
Trong vòng 3 tuần đầu tôi đã viết lại toàn bộ các script cá nhân, và phần mềm vận hành mới tôi làm cũng được viết bằng Janet
Về mặt triển khai, gần như toàn bộ ngôn ngữ Janet hoạt động như một hash map, nên có thể xem symbol cục bộ bằng
(keys (curenv)), xem symbol lõi bằng(keys (getproto (curenv))), và nếu muốn còn có thể dựng thứ gì đó giống CLOS dựa trên hash map; thậm chí đã có một triển khai như vậyTôi đang chạy khoảng 20 website và nhiều dịch vụ bằng framework web Joy trên một VPS miễn phí 512MB, và cũng đã viết hướng dẫn liên quan
Tuy vậy, cách gọi “bộ sưu tập bất biến” có phần không đúng với thực tế, vì thư viện chuẩn phần lớn trả về các giá trị có thể thay đổi, nên hiện tại không có nhiều lý do để quá câu nệ tính bất biến
Khả năng chuyển giá trị ở thời điểm biên dịch sang thời điểm chạy đặc biệt mạnh. Ví dụ, nếu nhúng một tệp Kinh Thánh
.tsvthành hash map vào binary ngay lúc biên dịch thì khi chạy chỉ cần tra cứu, và kết quả còn nhanh gấp đôi so với bản Go dùngembedNếu tự triển khai cùng thứ đó trong Go dưới dạng hash map thì mã sẽ dài hơn rất nhiều, nhưng với Janet tôi còn có thể viết cả trình biên dịch Lisp sang Go chỉ trong 46 dòng
Điểm thú vị hơn mà Ian Henry nhắc tới là Janet giữ lại trạng thái closure giữa các image/phiên, nghĩa là lưu môi trường liên quan vào hash map
(curenv)rồi khôi phục ở một phiên REPL mới thì trạng thái bên trong closure vẫn tiếp tục được giữ nguyênCòn có DSL âm nhạc dựa trên Lisp tại https://lisp.trane.studio/ ; có thể xem bài báo https://dl.acm.org/doi/abs/10.1145/3677996.3678285 và ví dụ kết quả tại https://x.com/greg_ash/status/1824218993118388708
Tôi cũng có thư viện tự viết, cung cấp cú pháp truy vấn kiểu SQL trên nhiều cấu trúc dữ liệu khác nhau
Nó hỗ trợ chèn/cập nhật dataframe và lưu/tải CSV, đồng thời bao gồm cả Datalog và miniKanren, và cũng cho phép các phép toán vector hóa kiểu APL
Cũng có jnj để dùng J trực tiếp trong Janet, còn Joy Web Framework có DSL truy vấn DB như
(var account (db/find-by :account :where {:login (auth-result :login)})), và nó cũng được dùng trong mã xác thực của website thực tếKhi nói “bộ sưu tập bất biến” người ta thường nghĩ đến cấu trúc dữ liệu persistent, mà dù hữu ích thì đó không phải tính năng mặc định của Janet
Điều tôi thực sự thích là tính đối xứng giữa kiểu giá trị và kiểu tham chiếu; dù ở cuối bài có viết “immutable composite values”, nếu không phải chính tôi viết thì có lẽ tôi cũng khó hiểu ngay ý đó
Janet là một ngôn ngữ mới mẻ và có thể nhúng, nên tôi muốn thử dùng nó cho phần script nhúng trong các dự án như game engine
Nó có vẻ rất hợp với những nơi cần hot reload để lặp nhanh mà không phải thay DLL; Lua cũng rất tuyệt, nhưng Janet ở vài mặt có vẻ biểu đạt tốt hơn
Janet thật sự rất ngầu với tư cách một ngôn ngữ, và một ngày nào đó tôi muốn dùng nó làm ngôn ngữ script cho các dự án Zig. Thật vui khi thấy ngày càng nhiều người nhắc đến Janet
Trông có vẻ hay, nhưng tôi đã quen dùng babashka để script bằng Clojure nên cảm giác khá giống. Ngoài khả năng nhúng ra thì không biết có ưu điểm lớn nào khác mà tôi đang bỏ lỡ không
Những đoạn như “destructure mảng với rest args có thể gây ra bản sao tốn kém” khiến nó nhìn chung có vẻ kém thiên về lập trình hàm hơn, nên tôi không thích lắm
Đây là kiểu tính năng mà mỗi lần phải parse văn bản bằng ngôn ngữ khác tôi đều thấy thiếu
Janet không hẳn là một Clojure nhỏ gọn có thể nhúng, mà gần với Lua có hỗ trợ lập trình hàm tốt hơn