1 điểm bởi GN⁺ 2025-06-08 | 1 bình luận | Chia sẻ qua WhatsApp
  • Tối ưu hóa cấp thấp là công việc giúp trình biên dịch hiểu rõ hơn ý định và ràng buộc của mã, và Zig rất phù hợp với mục đích này vì dễ biểu đạt rõ ràng thông tin về kiểu, căn chỉnh, alias và thời điểm biên dịch
  • Ngay cả các trình biên dịch tối ưu hóa như LLVM cũng không phải lúc nào cũng tạo ra mã tốt nhất, nên ở các đoạn nghẽn cổ chai vẫn cần kiểm tra mã được sinh ra và điều chỉnh mã nguồn
  • Zig có thể truyền noalias, align, kích thước mảng cố định và kiểu phần tử tại thời điểm biên dịch, từ đó tạo ra mã vector hóa nhỏ hơn so với ví dụ JavaScript
  • comptime cho phép chạy mã Zig thông thường tại thời điểm biên dịch, qua đó hỗ trợ metaprogramming như tạo hằng số, triển khai generic, phản chiếu kiểu và tối ưu hóa so sánh chuỗi
  • Điểm mạnh của Zig nằm ở việc thực thi tại thời điểm biên dịch được tích hợp vào ngôn ngữ, hơn là macro trực tiếp thay đổi AST; một số giá trị runtime cũng có thể được dispatch sang các hàm chuyên biệt hóa tại thời điểm biên dịch

Vì sao nên tin trình biên dịch nhưng vẫn phải kiểm chứng

  • Tối ưu hóa không chỉ là kỹ thuật tạo ra chương trình chạy nhanh, mà còn gắn với giảm chi phí, cải thiện khả năng mở rộng và giữ cho hệ thống đơn giản
  • Các trình biên dịch hiện đại tạo ra kết quả ấn tượng bằng backend như LLVM, nhưng trong một số tình huống vẫn sinh ra mã chưa tối ưu
  • Lý do ngôn ngữ cấp thấp nhanh không chỉ là vì ít overhead từ garbage collection hay trình thông dịch, mà còn vì chúng có thể biểu đạt nhiều thông tin về ý định hơn để trình biên dịch hiểu được
  • Trình biên dịch không thể tự thay đổi thuật toán hay paradigm lập trình, và nhìn chung thực hiện tối ưu hóa trong phạm vi hạn chế như vòng lặp

Ví dụ tìm giá trị lớn nhất trong mảng bằng JavaScript và Zig

  • Ví dụ JavaScript lưu giá trị lớn nhất theo từng phần tử của hai mảng vào x dưới dạng x[i] = y[i] > x[i] ? y[i] : x[i]
  • Với con người, đây là mã rõ ràng, nhưng bytecode do V8 sinh ra lại phình to
  • Ví dụ Zig chỉ rõ hơn trong tham số hàm các thông tin cần thiết cho tối ưu hóa
    • noalias x: x không phải là alias của con trỏ khác
    • *align(64): căn chỉnh 64 byte
    • [65536]f64: kích thước mảng và kiểu phần tử
    • const: tham số chỉ đọc
  • Nhờ thông tin này, trình biên dịch có thể tạo mã tốt hơn; trong ví dụ, assembly đã được vector hóa được sinh ra
  • Mã Rust tương đương cũng sinh ra assembly gần như giống hệt

Điểm Zig thuận lợi cho tối ưu hóa và các giới hạn

  • Zig cho phép cách biểu đạt dài dòng để truyền nhiều thông tin mã cho LLVM
  • Các yếu tố chính Zig cung cấp liên quan đến tối ưu hóa gồm:
  • Mô hình bộ nhớ của Rust cho phép trình biên dịch luôn giả định rằng tham số hàm không tạo alias, còn trong Zig phải chỉ định trực tiếp điều này
  • Nếu trình biên dịch không thể biết tham số hàm Zig có alias hay không, một hàm Zig không có chú thích có thể chậm hơn hàm Rust
  • Ngay cả nếu chỉ xét LLVM IR được chú thích tốt, Zig vẫn cho kết quả tốt, nhưng thế mạnh lớn hơn nằm ở thực thi tại thời điểm biên dịch

