4 điểm bởi GN⁺ 4 ngày trước | 1 bình luận | Chia sẻ qua WhatsApp
  • Ra đời giữa tình trạng hỗn loạn phần mềm của Bộ Quốc phòng Mỹ trong thập niên 1970, Ada là một ngôn ngữ lấy kiểu tĩnh mạnh và sự tách biệt giữa đặc tả với triển khai làm cốt lõi
  • Thông qua cấu trúc package và ẩn biểu diễn, Ada hiện thực hóa đóng gói hoàn chỉnh, và sau đó ảnh hưởng đến hệ thống mô-đun của các ngôn ngữ hiện đại như Java, C# và Go
  • Kiểu ràng buộc ngữ nghĩa, generic, đồng thời (task), thiết kế theo hợp đồng là những khái niệm Ada đã đưa ra trước hàng chục năm, rồi được Haskell, Rust, Swift và các ngôn ngữ khác kế thừa
  • SPARK Ada loại bỏ cả tranh chấp dữ liệu lẫn lỗi logic thông qua kiểm chứng hình thức, và được dùng trong các lĩnh vực đòi hỏi độ tin cậy cao như hàng không, đường sắt và hệ thống quốc phòng
  • Dù không phải ngôn ngữ phổ biến đại chúng, Ada vẫn là “ngôn ngữ âm thầm vận hành một cách đúng đắn”, nền tảng đã đặt ra các nguyên tắc cốt lõi cho thiết kế ngôn ngữ lập trình hiện đại

Bối cảnh ra đời và triết lý thiết kế của Ada

  • Đầu thập niên 1970, Bộ Quốc phòng Mỹ (DoD) khảo sát tình trạng hơn 450 ngôn ngữ và phương ngữ cùng tồn tại trong các hệ thống vũ khí, hậu cần và truyền thông
    • Mỗi hệ thống đều gặp các vấn đề như không thể tương tác, không thể bảo trì, hoặc tác giả ban đầu không còn nữa
    • Điều này dẫn đến khủng hoảng mua sắm phần mềm
  • Thay vì chọn một ngôn ngữ có sẵn như COBOL, Fortran hay PL/1, DoD đã trải qua quy trình xác định yêu cầu kéo dài 5 năm
    • Tài liệu phát triển theo các giai đoạn Strawman → Woodenman → Tinman → Ironman → Steelman
    • Steelman (1978) yêu cầu tách biệt giao diện một cách tường minh, kiểu tĩnh mạnh, đồng thời tích hợp sẵn, xử lý ngoại lệ nhất quán, độc lập máy, dễ đọc và có thể kiểm chứng
  • Trong cuộc cạnh tranh giữa 4 nhóm vào năm 1979 (Green, Red, Blue, Yellow), nhóm Green do Jean Ichbiah dẫn dắt đã được chọn, và ngôn ngữ được đặt tên là Ada
    • Cái tên này nhằm tôn vinh Ada Lovelace, đồng thời tượng trưng cho chủ đích của ngôn ngữ

Cấu trúc package và đóng gói

  • Cấu trúc trung tâm của Ada là package, trong đó specification và body được tách biệt vật lý
    • Specification là hợp đồng công khai ra bên ngoài, còn body là phần triển khai; trình biên dịch ép buộc mối quan hệ giữa hai phần này
    • Mã client không thể truy cập vào các thành phần không có trong specification
  • Cấu trúc này là nguyên mẫu của hệ thống mô-đun, và các ngôn ngữ về sau chỉ mô phỏng lại một phần
    • Java, Python, JavaScript, C, Go, Rust đều chưa hiện thực được sự tách biệt cấu trúc hoàn chỉnh như Ada
  • Kiểu private chỉ lộ tên, còn biểu diễn bên trong thì hoàn toàn mờ đục
    • Client không thể biết cấu trúc nội bộ của kiểu, và chỉ có thể dùng các phép toán được cho phép
    • Đây là ẩn biểu diễn (representational invisibility) mạnh hơn cả kiểm soát truy cập private của Java
  • Java và C# đã phát triển dần trong nhiều thập niên để tiến tới mức đóng gói như Ada

