
Sự trả thù của Grace Hopper
Grace Hopper's Revenge
Các benchmark hiện tại cho AI code thường tập trung vào Python, điều này có thể làm sai lệch đánh giá về khả năng của LLM trong các môi trường lập trình khác nhau. Một benchmark mới có tên AutoCodeBench đã chỉ ra rằng các ngôn ngữ như Elixir, Kotlin, Racket và C# lại có hiệu suất cao hơn Python và JavaScript khi sinh code bởi LLM. Điều này đi ngược lại với suy nghĩ thông thường rằng dung lượng dữ liệu huấn luyện là yếu tố quyết định hiệu suất. Kết quả này cho thấy các mô hình lập trình hàm (functional paradigms) và thiết kế ngôn ngữ có cấu trúc tốt đóng vai trò quan trọng hơn đối với việc AI sinh code hiệu quả, chứ không hẳn là dung lượng dữ liệu khổng lồ. Các nhà phát triển nên xem xét việc khám phá những ưu điểm của các ngôn ngữ có hiệu suất cao này, và nhận ra rằng lựa chọn thiết kế ngôn ngữ có ảnh hưởng đáng kể đến khả năng hỗ trợ lập trình của AI.
Thế giới phần mềm có rất nhiều quy tắc và luật lệ. Một trong những điều vui nhộn nhất là Định luật Kernighan: Việc gỡ lỗi khó gấp đôi so với việc viết mã ngay từ đầu. Vì vậy, nếu bạn viết mã một cách khéo léo...
Thế giới phần mềm có rất nhiều quy tắc và luật lệ. Một trong những điều vui nhộn nhất là Định luật Kernighan:
Việc gỡ lỗi khó gấp đôi so với việc viết mã ngay từ đầu. Do đó, nếu bạn viết mã một cách khéo léo nhất có thể thì theo định nghĩa, bạn không đủ thông minh để gỡ lỗi.
Tôi luôn hiểu Định luật Kernighan là về sự phức tạp—về việc giữ cho mã bạn viết càng đơn giản càng tốt để suy luận.
Với LLM, tôi đang tìm hiểu rằng nó cũng liên quan nhiều đến thiết kế ngôn ngữ.
Tôi vẫn thấy khá nhiều người trên Twitter thỉnh thoảng phàn nàn rằng họ đã thử quy trình viết mã do AI điều khiển và kết quả đầu ra rất tệ và họ có thể tự di chuyển nhanh hơn. Hiện tại có ít người như vậy hơn trong thế giới của Opus 4.5 và Gemini 3, nhưng họ vẫn ở đó. Mỗi lần nhìn thấy điều này, tôi đều muốn biết họ đang làm gì cũng như họ đang sử dụng ngôn ngữ và thư viện nào.
Các tiêu chuẩn chính dành cho kỹ sư phần mềm hiện nay là SWEBench dành cho mã hóa và TerminalBench dành cho các tác vụ máy tính. Điểm chuẩn được cho là đại diện cho tất cả tác vụ mã hóa, vì vậy, điều quan trọng cần lưu ý ở đây là SWEBench tập trung vào Python. TerminalBench bao gồm nhiều tác vụ máy tính đa dạng hơn, nhưng khi nhân viên cần viết mã, họ sẽ viết Python.
Đây thực sự là những điểm chuẩn của Python.
Vậy còn các ngôn ngữ khác thì sao? May mắn thay, có AutoCodeBench, không chỉ kiểm tra các mô hình khác nhau mà còn kiểm tra trên 20 ngôn ngữ lập trình khác nhau. Cái nhìn đó thế nào? Nó trông như thế này:
Bây giờ, những gì chúng ta được biết về các mô hình là chúng chỉ hoạt động tốt khi có dữ liệu đào tạo. Và vì vậy, các ngôn ngữ có lượng dữ liệu đào tạo khổng lồ sẽ hoạt động tốt nhất, phải không?
Hóa ra các mô hình đó hầu hết đều rất tệ với Python và Javascript.
Các ngôn ngữ có hiệu suất cao nhất (không phụ thuộc vào mô hình) là C#, Racket, Kotlin và đứng ở vị trí số 1 là Elixir.
Tôi đã sử dụng Elixir làm ngôn ngữ chính của mình được vài năm rồi, vì vậy tôi có sự thiên vị rõ ràng ở đây. Nhưng điều này chỉ ra điều gì đó quan trọng về thiết kế ngôn ngữ và bản chất của việc lập trình máy tính sẽ đi về đâu trong tương lai. Lượng dữ liệu huấn luyện không quan trọng như chúng ta nghĩ. Các mô hình chức năng chuyển giao tốt1. Cấu trúc âm lượng nhịp. JavaScript có dữ liệu huấn luyện nhưng chống lại kiến trúc. Elixir có ít dữ liệu hơn nhưng vẫn lưu chuyển theo nó.
Vậy hãy nói về Tesla.
Tesla đặt cược vào tầm nhìn khi mọi người khác đang lắp LIDAR lên mái nhà của họ. Điều này có vẻ ngây thơ—mắt con người là những cảm biến rẻ tiền bị đánh lừa bởi ánh sáng chói, mưa và bóng tối. Nhưng Tesla đã đặt cược vào điều đó vì đường sá không hỗn loạn một cách ngẫu nhiên. Chúng hỗn loạn theo cách con người đặc biệt, bởi vì con người đã tạo ra chúng. Đèn có mã màu. Đường sơn. Các dấu hiệu có hình dạng giống như ý nghĩa của chúng. Một thế kỷ ngữ pháp trực quan, được mọi người lái xe tiếp thu, được mã hóa ở mọi giao lộ.
Tesla và Picture đang đặt cược tương tự với robot. Hình dạng con người, bàn tay hình con người, chuyển động quy mô con người. Không phải vì nó thanh lịch – nó thực sự khó chế tạo hơn bánh xe và tay gắp. Nhưng con người đã xây dựng một thế giới cho con người. Cửa có chiều rộng bằng con người. Cầu thang có chiều cao bằng con người. Dụng cụ có tay cầm được tạo hình bằng lòng bàn tay. Chế tạo một robot có thể di chuyển giống như chúng ta di chuyển và cơ sở hạ tầng hàng nghìn năm sẽ được cung cấp miễn phí.
Đây không phải là vấn đề tối ưu hóa cho con người. Đó là về cơ sở hạ tầng: tối ưu hóa các giao diện chịu tải. Đối với ô tô và robot, đó là tầm nhìn và bàn tay—bởi vì chúng tôi đã xây dựng thế giới vật lý cho mắt và ngón tay.
Đối với phần mềm, giao diện chịu tải không thực sự là mã. Mã là thực hiện. Giao diện chịu tải là các ý tưởng được viết bằng tiếng Anh: tài liệu yêu cầu, báo cáo lỗi, thông số kỹ thuật giao diện, nhật ký kiểm tra. Con người xác định ý định và xác minh kết quả bằng ngôn ngữ. Mã chỉ là những gì diễn ra ở giữa.
Abelson và Sussman có câu nói nổi tiếng “Chương trình phải được viết cho con người đọc và chỉ ngẫu nhiên để máy thực thi”. Nhưng chúng tôi đã dành 50 năm để tối ưu hóa ngôn ngữ lập trình cho văn bản của con người. Chúng tôi xây dựng các đồ vật có nhận dạng và trạng thái vì đó là cách chúng tôi trải nghiệm thực tế—trẻ sơ sinh phát triển tính lâu dài của đồ vật khi được tám tháng. Nó cảm thấy tự nhiên. Nhưng nút thắt không bao giờ được tạo ra. Đó luôn là xác minh.
Khi Grace Hopper ban đầu tưởng tượng và viết trình biên dịch đầu tiên, cô đã hình dung ra lớp dịch chuyển trực tiếp từ tiếng Anh sang mã máy. 75 năm sau, cuối cùng chúng ta cũng có thể thực hiện được tầm nhìn ban đầu của bà.
“Các chương trình phải được viết để mọi người xác minh và chỉ ngẫu nhiên để máy móc thực thi.”
Đó là tuyên bố về trách nhiệm giải trình. Con người sở hữu các đặc điểm kỹ thuật. Con người sở hữu sự xác minh. Mọi thứ ở giữa là việc triển khai.
Con người chúng ta viết mã không giỏi lắm. Máy móc tốt hơn nhưng chúng lại là thứ tệ nhất từng có. Tốt thế nào? Tốt đến mức ngay cả Anthropic — mà tôi cảm thấy khá tự tin khi nói rằng có một số lập trình viên giỏi nhất ở mọi nơi — nói rằng Opus 4.5 hiện đã đánh bại tất cả trong số những người được thuê trong các bài kiểm tra viết mã của họ.
Các máy này tốt hơn. Vịnh sẽ phát triển. Nhưng hãy xem xét lý do tại sao.
Con người ghi nhớ qua các tình tiết và câu chuyện. Không phải điểm dữ liệu mà là cảnh — các chuỗi có trước và sau. Chúng ta tiến hóa để theo dõi một con vật khi nó di chuyển sau một tảng đá, để ghi nhớ rằng bụi dâu vẫn ở đó ngay cả khi chúng ta không thể nhìn thấy nó, và để dựng nên những đoạn phim nhỏ về nhân quả. Không có gì ngạc nhiên khi chúng tôi yêu thích các câu lệnh if-then. Không có gì ngạc nhiên khi chúng tôi đã xây dựng các ngôn ngữ lập trình giả mạo thế giới thực và đọc giống như các âm mưu: đầu tiên làm cái này, sau đó làm cái kia, bây giờ hãy kiểm tra xem nó có hoạt động không. Chúng tôi đã viết mã khớp với những bộ phim trong đầu mình.
LLM không có phim.
Sự phức tạp tồn tại trong cơ sở mã, đúng vậy, nhưng lập trình viên cần suy luận về trạng thái thời gian chạy của chương trình. điều này áp dụng cho phần này của hàm và var foo bị ràng buộc bên trong hàm này như thế nào?
Đây có phải là nơi chứa đựng sự phức tạp? Hay nó nên được gói gọn trong tiếng Anh, trong thế giới lộn xộn, cố hữu của các yêu cầu sản phẩm, tài liệu thiết kế và báo cáo lỗi xung quanh mã?
Grace Hopper sẽ có câu trả lời.
Bây giờ hãy xem xét các kỹ năng khác biệt của LLM. Họ là những người kết hợp khuôn mẫu phi thường. Họ tìm thấy các thành ngữ trong kho văn bản khổng lồ, xử lý tốt cấu trúc được khai báo, lý luận cục bộ trong bối cảnh hạn chế.
Và họ kém về không gian và thời gian vật lý. Đây là lý do tại sao họ cần một lượng dữ liệu khổng lồ và khả năng tinh chỉnh để có thể xử lý tốt hình ảnh và video. Họ rất tệ trong việc kể chuyện nhất quán. Họ rất tệ trong việc duy trì lượng lớn trạng thái.
Javascript cung cấp những gì? Ba giờ để gỡ lỗi một thành phần React và chúng tôi đi sâu vào năm lớp trong dấu vết ngăn xếp mà không cho chúng tôi biết điều gì. Lỗi nằm ở useEffect. useEffect nào? Cái nào kích hoạt khi gắn kết, cái nào kích hoạt khi cập nhật, cái nào kích hoạt bất cứ khi nào trạng thái thay đổi ngoại trừ khi nó không hoạt động vì mảng phụ thuộc đang nói dối? Chúng ta phải tái tạo lại toàn bộ vòng đời trong đầu mình—điều gì đã diễn ra, theo thứ tự nào, lời hứa nào đã được thực hiện và với điều gì ràng buộc với điều này —chỉ để hiểu tại sao một nút không chuyển đổi. Chúng tôi sẽ kết thúc việc thực hiện khảo cổ học trong công việc của mình từ thứ Ba.2
Mặt khác, các hàm trong Elixir là thuần túy. Bạn có đầu vào và bạn nhận được đầu ra. Một hàm lấy hình dạng này và trả về hình dạng đó. Thế thôi. Đó là toàn bộ câu chuyện. Không có nơi nào để che giấu sự phức tạp. Không có không có trạng thái đột biến. Tất cả dữ liệu là bất biến. Khớp mẫu có nghĩa là “hình dạng” của dữ liệu luôn được xác định rõ ràng trong các tham số. Kết hợp điều này với nhiều đầu chức năng, điều mà thoạt đầu con người có cảm giác kỳ lạ và bạn sẽ có được bối cảnh cục bộ rất rõ ràng: chức năng này thực hiện công việc này và dữ liệu luôn trông giống nhau.
Đối với con người, lập trình hướng đối tượng có cảm giác tự nhiên và lập trình chức năng có cảm giác kỳ lạ. Chức năng cần dịch. Không có chiếc cốc nào chuyển động; có một hàm lấy “cốc ở vị trí A” và trả về “cốc ở vị trí B”. Hoàn toàn không trực quan đối với những sinh vật đã phát triển các đối tượng theo dõi trong không gian.
Lập trình với các đối tượng và trạng thái dễ viết hơn. Lập trình với các hàm và dữ liệu bất biến dễ xác minh hơn. Đây là sự khác biệt giữa dễ dàng và đơn giản. Khi chúng tôi tiếp tục thêm những thứ “dễ dàng”, chúng tôi làm cho hệ thống trở nên quá phức tạp.
Mã được viết tốt nhất bằng các công cụ đơn giản, nguyên thủy đơn giản và cấu trúc lặp lại. Điều đáng chú ý không chỉ là thiết kế ngôn ngữ Elixir mà còn là toàn bộ hệ sinh thái. Trong Elixir, có một hệ thống xây dựng. Một tùy chọn định dạng. Có một thư viện dành cho Enums và Collections. Việc đặt tên có thể đoán trước được. Sự đơn giản là một giá trị rõ ràng và tất cả đều có tác dụng. Một LLM được đào tạo trên Elixir nhìn thấy các mẫu giống nhau nhiều lần.3 Một LLM được đào tạo về JavaScript có thể thấy hàng nghìn biến thể.
Elixir tối đa hóa lượng ý nghĩa chương trình hiển thị trong ngữ cảnh cục bộ. Và LLM là máy ngữ cảnh.
Điều này đưa chúng ta trở lại Định luật Kernighan. Khi chúng ta được khuyến khích viết những cỗ máy trạng thái phức tạp, chúng ta gặp khó khăn trong việc gỡ lỗi chúng vì chúng ta rất dễ đạt đến giới hạn nhận thức của mình.
Và bây giờ chúng ta có những LLM này và mọi người đều lo lắng rằng máy móc sẽ viết mã mà con người không thể đọc được. Tôi không lo lắng về điều đó. Nhưng tôi nghĩ về thiết kế ngôn ngữ. LLM có thể viết Python và JS ở mức khá, nhưng họ viết Elixir và Racket xuất sắc. Lượng dữ liệu đào tạo không thành vấn đề.
Điều quan trọng là vị trí địa phương: liệu LLM có thể thấy mọi thứ nó cần mà không cần phải xây dựng lại trạng thái từ nơi khác hay không. Khớp mẫu làm cho hình dạng dữ liệu trở nên rõ ràng. Tính bất biến có nghĩa là không có đột biến ẩn. Đường ống và thành phần có nghĩa là dòng chảy có thể dự đoán được. Một cách để thực hiện mọi việc là lặp lại các mẫu.
Đây chính là những tính năng tương tự giúp con người kiểm tra mã. Chúng giúp mã có thể xem xét, sửa lỗi, chứng minh được.
Các ngôn ngữ chức năng có ngữ nghĩa rõ ràng được tối ưu hóa cho quá trình tạo máy và xác minh của con người. Đây là điểm ngọt ngào. Và nơi chúng tôi đang phát triển.
Trước đây, con người viết, đọc và gỡ lỗi mã.
Bây giờ LLM viết mã, con người đọc và gỡ lỗi. (Và LLM viết rất nhiều mã tầm thường bằng các ngôn ngữ dài dòng.)
Con người sẽ ngày càng làm ít hơn. LLM sẽ viết mã, gỡ lỗi và quản lý các trường hợp khó khăn. LLM sẽ xác minh dựa trên các thông số kỹ thuật của con người, đánh giá của con người, yêu cầu của con người. Và con người sẽ chỉ can thiệp khi mọi thứ không được như ý muốn. Họ có thể thấy điều này vì họ có cơ chế xác minh dễ dàng.
Để làm tốt điều đó, chúng tôi cần có hợp đồng rõ ràng. Hiệu ứng rõ ràng. Thuộc tính có thể kiểm tra được. Logic có thể kiểm tra được Các mảnh ghép được.
Đây là những ưu điểm của lập trình chức năng. Và điều gì khiến việc xác minh chính thức trở nên khả thi. Và những gì LLM xử lý tốt nhất.
Tôi hiện đang chạy Claude Code hàng ngày, tạo ra hàng triệu mã thông báo và xuất ra nhiều mã hơn mức tôi có thể nhập trong một ngày. Tất cả đều là Elixir, mọi lúc, với các tài liệu lập kế hoạch nghiêm ngặt để hiểu được sự phức tạp. Cấu trúc đơn giản và giao diện rõ ràng ở cấp độ chức năng. Nếu tôi viết React, tôi sẽ lo lắng về các thư viện, cấu trúc thành phần, cách nó tương tác với công cụ xây dựng đã chọn...Tôi sẽ rất sợ món súp spaghetti mà tôi sẽ sống trong đó.
Tôi không thể quay lại viết mã của riêng mình vào thời điểm này. Không chỉ vì nó sẽ chậm hơn rất nhiều mà còn vì điều này rõ ràng là tốt hơn. Có thể đọc được. Có thể kiểm chứng được. Đối với tôi, viết sẽ khó hơn nhưng lại rất dễ mò mẫm.
LLM đã xuất hiện và cho chúng tôi thấy ngôn ngữ nào thực sự được thiết kế tốt. Các bài kiểm tra AutoCodeBench là một thông điệp: các ngôn ngữ “cứng” chưa bao giờ khó. Họ chỉ chờ đợi một tâm trí không cần phim ảnh.
Tương lai của công nghệ phần mềm vẫn phụ thuộc vào con người, nhưng chúng tôi sẽ không viết mã nữa. Hãy để việc đó cho máy móc, chỉ cần cung cấp cho chúng những công cụ tốt để làm việc.
Những người đam mê Haskell và Erlang đã không sai về thiết kế ngôn ngữ. Họ chỉ đến sớm thôi. McCarthy là người đầu tiên: Lisp vào năm 1958.
NVIDIA đặt tên cho cấu trúc chip AI hỗ trợ mọi tiến bộ của chúng tôi sau Grace Hopper. Những cỗ máy được đặt theo tên của cô ấy cuối cùng đã cho chúng ta thấy những gì cô ấy nhìn thấy.
Nhưng đó không chỉ là về lập trình chức năng per se. Rust hoạt động rất kém mặc dù rõ ràng, được đánh máy và có chức năng. Tại sao? Có rất nhiều trạng thái: độ phức tạp của bộ kiểm tra khoản vay đòi hỏi lý luận toàn cầu về thời gian tồn tại.
Điều thú vị là TypeScript (47,2%) đánh bại JavaScript (38,6%) khoảng ~9 điểm—hệ thống loại giúp ích rất nhiều. Nhưng cả hai vẫn là nửa dưới. Tôi cá rằng bất kỳ mã cụ thể nào của React sẽ đạt điểm thậm chí còn thấp hơn do đột biến useState, theo dõi sự phụ thuộc useEffect và độ phức tạp của vòng đời.
Elixir kế thừa triết lý "hãy để nó sụp đổ" của Erlang. Trong hầu hết các ngôn ngữ, bạn viết mã phòng thủ để xử lý mọi trường hợp cạnh: kiểm tra null, khối thử/bắt, xác thực ở mọi ranh giới. Trong Elixir, bạn viết đường dẫn hạnh phúc và để cây giám sát xử lý lỗi. Điều này có nghĩa là ít logic phân nhánh hơn để tạo ra, mục đích rõ ràng hơn trong mã và ít đường dẫn xử lý lỗi dẫn đến sai sót hơn. LLM không cần phải đoán mô hình xử lý lỗi nào trong số năm mô hình xử lý lỗi mà mã xung quanh sử dụng.
Không có bài đăng nào
Tác giả: ashirviskas