Vai trò của comptime

  • comptime của Zig là tính năng dành cho sinh mã tại thời điểm biên dịch
  • Những việc có thể làm tại thời điểm biên dịch gồm:
    • Tạo hằng số và đưa vào binary
    • Tránh phải viết lặp lại cùng một cấu trúc hashmap cho nhiều kiểu dữ liệu
    • Dẫn dắt tối ưu hóa để loại bỏ mã không cần thiết dựa trên dữ liệu đã biết tại thời điểm biên dịch
    • Kiểm tra, phản chiếu và tạo kiểu để triển khai generic
  • comptime là mã Zig thông thường được thực thi tại thời điểm biên dịch và không thể có tác dụng phụ như network IO
  • Máy mô phỏng tại thời điểm biên dịch khớp với mục tiêu biên dịch
  • Gần như mọi mã Zig đều có thể được chạy tại thời điểm biên dịch bằng comptime, và mọi kiểu đều có thể được kiểm tra, phản chiếu và tạo ra tại thời điểm biên dịch

Khác gì với macro

  • Mục đích của comptime tương tự macro, nhưng cách hoạt động thì khác
  • Một số macro thay thế văn bản thô, một số trực tiếp sửa AST của chương trình
  • comptime của Zig không trực tiếp thay đổi AST, và cũng không có tính năng như macro nối token
  • Vì Zig hướng tới một ngôn ngữ dễ đọc, nó không phù hợp với kiểu macro tạo hoặc sửa biến trong các scope không liên quan
  • Những việc macro làm được nhưng Zig comptime không trực tiếp làm được gồm:
    • Định nghĩa macro khác
    • Thay đổi AST
    • Trực tiếp triển khai mini-language hoặc DSL
  • Tuy vậy, vẫn có thể tạo DSL trong Zig; hàm print của Zig phân tích chuỗi định dạng bằng comptime để xây dựng đồ thị hàm dùng cho tuần tự hóa dữ liệu
  • Ví dụ gồm TigerBeetle account testing DSL, comath, zilliam

Tối ưu hóa so sánh chuỗi bằng comptime

  • So sánh chuỗi thông thường trả về false nếu độ dài khác nhau; nếu độ dài bằng nhau thì so sánh lần lượt từng byte
  • Cách này phải đọc byte từ cả hai chuỗi để so sánh
  • Vì một chuỗi thường đã được biết tại thời điểm biên dịch, trong Zig có thể yêu cầu một tham số là comptime
    • fn staticEql(comptime a: []const u8, b: []const u8) bool
  • Khi so sánh với chuỗi tĩnh như "Hello!\n", trình biên dịch sinh mã gồm so sánh độ dài và so sánh từng byte với hằng số
  • Mục đích của phần này không chỉ là cho thấy một tối ưu hóa mà trình biên dịch có thể tự làm, mà còn cho thấy comptime có thể ép buộc chuyển đổi để mở ra các cơ hội trình biên dịch không nhìn thấy

So sánh theo đơn vị lớn hơn và tận dụng SIMD

  • So sánh chuỗi comptime đơn giản vẫn thực hiện so sánh theo từng byte
  • Phiên bản cải tiến dùng std.simd.suggestVectorLength(u8) hoặc @sizeOf(usize) để xác định kích thước khối so sánh
  • Sau khi kiểm tra độ dài chuỗi trước, nó tính số khối lớn có thể so sánh và số byte còn lại
  • Mỗi khối được @bitCast sang kiểu số nguyên tạo bằng std.meta.Int(.unsigned, block_len * 8) rồi đem so sánh
  • Các byte còn lại cũng được so sánh bằng một kiểu số nguyên riêng
  • Assembly được sinh ra trong ví dụ "Hello, World!\n" sử dụng thanh ghi lớn hơn và giảm số nhánh điều kiện
  • Với so sánh chuỗi dài hơn, assembly được sinh ra sẽ dùng thanh ghi SIMD lớn hơn

Kết hợp giá trị runtime với chuyên biệt hóa tại thời điểm biên dịch

  • comptime của Zig không chỉ giới hạn ở dữ liệu đã biết tại thời điểm biên dịch
  • Trong các trường hợp đơn giản, có thể tạo sẵn nhiều thủ tục tại thời điểm biên dịch rồi dynamic dispatch đến thủ tục phù hợp theo giá trị runtime
  • Mã ví dụ xử lý các giá trị trong phạm vi inline 0...100switch (runtime_val) bằng staticFn(comptime_val), còn các giá trị còn lại bằng runtimeFn(runtime_val)
  • Nếu không muốn tăng kích thước binary, có thể fallback về triển khai runtime hoàn toàn