Hệ thống kiểu và ràng buộc ngữ nghĩa

  • Ada định nghĩa sự phân biệt giữa type và subtype theo nghĩa toán học
    • Ví dụ: type Age is range 0 .. 150 tạo ra một kiểu riêng với ràng buộc phạm vi
    • Việc truyền sai giữa các kiểu sẽ bị phát hiện thành lỗi thời điểm biên dịch
  • Ở thời điểm năm 1983, hệ thống kiểu của Ada biểu đạt mạnh hơn rất nhiều so với C, Fortran và Pascal
    • Kiểu ràng buộc ngữ nghĩa giúp ngăn lỗi ở mức miền nghiệp vụ
  • Bản ghi phân biệt (discriminated record) là cấu trúc có các trường khác nhau tùy theo giá trị
    • Đây chính là sum type hay kiểu dữ liệu đại số (ADT) trong các ngôn ngữ hiện đại
    • Haskell, Rust, Swift, Kotlin, TypeScript và nhiều ngôn ngữ khác đã đưa cùng khái niệm này vào sau đó hàng chục năm

Generic và đa hình

  • Generic của Ada là đơn vị có thể nhận kiểu, giá trị, chương trình con và package làm tham số
    • Nó hiện thực đa hình tĩnh (parametric polymorphism) với việc kiểm tra kiểu ngay khi biên dịch
  • C++ (1990), Java (2004), C# (2005), Go (2022) và các ngôn ngữ khác đã mất hàng chục năm sau Ada mới đưa vào các tính năng tương tự
    • Java dùng type erasure, làm mất thông tin kiểu lúc chạy
    • Ada vẫn giữ được kiểu lúc chạy và còn hỗ trợ tham số hóa package
  • Generic của Ada cung cấp mức độ biểu đạt tương đương higher-kinded polymorphism
    • Tương tự với type class của Haskell, trait của Rust, hay concepts trong C++20

Mô hình đồng thời và độ an toàn

  • Từ năm 1983, Ada đã tích hợp đồng thời ở cấp ngôn ngữ (task)
    • Thông qua khai báo task và mô hình giao tiếp rendezvous, nó hiện thực truyền thông điệp không cần chia sẻ trạng thái
    • Channel của Go thuộc cùng họ khái niệm CSP (Communicating Sequential Processes)
  • Ada 95 đưa vào protected object
    • Nó bảo vệ truy cập dữ liệu và phân chia thành procedure, function, entry
    • Cung cấp điều kiện chặn tự độngđồng bộ hóa không khóa
  • SPARK Ada dùng kiểm chứng hình thức để chứng minh bằng toán học rằng không có tranh chấp dữ liệu, ngoại lệ, lỗi phạm vi, hay vi phạm điều kiện trước/sau
    • Trong khi borrow checker của Rust chỉ bảo đảm an toàn bộ nhớ, SPARK còn chứng minh được cả tính nhất quán logic

Thiết kế theo hợp đồng và an toàn null

  • Ada 2012 tích hợp hợp đồng (contracts) vào trong ngôn ngữ
    • Có thể khai báo precondition, postcondition, và invariant của kiểu
    • Chuỗi công cụ SPARK tận dụng các yếu tố này cho việc chứng minh tĩnh
  • Khái niệm Design by Contract của Eiffel (1986) được Ada chính thức hóa ở cấp ngôn ngữ
    • C++, Java, Python, Rust và các ngôn ngữ khác vẫn chỉ dừng ở mức triển khai một phần hoặc ở cấp thư viện
  • Ada 2005 đưa vào kiểu not null để hỗ trợ loại trừ null ngay lúc biên dịch
    • Mặc định, nó bảo đảm thất bại an toàn bằng ngoại lệ thời gian chạy (Constraint_Error)
    • Cách tiếp cận này tương tự nullable reference của C# 8.0

