2 điểm bởi GN⁺ 2024-09-06 | 1 bình luận | Chia sẻ qua WhatsApp
  • 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-seqdelay dùng lock thay cho synchronized, 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-deps mà 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ý Java Stream
  • 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-seqdelay chạy mã người dùng bên trong khối synchronized để bảo đảm hành vi chỉ thực thi một lần
    • Theo JDK 21, synchronized vẫ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-seqdelay dùng lock thay cho khối synchronized
  • 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
  • serialVersionUID củ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.238
    • core.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úc
    • sync-deps: gọi add-libs cho các lib có trong deps.edn như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/repl tự động bind cờ này
    • Trong REPL của clojure.main, các hàm mới được refer tự động vào namespace user
    • Với REPL khác, có thể cần (require '[clojure.repl.deps :refer :all])
  • Việc phân giải và tải thư viện do tools.deps đảm nhiệm
    • Để tránh đưa tools.deps và 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
  • clojure.tools.deps.interop/invoke-tool gọ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ằng invoke-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

API chạy tiến trình ngoài

  • Bên cạnh namespace clojure.java.shell hiệ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
    • start: cho phép kiểm soát đầy đủ stream và truy cập đối tượng Java nền tảng cho các trường hợp nâng cao
    • exec: xử lý trường hợp phổ biến là chạy tiến trình ngoài và trả về stdout khi hoàn tất

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 map hay 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
  • 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ĩnh
    • Classname/.method: giá trị hàm Clojure gọi phương thức instance
    • Classname/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/methodClassname/.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-tags là 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-tags và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ó @FunctionalInterface và 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
  • Khả năng tương tác với Supplier cũng được cải thiện
    • Trước đây, để gọi phương thức nhận Supplier cung cấp giá trị, phải viết adapter bằng reify
    • Các triển khai IDeref của Clojure như delay, future, atom nay triển khai trực tiếp giao diện Supplier

Xử lý Stream và cải thiện hiệu năng collection

  • Các hàm để tiêu thụ Stream theo phong cách Clojure đã được thêm vào, khi Java API ngày càng trả về Stream nhiề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
  • PersistentVector trự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 PersistentVector hỗ trợ song song hóa và cải thiện hiệu năng đáng kể
  • Hiệu quả của drop, nthrest, nthnext và 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-at hiệ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

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"
  • Chính sách này xử lý nguyên nhân gốc của vấn đề bộc lộ khi clojure.core có thêm hàm mới trong Clojure 1.11.0, đặc biệt là abs
    • Nếu mã được biên dịch bằng phiên bản Clojure cũ hơn có tên var trùng với tên hàm mới thêm vào clojure.core, nó có thể trở thành unbound khi được nạp trên runtime 1.11.0
    • Ngoài CLJ-2711, bản sửa trước đó trong khu vực này là CLJ-1604 cũng đã bị hoàn tác

Danh sách thay đổi đầy đủ

1 bình luận

 
GN⁺ 2024-09-06
Ý 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

    • Không biết còn ai nhớ Groovy không. Groovy có annotation @Grab làm về cơ bản đúng việc mà add-libs được mô tả ở trên, và rất tiện khi viết script
    • Java giờ cũng đã có REPL và hỗ trợ scripting, nhưng vẫn chưa có tính năng kiểu add-libs trong các meta-command đang dùng được
  • 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] ...)

    • Khi thấy “maybe not”, tôi nhận ra mình thực sự cần schema/select, nên đã làm một thư viện cho Malli: https://github.com/eval/malli-select
    • Tôi tò mò “không thể kiểm tra macro” nghĩa là gì. Có thể gắn s/fdef vào macro bằng spec, và kiểm tra lời gọi ở thời điểm biên dịch
      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-libssync-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ỡ

    • Rich Hickey và Clojure Team là những nhà thiết kế cực kỳ thận trọng, nên tôi nghĩ không cần lo quá nhiều
    • Chỉ là suy đoán thôi, nhưng đây là bản phát hành đầu tiên sau khi Rich Hickey rời nubank, nên có lẽ ông ấy đã có thể dành nhiều sự chú ý hơn cho nó
  • 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?

    • spec vẫn tồn tại và vẫn đang được sử dụng. Rất nhiều công việc tiếp theo cũng đã được tiến hành, nhưng hiện đang tạm dừng trong lúc xem xét nên làm gì với một số vấn đề khác nhau
  • 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 cũng tự hỏi liệu nó có thực sự đang phát triển tốt không. Tôi đang đánh giá xem có nên dùng cho dự án mới hay không, cùng với Clara[0]. Nhưng có cảm giác nó không còn mang tính chủ lưu như trước, và hệ sinh thái cũng thưa thớt hơn trước
      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