Kết luận

  • comptime của Zig đóng vai trò thay thế template, macro, generic và sinh mã thủ công
  • Có thể làm việc tương tự bằng các ngôn ngữ khác, nhưng trong Zig, comptime được tích hợp vào ngôn ngữ một cách tự nhiên hơn
  • Zig giúp viết mã hiệu năng cao dễ hơn trong những tình huống thực sự hữu ích, đối lập với Turing tar-pit, nơi mọi thứ đều có thể nhưng các việc thú vị lại khó làm
  • Về các cuộc chiến ngôn ngữ, vẫn đồng thời tồn tại góc nhìn rộng rằng chỉ cần Turing-complete là đủ, và quan điểm rằng con người có thể có ngôn ngữ mình ưa thích
  • Những câu xem bản thân ngôn ngữ là đối tượng benchmark, như “C nhanh hơn Python”, có thể sai; đối tượng benchmark thực sự không phải là ngôn ngữ mà là mã và triển khai cụ thể

1 bình luận

 
GN⁺ 2025-06-08
Ý kiến trên Hacker News
  • Điều hấp dẫn nhất ở Zig là việc nó hướng tới hệ thống build dễ dùng, biên dịch chéo và tốc độ lặp nhanh
    Là nhà phát triển game nên tôi có yêu cầu về hiệu năng, nhưng tôi cho rằng hầu hết ngôn ngữ đều cho hiệu năng cần thiết đủ tốt, nên đó không phải ưu tiên số một khi chọn ngôn ngữ
    Có thể viết mã mạnh mẽ bằng bất kỳ ngôn ngữ nào, nhưng điều cốt lõi là chọn một framework có khả năng thích ứng tương lai cao để có thể duy trì mã theo kiểu mô-đun trong hàng chục năm
    C/C++ từng là câu trả lời mặc định vì được hỗ trợ ở khắp nơi, và tôi có cảm giác Zig cũng có thể đạt tới mức đó

    • Tôi thích Zig, nhưng cho rằng khả năng bảo trì dài hạn và tính mô-đun lại là một trong những điểm yếu nhất của nó
      Zig không thân thiện với đóng gói, và không thể đặt thành viên của struct là riêng tư: https://github.com/ziglang/zig/issues/9909#issuecomment-9426...
      Trích dẫn cốt lõi là quan điểm rằng “field riêng tư và getter/setter là một anti-pattern do Java phổ biến hóa; field là dữ liệu đang tồn tại, nên hãy đặt tên cẩn thận và ghi tài liệu như một phần của API công khai”
      Nếu không thể ẩn biểu diễn bên trong, sẽ khó tạo đúng hợp đồng API, vốn là nền tảng của tính mô-đun trong phần mềm; cần có khả năng thay đổi biểu diễn bên trong mà không làm hỏng mã của người dùng
      Lập trường của Zig dường như là không nên có cái gọi là biểu diễn bên trong riêng biệt, mà chính biểu diễn đó phải được công khai, ghi tài liệu và bảo đảm; tôi hy vọng một ngày nào đó họ sẽ đảo ngược quyết định này và hỗ trợ field riêng tư
    • Tôi thử cho Zig chạy trên một thiết bị Kindle cũ, trên nền Linux 4.1.15 đã bị cắt gọt, chỉ để cho vui; đó là một trải nghiệm khá thú vị và tôi pleasantly surprised trước độ trưởng thành của Zig
      Rất nhiều thứ chạy được ngay, và tôi còn có thể dùng GDB cũ để debug cả những lỗi kỳ quặc
      Tôi cũng đã bị Zig thuyết phục, và có viết về việc đó ở đây: https://news.ycombinator.com/item?id=44211041
    • Tôi từng dùng Rust một chút và thấy thích, nhưng nghe nói nó bị đánh giá không tốt nên tạm dừng; sau đó dùng lại thì vẫn thấy thích
      Tôi không hiểu lắm vì sao người ta ghét nó đến vậy
      Generic khó nhìn thì C# hay TypeScript cũng có, còn borrow checker là một khái niệm có thể hiểu được nếu từng làm việc ở mức thấp
    • Zig trông giống một Rust đơn giản hơn và một Go tốt hơn
      Trong các công cụ được xây trên Zig, thứ khiến tôi thật sự ấn tượng là bun; sau khi dùng bun, cuộc sống của tôi đơn giản hơn rất nhiều
      Có thể nói điều tương tự về uv được viết bằng Rust
    • Tôi tò mò Zig sẽ hoạt động thế nào trên console
      Thông thường console không thích những thứ không phải C/C++, nhưng vì Zig có thể chuyển thành C nên có lẽ nó sẽ không bị loại trừ hoàn toàn
  • Về tuyên bố “ngay cả compiler hiện đại cũng phá vỡ đặc tả ngôn ngữ (Clang giả định rằng mọi vòng lặp không có side effect đều kết thúc)”, tôi không nghi ngờ việc compiler đôi khi phá vỡ đặc tả, nhưng trong trường hợp đó Clang đúng, ít nhất là theo chuẩn từ C11 trở đi
    C11 nói rằng với các vòng lặp có biểu thức điều khiển không phải là biểu thức hằng, và không thực hiện nhập/xuất, truy cập volatile, đồng bộ hóa hay thao tác nguyên tử, implementation có thể giả định rằng chúng sẽ kết thúc

    • C++ cho đến trước khi C++26 ra mắt thì nói như vậy với mọi vòng lặp, nhưng như đã chỉ ra, bản thân C thì không như thế, mà chỉ áp dụng cho trường hợp “biểu thức điều khiển không phải là biểu thức hằng”
      Vì vậy vòng lặp vô hạn đơn giản trong C như for (;;); phải thực sự được biên dịch thành vòng lặp vô hạn, và loop {} ít mập mờ hơn của Rust cũng phải như vậy
      Nhưng LLVM được tạo bởi những người đôi khi quên rằng họ không phải lúc nào cũng đang làm compiler C++, nên đã có lúc khi Rust yêu cầu “cho tôi vòng lặp vô hạn”, LLVM lại nói “theo C++ thì thứ đó không tồn tại, nên tôi sẽ tối ưu hóa”, và đó là áp dụng sai cho ngôn ngữ khác
  • Không nhất thiết phải có comptime để inline hóa và bung phép so sánh chuỗi
    Trong C cũng làm được: https://godbolt.org/z/6edWbqnfT
    Tôi đã sửa lỗi chính tả

  • Tôi cho rằng so sánh ví dụ JavaScript với ví dụ Zig/Rust không phải là một phép so sánh hay
    Với trình biên dịch Zig và Rust thì lại bảo chọn CPU đích rất hiện đại, còn V8 có vẻ không ở cùng điều kiện
    JIT tối ưu hóa cũng biết vector hóa nếu điều kiện phù hợp
    Nhân tiện, hầu hết ngôn ngữ hiện đại đều thực hiện tối ưu hóa tương tự với chuỗi; ví dụ C++ ở đây: https://godbolt.org/z/TM5qdbTqh

    • Nhìn chung có cảm giác như so sánh táo với salad trái cây, nhưng để cho thấy khác biệt về nơi dùng giữa JS và Zig thì vẫn phù hợp
      Ví dụ Zig dùng mảng kích thước cố định với kiểu đã biết, còn mã JS thì “generic” ở runtime nên xy có thể là bất kỳ object nào
      Đúng là trong JS phải trả chi phí đó, nhưng trớ trêu là ở ví dụ cụ thể này có thể truyền thông tin kiểu tốt hơn cho JIT
      Nếu khiến hàm này luôn được gọi với Float64Array cùng kích thước, JIT sẽ biết điều đó và tạo vòng lặp nhanh hơn. Dù không vector hóa thì cũng tốt hơn nhiều
      Tuy nhiên typed array có chi phí khởi tạo lớn, nên thực tế không hay dùng trừ khi cấp phát một typed array lớn một lần rồi tái sử dụng nhiều lần
      Ngoài ra bài viết nói bytecode JS khá phình ra; có lẽ phần lớn là do JIT không thể đảm bảo 65536 bằng độ dài của cả hai mảng nên phải chèn guard
      Dù vậy trên thực tế không ai viết vòng lặp for kiểu đó mà sẽ viết i < x.length, và trong trường hợp này JIT ít nhất sẽ loại bỏ được một phép kiểm tra mảng
    • Trong ví dụ godbolt của Rust và Zig có thể đổi target sang CPU cũ hơn
      Xin lỗi vì đã không nghĩ tới giới hạn của mục tiêu JS
      Ví dụ C++ được liên kết là một minh họa tốt cho những gì Clang có thể làm với C++, nhưng dù xét việc Zig biên dịch nhắm tới CPU cụ thể, assembly được tạo ra trông vẫn hơi đáng tiếc
      Sẽ rất thú vị nếu xem bản port C++ của https://github.com/RetroDev256/comptime_suffix_automaton
      Đây là cách tận dụng comptime mà trình biên dịch C++ không thể suy luận gọn gàng
  • Tôi không chắc câu “ngôn ngữ cấp cao thiếu lượng ý định phong phú mà ngôn ngữ cấp thấp có” có thật sự đúng không
    Biểu đạt ý định dường như không phải là yếu tố nằm trên phổ cao/thấp; trái lại, càng có nhiều cách biểu đạt ý định chi tiết thì càng nên gần với cấp cao hơn

    • Đồng ý, và đi xa hơn nữa, tôi nghĩ khác biệt căn bản giữa ngôn ngữ cấp cao và cấp thấp là ở ngôn ngữ cấp cao ta biểu đạt ý định, còn ở ngôn ngữ cấp thấp ta buộc phải biểu đạt cơ chế bên dưới
    • “Ý định” ở đây có vẻ gần với “dịch byte này sang trái ba vị trí” hơn là “tính thuế suất cho giao dịch mua này”
      Đó là ý định về muốn khiến máy làm gì, hơn là muốn đạt được điều gì
      Mã như purchase.calculate_tax().await.map_err(|e| TaxCalculationError { source: e })?; đầy ý định, nhưng không thể biết cuối cùng machine code nào sẽ được sinh ra
  • Cú pháp vòng lặp for đó thật kinh khủng
    Ý là có hai list đặt cạnh nhau, và vị trí phần tử trong một list tương ứng với vị trí phần tử trong list kia sao?
    Chỉ nhìn thôi đã đau mắt
    Có vẻ các ngôn ngữ hiện đại đã đi sai đường khi bắt đầu thêm đủ thứ “ma thuật” vào parser và rải các ký hiệu nhỏ khắp nơi trong mã
    Không phải dạng tôi muốn nhìn chằm chằm hàng giờ

    • Những mảng như vậy là pattern rất phổ biến trong mã cấp thấp bất kể ngôn ngữ, và việc duyệt song song cũng vậy
      Vì thế việc Zig cung cấp cú pháp giúp làm đúng việc đó một cách tiện lợi mà vẫn cho thấy rõ điều gì đang xảy ra là tự nhiên
      Cá nhân tôi thấy làm khá tốt, nên tò mò vì sao lại đau mắt
  • Tôi thật sự thích mô hình allocator của Zig
    Giá mà trong Go cũng có thể dùng thứ như allocator theo từng request thay cho garbage collection

    • Trong Go cũng có custom allocator và arena, và thực tế là có tồn tại, nhưng tính tiện dụng rất tệ và khó dùng cho đúng
      Vì bản thân ngôn ngữ không có cách biểu đạt và cưỡng chế quy tắc ownership, cuối cùng chẳng khác gì viết C với cú pháp hơi khác rồi hy vọng mọi thứ ổn
      Nếu không có garbage collection thì ngay cả C++ cũng an toàn hơn Go rất nhiều
  • Memory model của Rust cho phép trình biên dịch luôn giả định rằng đối số hàm tuyệt đối không tham chiếu alias, nhưng trong Zig phải chỉ định điều này thủ công
    Tôi đang tránh các chỉ định alias kiểu này
    Vì ít người hiểu, và nếu dùng sai có thể tạo ra bug khó hiểu trong mã

  • Về lập luận rằng sự linh hoạt comptime của Zig đã mang lại cải tiến tốt cho các ngôn ngữ khác: thực thi hàm tại thời gian biên dịch và hàm nhận đối số hằng đã được D giới thiệu từ năm 2007 và khiến nhiều ngôn ngữ áp dụng thứ tương tự
    https://dlang.org/spec/function.html#interpretation

  • Tôi thích Zig, nhưng câu “tôi thích sự dài dòng của Zig” nghe có vẻ kỳ lạ
    Đúng là C quá lỏng lẻo ở nhiều ngóc ngách, nhưng theo tiêu chuẩn hiện nay, Zig đôi khi lại vung hơi quá sang phía ngược lại, khiến có quá nhiều nhiễu ký hiệu kiểu dữ liệu như chú thích
    Điều này đặc biệt đúng với việc ép kiểu số nguyên tường minh trong biểu thức; liên quan đến việc này, tôi có viết một chút ở đây: https://floooh.github.io/2024/08/24/zig-and-emulators.html
    Về mặt hiệu năng, nếu mã Zig nhanh hơn mã C tương tự thì thường là do thiết lập tối ưu hóa LLVM của Zig mạnh tay hơn
    Ví dụ, Zig mặc định dùng -march=native và tối ưu hóa toàn chương trình, đồng thời biên dịch toàn bộ mã Zig của dự án thành một đơn vị biên dịch duy nhất
    Hầu như mọi “mẹo” kiểu dùng unreachable làm gợi ý tối ưu hóa cũng có thể làm trong C, chỉ là đôi khi cần phần mở rộng ngôn ngữ không chuẩn
    Trình biên dịch C, đặc biệt là Clang, cũng gấp hằng rất mạnh tay, và có thể thu gọn những vùng mã lớn có thể gấp thành hằng ngay cả khi có ngăn xếp lời gọi sâu
    Vì vậy nếu chỉ nhìn vào sinh mã, nhiều khi không khác comptime của Zig là mấy
    Ưu điểm của comptime là nó không âm thầm quay lại thành mã runtime, còn mã không phải comptime cũng là đối tượng của cùng kiểu tối ưu hóa gấp hằng như C
    Chẳng hạn, nếu một hàm không-comptime “thuần” được gọi với đối số hằng, trình biên dịch vẫn sẽ thay lời gọi hàm bằng kết quả của nó
    Tóm lại, nếu mã C chậm hơn mã Zig thì nên kiểm tra thiết lập trình biên dịch C. Rốt cuộc, mọi phần việc nặng của tối ưu hóa đều diễn ra bên dưới LLVM

    • Liên quan đến ví dụ ép kiểu, có thể bọc thao tác cast trong một hàm
      fn signExtendCast(comptime T: type, x: anytype) T { const ST = std.meta.Int(.signed, @bitSizeOf(T)); const SX = std.meta.Int(.signed, @bitSizeOf(@TypeOf(x))); return @bitCast(@as(ST, @as(SX, @bitCast(x)))); }
      export fn addi8(addr: u16, offset: u8) u16 { return addr +% signExtendCast(u16, offset); }
      Nó biên dịch ra cùng assembly, có thể tái sử dụng, và ý định cũng rõ ràng hơn
    • Zig có những ý tưởng thú vị, và tôi tưởng bài viết sẽ tập trung hơn vào tối ưu hóa cấp thấp, nhưng cuối cùng nội dung lại là “comptime và biên dịch toàn chương trình rất tuyệt”
      Tôi đồng ý với điều đó
      Virgil đã có thể dùng toàn bộ ngôn ngữ ở thời điểm biên dịch từ năm 2006, và cũng hỗ trợ biên dịch toàn chương trình
      Tuy nhiên, vì Virgil không nhắm tới LLVM, so sánh tốc độ rốt cuộc trở thành so sánh giữa hai backend của trình biên dịch
      Virgil phụ thuộc nhiều vào phân tích khả đạt và các tối ưu hóa chuyên biệt hóa mà mô hình biên dịch này cho phép
      Ví dụ, nó khử ảo hóa các lời gọi phương thức một cách mạnh tay, loại bỏ các trường và đối tượng không thể đạt tới, nâng hằng qua các trường và đối tượng heap, và đơn hình hóa hoàn toàn mã đa hình
    • Khi backend x86 mới ra mắt, có thể ta sẽ thấy phần chênh lệch hiệu năng giữa C và Zig nào có thể quy riêng cho bản thân dự án Zig
    • Liên quan đến ép kiểu số nguyên tường minh, có vẻ sắp có phần dọn dẹp: https://ziggit.dev/t/short-math-notation-casting-clarity-of-...
    • Trong vài năm tới, tôi nghĩ xu hướng bổ sung của toàn bộ các ngôn ngữ sẽ dịch chuyển khá mạnh về phía ngôn ngữ dài dòng và tường minh hơn
      Chỉ vì điều đó khiến AI dễ xử lý hơn
      Việc dùng AI để lập trình có phải là ý tưởng hay hay không, và hay đến mức nào, là chuyện khác; nhưng nhiều lập trình viên tin như vậy, và các ngôn ngữ sẽ cố gắng đáp ứng họ