Cấu trúc xử lý ngoại lệ

  • Ada 83 là ngôn ngữ đầu tiên đưa vào xử lý ngoại lệ có cấu trúc (structured exception handling)
    • Ngoại lệ được khai báo rồi mới sử dụng, được xử lý theo phạm vi, và có quy tắc lan truyền rõ ràng
  • Checked exception của Java là một dạng phát triển hơn so với Ada, trong đó bên gọi phải khai báo ngoại lệ
    • Ada cho phép lan truyền ngoại lệ tự do hơn
  • Rust loại bỏ ngoại lệ và chọn xử lý lỗi dựa trên kiểu Result
    • Đóng góp của Ada là làm cho việc lan truyền ngoại lệ trở nên có cấu trúc và có thể dự đoán được

Annex và cấu trúc tiêu chuẩn hóa

  • Chuẩn Ada có cấu trúc mở rộng tùy chọn gọi là Annex
    • Annex C~H định nghĩa các tính năng cho hệ thống, thời gian thực, phân tán, số học và các lĩnh vực độ tin cậy cao
    • Trình biên dịch phải nhận chứng nhận độc lập cho từng Annex
  • ACAA dùng bộ kiểm thử ACATS để xác minh mức độ tuân thủ chuẩn
    • Cấu trúc chuẩn của Ada có thể được áp dụng trực tiếp vào chứng nhận phần mềm hàng không DO-178C
    • C/C++ cũng có thể đạt chứng nhận tương tự, nhưng Ada phù hợp hơn về mặt cấu trúc

Ảnh hưởng của Ada và sự mất cân bằng trong nhận thức

  • Ada là một ngôn ngữ do chính phủ dẫn dắt, nên không được chú ý nhiều trong văn hóa công nghệ kiểu Silicon Valley
    • Điều này đối lập với xu hướng ưa chuộng cú pháp ngắn gọn dựa trên C
  • Các trường hợp thành công của Ada (hàng không, đường sắt, hệ thống quốc phòng) ít hiện diện vì không có thất bại
    • Những hệ thống có độ tin cậy cao không tạo ra tranh luận hay sự cố để thu hút chú ý
  • Hướng phát triển của các ngôn ngữ hiện đại đang hội tụ về những nguyên tắc mà Ada đã nêu ra từ trước
    • Như tách biệt đặc tả và triển khai, kiểm tra kiểu tĩnh, đồng thời ở cấp ngôn ngữ, an toàn dựa trên hợp đồng
  • Ada hiện vẫn đang vận hành trong các hệ thống độ tin cậy cao như máy bay, đường sắt và tàu vũ trụ, và vẫn là “ngôn ngữ âm thầm vận hành một cách đúng đắn”

