Phát hành Clojure 1.12.0
(clojure.org)- Clojure 1.12.0 vẫn duy trì bytecode Java 8, đồng thời được thông báo là bản phát hành cuối cùng theo chuẩn Java 8 trước khi các bản phát hành tiếp theo chuyển mức tương thích Java tối thiểu và chuẩn bytecode sang Java LTS mới hơn
- Trong môi trường luồng ảo của JDK 21,
lazy-seqvàdelaydùng lock thay chosynchronized, giúp giảm các tình huống I/O chặn làm ghim luồng thực - Trong REPL, có thể thêm thư viện bằng
add-lib,add-libs,sync-depsmà không cần khởi động lại JVM, nhưng tính năng này chỉ dành cho sử dụng tương tác trong quá trình phát triển - Khả năng tương tác với Java được mở rộng với method value,
:param-tags, cú pháp lớp mảng, chuyển đổi giao diện hàm,Supplier, và các hàm xử lý JavaStream - Về hiệu năng và khả năng tương thích, bản này bao gồm spliterator cho
PersistentVector, xử lýdrop/partition hiệu quả hơn, siết chặt chính sách Var interning, sửa CVE-2024-22871, và làm gọn các định danh tuần tự hóa Java
Tương thích Java 8, bảo mật, và sắp xếp lại tuần tự hóa
- Có thể xem thông tin tải xuống và sử dụng Clojure 1.12.0 tại trang Downloads
- Chuẩn Java 8 vẫn được giữ trong bản phát hành này
- Clojure 1.12 tạo bytecode Java 8 giống như Clojure 1.10 và 1.11
- Các bản phát hành sau sẽ chuyển bytecode và mức tương thích Java tối thiểu sang một bản Java LTS mới hơn
- Vấn đề ghim luồng ảo trên JDK 21 đã được giảm nhẹ
- Trước 1.12,
lazy-seqvàdelaychạy mã người dùng bên trong khốisynchronizedđể bảo đảm hành vi chỉ thực thi một lần - Theo JDK 21,
synchronizedvẫn chưa tham gia cơ chế chặn hợp tác, nên nếu đoạn mã đó thực hiện I/O chặn thì có thể ghim luồng thực - Khi dùng
-Djdk.tracePinnedThreads=full, JDK 21 có thể cảnh báo về tình huống này - Trong 1.12,
lazy-seqvàdelaydùng lock thay cho khốisynchronized
- Trước 1.12,
- Bản vá bảo mật đã tích hợp CVE-2024-22871, và khuyến nghị liên quan có tại GHSA-vr64-r9qj-h27f
serialVersionUIDcủa các lớp liên quan đến tuần tự hóa Java đã được đặt tường minh- Các kiểu dữ liệu Clojure đã triển khai giao diện tuần tự hóa Java từ Clojure 1.0
- Tuần tự hóa Java chỉ hoạt động khi định danh được tạo từ tên lớp, hệ phân cấp kiểu và các trường tuần tự hóa khớp nhau trong lúc giải tuần tự hóa
- Clojure không đảm bảo tính nhất quán tuần tự hóa giữa các phiên bản, nhưng đã áp dụng thay đổi để tăng khả năng kiểm soát trong tương lai nhằm tránh phá vỡ tương thích nhiều hơn mức cần thiết
- Các phụ thuộc cũng được cập nhật
spec.alphađược nâng lên 0.5.238core.specs.alphađược nâng lên 0.4.74
Các tính năng xử lý thư viện và công cụ trong REPL
- Trong quá trình phát triển, đôi khi cần thêm thư viện mà không khởi động lại JVM
- Đánh giá thử nghiệm
- Thêm phụ thuộc đã biết vào dự án
- Thêm thư viện cho một tác vụ cụ thể
- Clojure 1.12 cung cấp các hàm mới để thêm thư viện mà không làm mất trạng thái REPL
add-lib: tải xuống lib chưa có trong classpath và thêm vào classloader- Không cập nhật lib đã có trong classpath
- Nếu không có tọa độ, có thể suy ra phiên bản Maven mới nhất hoặc dùng phiên bản/tag git mới nhất khi suy ra được tên kho git
add-libs: phân giải nhiều thư viện mới và phiên bản của chúng cùng lúcsync-deps: gọiadd-libscho các lib có trongdeps.ednnhưng chưa có trên classpath
- Các hàm này chỉ nhằm phục vụ việc dùng REPL khi phát triển
- Cách đúng để build và duy trì mã production vẫn là dùng
deps.edn - Cả ba hàm đều kiểm tra xem
*repl*có được bind là true hay không clojure.main/repltự động bind cờ này- Trong REPL của
clojure.main, các hàm mới đượcrefertự động vào namespaceuser - Với REPL khác, có thể cần
(require '[clojure.repl.deps :refer :all])
- Cách đúng để build và duy trì mã production vẫn là dùng
- Việc phân giải và tải thư viện do tools.deps đảm nhiệm
- Để tránh đưa
tools.depsvà các phụ thuộc của nó vào classpath dự án trong khi phát triển, một API mới cũng được thêm để gọi hàm qua Clojure CLI trong tiến trình riêng
- Để tránh đưa
clojure.tools.deps.interop/invoke-toolgọi hàm công cụ trong tiến trình riêng- Classpath của công cụ được định nghĩa trong
deps.edn - Không cần thêm phụ thuộc của công cụ vào classpath dự án
- Tính năng
add-libđược xây dựng bằnginvoke-tool, và cũng có thể dùng để build hoặc gọi công cụ người dùng theo cách tương tác - Giao thức thực thi hàm có thể xem trong CLI reference
- Classpath của công cụ được định nghĩa trong
API chạy tiến trình ngoài
- Bên cạnh namespace
clojure.java.shellhiện có, nay đã có thêm hướng tiếp cận tận dụng API mới của Java cho thông tin tiến trình, điều khiển và chuyển hướng I/O - Clojure 1.12 thêm namespace mới
clojure.java.process- Tận dụng API mới liên quan đến tiến trình của Java
- Được thiết kế để dễ dùng hơn cách hiện có
- Các hàm chính gồm
Mở rộng khả năng tương tác với Java
- Method value được thêm vào để có thể dùng phương thức Java trực tiếp hơn trong các hàm bậc cao
- Trước đây, muốn truyền phương thức Java vào
maphay tương tự thì phải tự bọc nó thành hàm - Việc bọc thủ công vừa dài dòng, vừa có thể cần hint để phân biệt overload hoặc phát sinh reflection/boxing phụ
- Giờ đây có thể dùng qualified methods ở vị trí giá trị như hàm thông thường, và compiler sẽ tự tạo hàm bọc
- Nếu qualified method không phân giải được do overload, compiler sẽ tạo lời gọi reflection
- Nhà phát triển có thể chỉ định chữ ký phương thức mong muốn bằng metadata
:param-tags
- Trước đây, muốn truyền phương thức Java vào
- Cú pháp qualified method nêu rõ lớp và phương thức
Classname/method: giá trị hàm Clojure gọi phương thức tĩnhClassname/.method: giá trị hàm Clojure gọi phương thức instanceClassname/new: giá trị hàm Clojure gọi constructor- Để phân biệt phương thức tĩnh và instance, phải dùng cú pháp
Classname/methodvàClassname/.method
- Metadata
:param-tagsđược dùng để phân giải phương thức overload- Qualified method dùng ở vị trí giá trị chỉ cung cấp lớp và tên phương thức nên không thể phân giải phương thức overload
:param-tagslà vector dạng[tag …], trong đó mỗi tag tương ứng với tham số của chữ ký mong muốn- Có thể dùng placeholder
_cho tham số thuộc kiểu không overload - Khi cung cấp
:param-tags, compiler phải có thể phân giải thành một phương thức duy nhất tại thời điểm biên dịch - Cú pháp reader metadata mới
^[tag …]gắn metadata:param-tagsvào symbol thành viên
- Cú pháp lớp mảng đã được thêm
- Clojure vốn hỗ trợ symbol tên lớp như giá trị đối tượng lớp và type hint, nhưng ngoài chuỗi thì chưa có cú pháp lớp mảng
- Giờ có thể tham chiếu lớp mảng bằng symbol dạng
ComponentClass/#dimensions - Ví dụ:
String/1,java.lang.String/1,long/2 - Lớp thành phần có thể là tên lớp đầy đủ, lớp đã import, hoặc kiểu primitive
- Cú pháp lớp mảng có thể dùng cả làm type hint lẫn giá trị
- Khả năng tương tác với giao diện hàm của Java được cải thiện
- Giao diện hàm của Java có
@FunctionalInterfacevà chỉ có một phương thức - Hàm Clojure có thể được truyền vào lời gọi phương thức Java nhận giao diện hàm nếu arity khớp
- Compiler Clojure tạo lambda adapter để chuyển ngầm hàm Clojure sang giao diện hàm cần thiết
- Để tránh tạo adapter lặp đi lặp lại trong vòng lặp, có thể ép tường minh bằng cách gắn hint vào tên trong
let
- Giao diện hàm của Java có
- Khả năng tương tác với
Suppliercũng được cải thiện- Trước đây, để gọi phương thức nhận
Suppliercung cấp giá trị, phải viết adapter bằngreify - Các triển khai
IDerefcủa Clojure nhưdelay,future,atomnay triển khai trực tiếp giao diệnSupplier
- Trước đây, để gọi phương thức nhận
Xử lý Stream và cải thiện hiệu năng collection
- Các hàm để tiêu thụ
Streamtheo phong cách Clojure đã được thêm vào, khi Java API ngày càng trả vềStreamnhiều hơn- Cùng với hỗ trợ giao diện hàm trong Clojure 1.12, các hàm tương tác Stream cũng được cung cấp
(stream-seq! stream) ⇒ seq(stream-reduce! f [init-val] stream) ⇒ val(stream-transduce! xf f [init-val] stream) ⇒ val(stream-into! to-coll [xf] stream) ⇒ to-coll- Tất cả đều là các phép toán stream terminal và sẽ tiêu thụ stream
PersistentVectortrực tiếp cung cấp spliterator được dùng trong triển khai stream của collection Java- Spliterator là iterator có thể chia tách để duyệt song song nhanh hơn
- Spliterator tùy biến mới của
PersistentVectorhỗ trợ song song hóa và cải thiện hiệu năng đáng kể
- Hiệu quả của
drop,nthrest,nthnextvà xử lý partition đã được cải thiện- CLJ-2713 thêm giao diện nội bộ
IDropđể biểu thị rằng collection có thể thực hiện drop hiệu quả hơn so với duyệt tuần tự - Giao diện này được triển khai cho persistent collection và các collection theo thuật toán như
range,repeat - Các hàm mới
partitionv,partitionv-all,splitv-athiệu quả hơn các hàm tương ứng trước đây và tạo partition dạng vector thay vì partition của realized seq
- CLJ-2713 thêm giao diện nội bộ
Siết chặt chính sách Var interning
- Interning var trong namespace, khác với aliasing, là tạo ra tham chiếu ổn định để mọi tham chiếu đều nhận cùng một đối tượng
- Trước đây có một số trường hợp var đã intern có thể bị thay thế, và chính sách này đã nghiêm ngặt hơn từ 1.12.0-alpha1
- Nếu xảy ra tình huống đó, sẽ xuất hiện cảnh báo dạng
"REJECTED: attempt to replace interned var #'some-ns/foo with #'other-ns/foo in some-ns, you must ns-unmap first"
- Nếu xảy ra tình huống đó, sẽ xuất hiện cảnh báo dạng
- Chính sách này xử lý nguyên nhân gốc của vấn đề bộc lộ khi
clojure.corecó thêm hàm mới trong Clojure 1.11.0, đặc biệt làabs
Danh sách thay đổi đầy đủ
- Danh sách thay đổi đầy đủ của Clojure 1.12.0 có tại changelog chính thức
1 bình luận
Ý kiến trên Hacker News
Đây thực sự là một bản phát hành lớn với rất nhiều tính năng mới tuyệt vời
Cá nhân tôi thích nhất add-libs. Giờ đây có thể tạo bản demo một tệp hoặc ví dụ tối giản để tái hiện issue, nên rào cản chia sẻ các đoạn mã nhỏ có thể chạy được đã giảm đi rất nhiều
Cũng có thể demo các thư viện Java mà không cần boilerplate Java. Sau khi nghịch thử trong REPL rồi dán mã vào chỗ như bình luận HN, ai cũng có thể tái tạo và chạy đúng cùng một “thiết lập”. Thậm chí không cần clone repository
Tôi đã nghĩ bản phát hành này sẽ bị dời đến tận Clojure/conj 2024. Không hẳn có căn cứ gì rõ ràng, nhưng Clojure 1.10 ra mắt vào khoảng Clojure/conj 2021, còn thông báo Datomic miễn phí cũng đến vào đầu Clojure/conj 2023
Dù vậy tôi vẫn đang chờ spec2. Hiện tại tôi lách sự cứng nhắc của spec bằng Malli, nhưng nó không phải công dân hạng nhất trong Clojure. Chủ yếu vì không thể kiểm tra macro, và đây là phần được chủ đích trong thiết kế của trình biên dịch Clojure. Tuy nhiên, nếu thao tác schema Malli như dữ liệu thì vẫn có thể bắt chước ý tưởng của schema/select
Nhờ thay đổi về functional interface, giờ có thể truyền hàm trực tiếp mà không cần duy trì các macro tiện ích như
(defmacro ->Consumer [f] ...)Thực tế Clojure core cũng kiểm tra lời gọi macro bằng spec, nên đôi khi nếu gọi sai bạn sẽ thấy dấu vết đó trong stack trace. Không rõ bạn đang nói theo nghĩa nào khác
Thật tuyệt khi có cả đống tính năng mới mà mã hiện có vẫn chạy nguyên vẹn. Nỗ lực nhất quán để tránh phá vỡ tương thích thực sự tỏa sáng
Nếu muốn tìm hiểu thêm về Clojure, bạn có thể xem hội nghị Clojure/conj diễn ra tại Alexandria, Virginia vào ngày 23–25 tháng 10: https://2024.clojure-conj.org
Rất vui khi thấy add-libs và sync-deps được đưa vào. Giờ gần như không còn, hoặc hoàn toàn không còn, lý do gì để phải đóng phiên làm việc
Bản phát hành lần này có vẻ khá khác về phạm vi so với các bản trước, và lượng nội dung được đưa vào khiến nó trở nên thú vị. Tuy vậy, vì tốc độ đang nhanh hơn, tôi chỉ hy vọng vài bản phát hành nữa nó sẽ không biến thành một mớ rối rắm khó gỡ
Thay đổi về functional interface là cực lớn. Clojure phát huy tốt nhất khi giữ được sự gần gũi với Java thông qua khả năng tương tác được cân nhắc kỹ, và thay đổi lần này đã lấp được một khoảng trống lớn
Không rõ spec giờ ra sao. Nó bị bỏ dở rồi à? Có tin gì đáng mong đợi không?
Có vẻ là một bản phát hành khá chắc chắn, và tôi mừng vì Clojure vẫn đang phát triển tốt
Tôi không cố troll đâu. Tôi muốn chọn nó, và xét về mặt kỹ thuật thì đây có vẻ là quyết định tốt. Nhưng nếu mức độ phổ biến và số người đóng góp đang giảm mạnh, điều đó có thể trở thành lực cản trong tương lai gần
[0] https://www.clara-rules.org/
Việc chuyển các lập trình viên hiện có thành lập trình viên Clojure giờ dễ hơn bao giờ hết
Vấn đề lớn lúc đầu[0] là đọc mã, nhưng các AI như ChatGPT hay Claude làm rất tốt việc giải thích mã Clojure hiện có. Nhờ đó quá trình onboarding lập trình viên có thể nhanh hơn rất nhiều
[0] Sau vài tuần, việc đọc Clojure sẽ trở nên tự nhiên và bạn thậm chí còn quên mất rằng trước đây mình từng không đọc được nó
Có rất nhiều cải tiến hay. Đây thường là ngôn ngữ họ Lisp mà tôi hay dùng nhất