1 bình luận

 
Ý kiến Hacker News
  • Tôi thích Ada. Nhưng khi bàn về xử lý kiểu mà lại hoàn toàn bỏ qua các ngôn ngữ họ ML (ML, SML, CML, Caml, OCaml, v.v.) thì thật đáng ngạc nhiên
    Các ngôn ngữ này hỗ trợ kiểu cấu trúc ở cấp trình biên dịch. Vấn đề của Ada là bản thân ngôn ngữ quá lớn và cú pháp quá phức tạp, giống như PL/I, PHP, Perl. Bài viết xem đó là ưu điểm, nhưng cá nhân tôi cho rằng các phần mở rộng tiêu chuẩn được tách ra thành Annex còn hay hơn. Nếu ngôn ngữ lõi được giữ nhỏ gọn và đi theo hướng lấy Annex làm trung tâm, có lẽ nó đã được dùng rộng rãi hơn

    • Trong các ngôn ngữ ML, người dùng không thể tự định nghĩa kiểu số nguyên / số thực có giới hạn. Đây chính là một trong những tính năng cốt lõi của Ada. Khi đã trải nghiệm hệ thống kiểu của Ada, bạn sẽ ngạc nhiên vì nó nâng cao chất lượng và độ tin cậy của mã đến mức nào
  • Một trong những lý do Ada bị ghẻ lạnh là vì giá trình biên dịch lên tới hàng chục nghìn đô la. Thời chưa có trình biên dịch miễn phí hay mã nguồn mở, các ngôn ngữ khác có thể dùng miễn phí. Đó là yếu tố mang tính quyết định

    • Có nhiều nguyên nhân khiến Ada không thể thoát khỏi thị trường ngách. Độ phức tạp của ngôn ngữ cùng với công nghệ trình biên dịch thời đó khiến nó không thể chạy tử tế trên máy vi tính thập niên 1980. Intel từng tạo ra i432, đưa các khái niệm của Ada vào phần cứng, nhưng hiệu năng lại tệ hại. Sau đó máy vi tính thống trị thế giới, và di sản của C cùng assembly kéo dài hơn 20 năm
    • Tôi đã dùng Ada vài năm, nhưng việc đồng nghiệp dùng các ngôn ngữ khác như sở thích cá nhân không giúp ích gì cho việc phổ biến Ada. Xử lý chuỗi cũng yếu, tốc độ lại chậm. Đặc biệt, xử lý đồng thời bất tiện vì không dùng OS thread. Cuối cùng chúng tôi phải dùng phần mở rộng thời gian thực của HPUX để chạy thành nhiều tiến trình
    • Từ giữa thập niên 90 đã có GNAT, và thời đó trình biên dịch thương mại cũng rất phổ biến. Lý do Ada bị chặn lại là vì bị xem là có “overhead không cần thiết”. Nói cách khác, nó mang tiếng là ngoài hệ thống quân sự hay an toàn thì không có lý do gì để dùng
    • GNU Ada Compiler (GNAT) lần đầu được công bố vào năm 1995
    • Thực ra trong thập niên 80, chất lượng trình biên dịch của ngôn ngữ nào cũng không tốt. GCC cũng phải tới giai đoạn sau mới tạm ổn. Dùng ngôn ngữ mới mà trình biên dịch còn non là chuyện hiển nhiên. C++ khi đó cũng kém xa Ada, và “tính năng sát thủ của C++” thực chất là có thể dùng nó như C. Có lẽ lịch sử đã khác nếu Ada cung cấp chế độ tương thích Pascal
  • Khi đọc bài, tôi thấy cả Ada lẫn chính bài viết đều thú vị, nhưng có vài lỗi thực tế khá dễ thấy. Ví dụ, bài nói chỉ Ada mới tách biệt hoàn toàn implementation và specification, nhưng JavaScript cũng có thể định nghĩa các phần tử private trong module ES6. Phần giải thích về mức truy cập private của Java cũng sai. Những lỗi kiểu này làm độ tin cậy của bài giảm đi

    • Trong Ada, khi định nghĩa kiểu private thì bên ngoài không thể truy cập các trường nội bộ. Ngược lại, trong JavaScript, với bất kỳ object nào bạn cũng có thể tự do thêm hoặc xóa thuộc tính. Nói cách khác, mức bảo vệ lúc biên dịch của Ada không thể đem so với JS
    • Trong Java, có thể truy cập các thành viên private bằng reflection. Với setAccessible(true), thậm chí còn có thể sửa nội bộ của String
    • Tôi thấy câu nói rằng khi LLM viết cho độc giả là con người thì nó vận hành giống như Gell-Mann amnesia khá ấn tượng
  • Tổng thể bài viết khá hay, nhưng câu kiểu “ngôn ngữ X có tính năng này muộn hơn Ada” cứ lặp đi lặp lại nên khá chán. Nếu có ví dụ mã thì có lẽ sẽ thuyết phục hơn nhiều

    • Thực ra, nhiều tính năng của Ada là được vay mượn từ các ngôn ngữ trước đó (PASCAL, CLU, MODULA, CSP, v.v.). Nó không hẳn là các khái niệm hoàn toàn mới, mà là kết quả của việc tích hợp những ý tưởng đã được kiểm chứng
    • Một số khái niệm mà bài cho là Ada đưa vào trước thực tế thường được hiện thực theo cách khác. Vì vậy cần có ví dụ so sánh
    • Bài này không phải để khẳng định “Ada là tốt nhất”, mà là để cho thấy Ada đã được thiết kế như thế nào nhằm hướng tới an toàn và độ tin cậy. So sánh theo mốc thời gian có ý nghĩa trong bối cảnh đó
    • Tôi đã nhờ Claude viết mã demo Ada, và kết quả khá ổn. Tôi biết Ada từ lâu nhưng đến khi thử trực tiếp mới thấy FOMO
    • Từ góc nhìn của các nhà phát triển Ada, kiểu so sánh lặp đi lặp lại này có thể gây mệt mỏi vì đã kéo dài suốt nhiều năm
  • Tài khoản Twitter này được tạo vào tháng 4 năm 2026 và không ghi rõ tác giả. Trong thời gian ngắn mà năng suất khổng lồ như vậy, việc không lộ danh tính khiến người ta thấy khá thú vị

    • Cũng có ý kiến cho rằng lý do không có tác giả là vì đó là bot
  • Nhận định rằng “mọi ngôn ngữ trong 20 năm qua đều thêm sum type, còn Ada thì đã có từ đầu” là đúng, nhưng nguồn gốc của nó không phải từ Ada. Ngôn ngữ Hope hay NPL đã có trước

    • Thực ra nguồn gốc của sum type là bài báo năm 1964 của John McCarthy, “Definition of new data types in ALGOL X”. Ông đề xuất từ khóa UNION, sau đó khái niệm này được phát triển trong ALGOL 68, Hope, Miranda, v.v. union của C là một khái niệm khác
  • Tôi thích bài này đến mức hy vọng nó không phải do AI viết, nhưng tốc độ đăng trên Twitter quá nhanh nên vẫn thấy đáng nghi

    • Bài viết hơi dài dòng và lặp lại, nên có cảm giác giống AI
    • Dạo này có nhiều công cụ AI tự động viết lại bài blog nên càng khiến người ta nghi ngờ
    • Tôi cũng đọc bài rất thích, nhưng đến khi thấy 110 dấu em dash thì mới nghĩ “hay là AI nhỉ?”. Thật buồn khi giờ ngay cả bài blog cũng phải đọc bằng kiểu kiến thức truyền thông số
    • Tôi cũng đã viết 50 bài trong 3 năm nhưng phần lớn vẫn chưa xuất bản. Có khi tác giả này cũng từng ở hoàn cảnh tương tự rồi gần đây mới lấy dũng khí đăng lên. Tôi hiểu việc bị hiểu lầm là AI có thể gây nản lòng đến mức nào
    • Nếu AI thực sự có thể viết ở mức này, thì đáng giá đến mức bị lấy mất thời gian cũng chấp nhận được
  • Không quân Mỹ ban đầu định dùng Ada, nhưng do phát triển chậm nên đã dùng JOVIAL. Tôi làm dự án đầu tiên bằng JOVIAL vào năm 1981, khi đó Ada vẫn còn ở giai đoạn đặc tả

    • JOVIAL là ngôn ngữ phái sinh từ IAL, tiền thân của ALGOL 60, và đã ảnh hưởng tới các yêu cầu ban đầu khi phát triển Ada (STRAWMAN~STEELMAN). Sau đó Ada cải tiến các tính năng của JOVIAL và đưa vào những đổi mới như khai báo rõ tham số in/out. Nhờ vậy, nó không cần đến những thứ như copy constructor hay move semantics phức tạp kiểu C++
  • Trên trang chủ có câu “đây không phải lập trường mà là đề xuất”, và có người lấy đó làm căn cứ để khẳng định đây là website do AI viết

    • Nhưng câu đó không phải bằng chứng cho việc do AI viết
  • Bài viết nói rằng “hệ thống module của JavaScript không thể che giấu biểu diễn nội bộ của kiểu như Ada”, nhưng thực ra trong module JS, nếu không export thì hoàn toàn có thể ẩn đi. Vì vậy tôi nghi ngờ Ada có điểm gì vượt trội đặc biệt ở đây

    • Nếu lấy TypeScript làm chuẩn, object trong JS vẫn có thể bị thêm hoặc sửa thuộc tính từ bên ngoài. Trong Ada, với thực thể của kiểu private, không thể thao tác ngoài API đã được định nghĩa
    • Có thể xem ví dụ cụ thể ở bình luận này