John McCarthy, Lisp và nguồn gốc của thiết kế AI biểu tượng
Khám phá cách tiếp cận ký hiệu của John McCarthy và các ý tưởng thiết kế Lisp—danh sách, đệ quy, garbage collection—đã ảnh hưởng tới AI và lập trình hiện đại như thế nào.

Tại sao McCarthy và Lisp vẫn quan trọng
Đây không phải một chuyến tham quan bảo tàng về “AI xưa”. Đây là một bài học lịch sử mang tính thực dụng cho bất kỳ ai xây dựng phần mềm—lập trình viên, tech lead và người làm sản phẩm—bởi vì các ý tưởng của John McCarthy đã định hình cách chúng ta nghĩ về mục đích của ngôn ngữ lập trình.
Lisp không chỉ là cú pháp mới. Nó là một canh bạc rằng phần mềm có thể thao tác với ý tưởng (không chỉ số) và rằng các lựa chọn thiết kế ngôn ngữ có thể tăng tốc nghiên cứu, lặp sản phẩm và cả hệ sinh thái công cụ.
Một cách hữu ích để đọc di sản của McCarthy là như một câu hỏi vẫn còn giá trị ngày nay: chúng ta có thể biến ý định thành hệ thống có thể chạy được trực tiếp đến mức nào—mà không bị chết đuối trong boilerplate, ma sát hay độ phức tạp vô tình? Câu hỏi đó vang vọng từ REPL của Lisp tới các workflow “chat-đến-ứng-dụng” hiện đại.
Ý tưởng lớn của John McCarthy: Biến suy luận thành có thể lập trình
John McCarthy được nhớ đến không chỉ vì góp phần khai sinh lĩnh vực AI, mà còn vì khăng khăng theo đuổi một loại AI cụ thể: các hệ thống có thể thao tác ý tưởng, không chỉ tính toán kết quả. Vào giữa thập niên 1950, ông tổ chức Dartmouth Summer Research Project (nơi thuật ngữ “trí tuệ nhân tạo” được đề xuất) và sau đó ảnh hưởng tới công trình AI tại MIT và Stanford. Nhưng đóng góp lâu bền nhất của ông có thể là câu hỏi ông liên tục đặt ra: nếu suy luận có thể được biểu diễn dưới dạng chương trình thì sao?
Từ số tới ký hiệu
Phần lớn thành công ban đầu của tin học là số học: bảng balist, mô phỏng kỹ thuật, tối ưu hóa và thống kê. Những bài toán này phù hợp hoàn hảo với đại số.
McCarthy nhắm đến điều khác. Lý luận con người hoạt động với các khái niệm như “nếu”, “bởi vì”, “thuộc về”, “là một loại” và “tất cả những thứ thỏa điều kiện này”. Những thứ đó không được biểu diễn tự nhiên bằng số dấu phẩy động.
Cách tiếp cận của McCarthy coi tri thức là ký hiệu (tên, quan hệ, danh mục) và coi suy nghĩ là phép biến đổi theo quy tắc trên các ký hiệu đó.
Một cách hình dung tổng quan: các phương pháp số trả lời “bao nhiêu?” còn phương pháp ký hiệu cố gắng trả lời “đó là gì?” và “từ những gì chúng ta biết, suy ra được gì?”.
Lisp như công cụ cho nhiệm vụ đó
Khi bạn tin rằng suy luận có thể được lập trình, bạn cần một ngôn ngữ có thể biểu diễn thoải mái các biểu thức như quy tắc, phát biểu logic và các quan hệ lồng nhau—và sau đó xử lý chúng.
Lisp được xây dựng để phục vụ mục tiêu đó. Thay vì ép các ý tưởng vào cấu trúc dữ liệu cứng nhắc, Lisp làm cho việc biểu diễn mã và tri thức dưới dạng tương tự trở nên tự nhiên. Lựa chọn này không phải phong cách học thuật—nó là cầu nối thực dụng giữa mô tả một suy nghĩ và thực thi một thủ tục, đúng là cây cầu McCarthy muốn AI vượt qua.
Tư duy ký hiệu, bằng tiếng thường
Khi McCarthy và các nhà nghiên cứu AI đầu tiên nói “ký hiệu”, họ không ám chỉ toán học huyền bí. Một ký hiệu đơn giản là một nhãn có ý nghĩa: một tên như customer, một từ như hungry, hoặc một tag như IF và THEN. Ký hiệu quan trọng vì chúng cho phép chương trình làm việc với ý tưởng (danh mục, quan hệ, quy tắc) thay vì chỉ số thô.
Một cách dễ hình dung: bảng tính tuyệt khi thế giới của bạn là các cột và phép toán. Hệ thống ký hiệu tuyệt khi thế giới của bạn là các quy tắc, danh mục, ngoại lệ và cấu trúc.
Ký hiệu: tên mang ý định
Trong nhiều chương trình, khác biệt giữa 42 và "age" không phải là kiểu dữ liệu—mà là thứ giá trị đại diện. Một ký hiệu cho bạn thứ có thể so sánh, lưu trữ và kết hợp mà không mất nghĩa.
Điều đó giúp việc biểu diễn các thứ như “Paris là một thành phố” hoặc “nếu pin yếu thì tìm bộ sạc” trở nên tự nhiên.
Danh sách: cấu trúc đơn giản cho “được tạo từ các phần”
Để làm gì đó hữu ích với ký hiệu, bạn cần cấu trúc. Lisp phổ biến hóa một cấu trúc rất đơn giản: danh sách. Một danh sách chỉ là một nhóm có thứ tự các phần tử, và các phần tử đó có thể tự là danh sách. Với một ý tưởng đó, bạn có thể biểu diễn câu, biểu thức và tri thức dạng cây.
Đây là một ví dụ khái niệm nhỏ (theo phong cách giống Lisp):
(sentence (subject robot) (verb needs) (object power))
Nó gần như đọc được như tiếng Anh: một câu gồm chủ ngữ, vị ngữ và tân ngữ. Vì nó có cấu trúc, chương trình có thể lấy ra (subject robot) hoặc thay (object power) bằng thứ khác.
Từ cấu trúc đến suy luận: quy tắc, lập kế hoạch, tìm kiếm
Khi thông tin ở dạng cấu trúc ký hiệu, các nhiệm vụ AI cổ điển trở nên khả thi:
- Quy tắc: NẾU một mẫu khớp, THÌ suy luận ra điều mới.\n- Lập kế hoạch: biểu diễn mục tiêu và hành động dưới dạng danh sách, rồi xâu chuỗi hành động để đạt mục tiêu.\n- Tìm kiếm: duyệt các kết hợp ký hiệu (các trạng thái) khác nhau để tìm lời giải.
Điểm chuyển là chương trình không chỉ tính toán; nó thao tác các mảnh tri thức có ý nghĩa ở dạng mà nó có thể kiểm tra và biến đổi.
Tại sao lựa chọn thiết kế ngôn ngữ vang vọng qua thập kỷ
Các quyết định thiết kế của Lisp không dừng trong học viện. Chúng ảnh hưởng tới cách người ta xây dựng công cụ và tốc độ họ khám phá ý tưởng:
- Tính biểu đạt: Lisp làm việc với tri thức cấu trúc và biến đổi chúng một cách tự nhiên.\n- Lặp nhanh: phát triển tương tác rút ngắn vòng giữa “nghĩ” và “thử”.\n- Khả năng mở rộng: ngôn ngữ khuyến khích xây dựng trừu tượng thay vì lặp lại mẫu.
Những đặc tính đó tạo ra hệ sinh thái nơi thử nghiệm rẻ, nguyên mẫu thành sản phẩm nhanh hơn và đội ngũ có thể thích nghi khi yêu cầu thay đổi.
Mục tiêu thiết kế đằng sau Lisp
Lisp bắt đầu với một bài toán thiết kế rất thực dụng: làm sao viết chương trình có thể làm việc với ký hiệu tự nhiên như cách chúng làm với số?
McCarthy không cố gắng tạo ra “máy tính tốt hơn”. Ông muốn một ngôn ngữ nơi một biểu thức như (is (parent Alice Bob)) có thể được lưu, kiểm tra, biến đổi và suy luận dễ dàng như (+ 2 3).
Ngôn ngữ xây cho dữ liệu ký hiệu
Ưu tiên là làm cho thông tin ký hiệu dễ biểu diễn và thao tác. Điều đó dẫn đến tập trung vào danh sách và cấu trúc dạng cây, vì chúng phù hợp với cách con người đã dùng để diễn đạt ý nghĩa: câu, quy tắc logic, phân loại lồng nhau và các quan hệ.
Độ đơn giản mở khóa tính linh hoạt
Một mục tiêu khác là giữ lõi ngôn ngữ nhỏ và nhất quán. Khi ngôn ngữ có ít “trường hợp đặc biệt”, bạn dành ít thời gian học luật hơn và nhiều thời gian hơn để kết hợp ý tưởng. Lisp nghiêng về một tập nhỏ các khối xây dựng có thể kết hợp thành trừu tượng lớn hơn.
Chương trình có hình dạng như dữ liệu
Một nhận xét then chốt là chương trình và dữ liệu có thể chia sẻ cùng loại cấu trúc. Nói ngắn gọn: nếu dữ liệu của bạn là danh sách lồng nhau, chương trình của bạn cũng có thể là danh sách lồng nhau.
Điều đó có nghĩa là bạn có thể:
- sinh ra một chương trình (như dữ liệu có cấu trúc)\n- sửa nó (như bất kỳ dữ liệu nào khác)\n- rồi chạy kết quả
Một chuyển đổi văn hóa trong thiết kế ngôn ngữ
Lisp cũng phổ biến một tư duy: ngôn ngữ không nhất thiết phải một kích thước phù hợp cho mọi thứ. Chúng có thể được thiết kế quanh miền bài toán—như suy luận, tìm kiếm và biểu diễn tri thức—và vẫn ảnh hưởng tới lập trình chung nhiều thập niên.
S-Expressions: một cấu trúc đơn giản với hệ quả lớn
S-expressions (viết tắt của symbolic expressions) là ý tưởng đặc trưng của Lisp: một cách duy nhất, nhất quán để biểu diễn mã và dữ liệu dưới dạng danh sách lồng nhau.
Thoạt nhìn, S-expression chỉ là ngoặc bao quanh các phần tử—một số là nguyên tử (như tên và số), một số là danh sách. Qui tắc “danh sách trong danh sách” chính là trọng tâm.
Một hình dạng cho mọi thứ
Vì cấu trúc đồng nhất, chương trình Lisp được xây từ cùng khối xây dựng đến tận cùng. Một lời gọi hàm, một đoạn cấu hình, và một mảnh cấu trúc chương trình đều có thể biểu diễn như một danh sách.
Sự nhất quán đó đem lại lợi ích ngay lập tức:
- Parsing đơn giản hơn: ngôn ngữ không cần nhiều trường hợp đặc biệt.\n- Biến đổi mã khả thi: dễ dàng đi qua một cây danh sách và viết lại phần của nó.\n- Đánh giá nhất quán: bộ đánh giá có thể xử lý nhiều cấu trúc theo cùng một hình dạng cơ bản.
Ngay cả khi bạn không viết Lisp, bài học thiết kế này quan trọng: khi hệ thống được xây từ một hai dạng dự đoán được, bạn tốn ít thời gian chống lại các trường hợp biên và nhiều thời gian xây dựng.
Tính hợp thành có thể cảm nhận
S-expressions khuyến khích tính hợp thành vì các phần nhỏ, dễ đọc tự nhiên kết hợp thành phần lớn. Khi chương trình bạn “chỉ là danh sách lồng nhau”, kết hợp ý tưởng thường là lồng một biểu thức vào biểu thức khác, hoặc ghép danh sách từ các phần tái sử dụng.
Điều này thúc đẩy phong cách mô-đun: viết các thao tác nhỏ làm một việc, rồi xếp chúng lại để diễn tả ý định lớn hơn.
Đổi chác: ngoặc
Nhược điểm rõ ràng là sự không quen. Với người mới, cú pháp nhiều ngoặc trông lạ.
Nhưng lợi ích là tính dự đoán: khi bạn hiểu quy tắc lồng nhau, bạn có thể thấy cấu trúc chương trình một cách đáng tin—và công cụ cũng vậy. Sự rõ ràng này là lý do S-expressions có ảnh hưởng vượt ra ngoài Lisp.
Đệ quy và xử lý danh sách như các khối xây dựng lõi
Đệ quy dễ hiểu nhất qua ẩn dụ hàng ngày: dọn phòng lộn xộn bằng cách biến nó thành các “phòng” nhỏ hơn. Bạn không cố giải quyết mọi thứ cùng lúc. Bạn nhặt một món, đặt đúng chỗ, rồi lặp lại trên phần còn lại. Các bước đơn giản; sức mạnh đến từ việc lặp lại cho tới khi không còn gì để làm.
Lisp dựa vào ý tưởng này vì phần lớn dữ liệu của nó được xây từ danh sách: một danh sách có “phần đầu” và “phần còn lại”. Hình dạng đó phù hợp hoàn hảo với tư duy đệ quy.
Để xử lý danh sách, bạn xử lý phần tử đầu, rồi áp cùng logic lên phần còn lại. Khi danh sách rỗng, bạn dừng—đó là khoảnh khắc “không còn gì để làm” rõ ràng khiến đệ quy dễ hiểu hơn là huyền bí.
Ví dụ khái niệm nhỏ: tổng một danh sách
Giả sử bạn muốn tổng một danh sách số.
- Nếu danh sách rỗng, tổng là 0.\n- Nếu không, tổng là phần tử đầu cộng với tổng phần còn lại.
Chỉ vậy thôi. Định nghĩa đọc như tiếng thường, và cấu trúc chương trình phản chiếu ý tưởng.
Tư duy “duyệt cây”
AI ký hiệu thường biểu diễn biểu thức như cây (toán tử với các biểu thức con). Đệ quy là cách tự nhiên để “đi qua” cây đó: đánh giá phần trái theo cùng cách bạn đánh giá phần phải, và tiếp tục cho tới khi gặp giá trị đơn giản.
Những mẫu này đã định hình lập trình hàm sau này: hàm nhỏ, trường hợp cơ sở rõ ràng và biến đổi dữ liệu dễ lý giải. Ngay cả ngoài Lisp, thói quen chia công việc thành “làm một bước, rồi lặp trên phần còn lại” dẫn tới mã sạch hơn và ít tác dụng phụ ẩn.
Garbage Collection: tính năng tăng năng suất, không chỉ chi tiết kỹ thuật
Các lập trình viên thời đầu thường phải quản lý bộ nhớ thủ công: cấp phát không gian, theo dõi ai “sở hữu” nó, và nhớ giải phóng đúng lúc. Công việc đó không chỉ làm chậm phát triển—nó tạo ra loại lỗi khó tái tạo và dễ mang vào sản phẩm: rò rỉ làm giảm hiệu năng, con trỏ treo làm crash chương trình sau lỗi ban đầu.
Garbage collection làm gì (nói đơn giản)
John McCarthy giới thiệu garbage collection cho Lisp như một cách để lập trình viên tập trung vào ý nghĩa hơn là sổ sách.
Ở mức cao, GC tự động tìm các vùng nhớ không còn truy cập được từ chương trình chạy—những giá trị mà không gì có thể dùng nữa—và thu hồi không gian đó.
Thay vì hỏi “chúng ta đã free mọi đối tượng đúng một lần chưa?”, GC chuyển câu hỏi thành “đối tượng này còn truy cập được không?”. Nếu chương trình không thể truy cập nó, nó được coi là rác.
Tại sao đây thực sự là về năng suất
Với công việc AI ký hiệu, chương trình Lisp thường tạo rất nhiều danh sách, cây và kết quả trung gian ngắn hạn. Quản lý bộ nhớ thủ công sẽ biến việc thử nghiệm thành cuộc chiến liên tục với dọn dẹp tài nguyên.
GC thay đổi trải nghiệm hàng ngày:
- Lặp nhanh hơn: bạn có thể prototype thoải mái mà không cần chiến lược bộ nhớ trước.\n- Ít lỗi crash: các loại lỗi liên quan con trỏ giảm đi nhiều.\n- Mã sạch hơn: hàm có thể trả về cấu trúc mới mà không cần hợp đồng ẩn về ai giải phóng gì.
Ý tưởng chính là một tính năng ngôn ngữ có thể là bộ nhân đội: ít giờ sửa lỗi nghiêm trọng nghĩa là nhiều thời gian cải thiện logic thực sự.
Ảnh hưởng lâu dài
Lựa chọn của McCarthy không dừng ở Lisp. Nhiều hệ thống sau này áp dụng GC (và các biến thể của nó) vì đổi chác thường có lợi: Java, C#, Python, runtime JavaScript và Go đều dựa vào GC để làm phát triển quy mô lớn an toàn và nhanh hơn—ngay cả khi hiệu năng vẫn là ưu tiên.
Đánh giá và Macros: mở rộng ngôn ngữ từ bên trong
“Đánh giá” nghĩa là gì (không dùng biệt ngữ)
Trong Lisp, một biểu thức là một mảnh mã viết theo hình dạng nhất quán (thường là danh sách). Đánh giá đơn giản là quá trình quyết định biểu thức đó có nghĩa gì và sản sinh gì.
Ví dụ, khi bạn viết “cộng các số này” hoặc “gọi hàm này với các tham số này”, bộ đánh giá theo một tập quy tắc nhỏ để biến biểu thức đó thành một kết quả. Nghĩ về nó như trọng tài của ngôn ngữ: quyết định làm gì tiếp, theo thứ tự nào và khi nào dừng.
Tại sao một bộ đánh giá nhỏ, nhất quán là sức mạnh
Bước đi then chốt của McCarthy không chỉ là phát minh cú pháp mới—mà là giữ “động cơ nghĩa” gọn và đều. Khi bộ đánh giá xây từ vài quy tắc rõ ràng, có hai điều tốt xảy ra:
- Ngôn ngữ dễ lý giải hơn, vì bạn không phải xoay nhiều trường hợp đặc biệt.\n- Bạn có thể mở rộng ngôn ngữ mà không cần viết lại toàn bộ hệ thống, vì tính năng mới có thể biểu đạt theo các phần đã có.
Sự nhất quán đó là một lý do Lisp thành sân chơi cho các ý tưởng AI ký hiệu: các nhà nghiên cứu có thể thử các biểu diễn và cấu trúc điều khiển mới nhanh chóng, mà không phải đợi nhóm compiler thiết kế lại ngôn ngữ.
Macros: tự động hóa các mẫu mã
Macro là cách của Lisp để tự động hóa các hình dạng mã lặp lại, không chỉ là giá trị lặp. Nơi hàm giúp tránh lặp lại phép tính, macro giúp tránh lặp lại cấu trúc—các mẫu phổ biến như “làm X, nhưng cũng log nó”, hoặc “định nghĩa một ngôn ngữ nhỏ cho quy tắc”.
Hiệu ứng thực dụng là Lisp có thể tự xây các tiện nghi mới từ bên trong. Nhiều công cụ hiện đại phản chiếu ý tưởng này—hệ thống template, generator mã và metaprogramming—bởi vì chúng hỗ trợ mục tiêu chung: thử nghiệm nhanh hơn và ý định rõ ràng hơn.
Nếu bạn tò mò cách tư duy này ảnh hưởng đến quy trình phát triển hàng ngày, hãy xem /blog/the-repl-and-fast-feedback-loops.
REPL và vòng phản hồi nhanh
Một phần lớn sức hấp dẫn của Lisp không chỉ là ngôn ngữ—mà là cách bạn làm việc với nó. Lisp phổ biến REPL: Read–Eval–Print Loop. Nói đơn giản, nó giống một cuộc trò chuyện với máy tính. Bạn gõ một biểu thức, hệ thống chạy ngay, in kết quả và chờ nhập tiếp.
Quy trình làm việc bằng tiếng thường
Thay vì viết cả chương trình, biên dịch, chạy và rồi dò lỗi, bạn có thể thử ý tưởng từng bước nhỏ. Bạn định nghĩa một hàm, kiểm tra với vài giá trị, chỉnh sửa và kiểm tra lại—tất cả trong vài giây.
Nhịp điệu đó khuyến khích thử nghiệm, điều rất quan trọng cho công việc AI thời đầu khi bạn thường không biết ngay phương pháp đúng.
Tại sao vòng phản hồi chặt thay đổi kết quả
Phản hồi nhanh biến “cược lớn” thành “kiểm tra nhỏ”. Với nghiên cứu, nó giúp khám phá giả thuyết và kiểm tra kết quả trung gian. Với nguyên mẫu sản phẩm, nó giảm chi phí lặp: bạn có thể xác thực hành vi bằng dữ liệu thực nhanh, nhận ra trường hợp biên sớm và tinh chỉnh tính năng mà không chờ chu trình build dài.
Đây cũng là lý do các công cụ vibe-coding hiện đại hấp dẫn: chúng nén vòng phản hồi một cách quyết liệt. Ví dụ, Koder.ai dùng giao diện chat (với kiến trúc agent bên dưới) để biến ý định sản phẩm thành mã web, backend hoặc mobile nhanh chóng—thường làm cho vòng “thử → điều chỉnh → thử lại” gần giống REPL hơn là đường ống truyền thống.
Những tương đương hiện đại bạn đã biết
Ý tưởng REPL xuất hiện hôm nay trong:
- shell tương tác Python/Node\n- Jupyter và các notebook khác\n- console devtools trên trình duyệt\n- “Live reload” và hot module replacement trong web apps
Các công cụ khác nhau, cùng một nguyên tắc: rút ngắn khoảng cách giữa suy nghĩ và thấy được kết quả.
Khi nào phát triển tương tác có ích nhất
Đội ngũ hưởng lợi nhất từ workflow giống REPL khi họ khám phá yêu cầu chưa chắc chắn, xây tính năng nặng dữ liệu, thiết kế API hoặc gỡ lỗi logic phức tạp. Nếu công việc liên quan đến học nhanh—về người dùng, dữ liệu hoặc trường hợp biên—phản hồi tương tác không phải là thứ sang trọng; đó là bộ nhân lực.
Làm thế nào các ý tưởng Lisp lan tỏa khắp lập trình
Lisp không “thắng” bằng cách trở thành cú pháp hàng ngày của mọi người. Nó thắng bằng cách gieo rắc các ý tưởng rồi dần trở nên bình thường trong nhiều hệ sinh thái.
Mẫu hàm trong mã hàng ngày
Các khái niệm mà Lisp coi là mặc định—hàm như giá trị, thao tác bậc cao, ưu tiên ghép các phần nhỏ—xuất hiện rộng rãi ngày nay. Ngay cả ngôn ngữ chẳng giống Lisp vẫn khuyến khích map/filter-style, thói quen dữ liệu bất biến và tư duy gần giống đệ quy (thường qua iterator hay fold).
Thay đổi tinh thần là: xem biến đổi dữ liệu như pipeline, và xem hành vi như thứ bạn có thể truyền qua lại.
Biểu diễn ký hiệu: từ AI tới compiler và công cụ
Lisp làm cho chương trình dễ biểu diễn như dữ liệu. Tư duy đó xuất hiện trong cách ta xây và thao tác AST (abstract syntax trees) cho compiler, formatter, linter và code generator. Khi bạn làm việc với AST, bạn đang làm công việc anh em gần gũi với “mã như dữ liệu”, dù cấu trúc đó là JSON, node có kiểu hay đồ thị bytecode.
Cách tiếp cận ký hiệu tương tự thúc đẩy tự động hóa thực dụng: định dạng cấu hình, hệ thống template và pipeline build đều dựa trên biểu diễn có cấu trúc mà công cụ có thể kiểm tra, biến đổi và xác thực.
DSL và mở rộng mà không viết lại mọi thứ
Các ngôn ngữ họ Lisp hiện đại (theo nghĩa rộng: Lisp đương thời và công cụ chịu ảnh hưởng Lisp) tiếp tục ảnh hưởng cách các nhóm thiết kế DSL nội bộ—các ngôn ngữ nhỏ cho test, deploy, xử lý dữ liệu hoặc UI.
Ngoài Lisp, hệ thống macro, thư viện metaprogramming và framework sinh mã hướng tới cùng kết quả: mở rộng ngôn ngữ để phù hợp với vấn đề.
Một kết luận thực dụng: sở thích cú pháp thay đổi, nhưng các ý tưởng bền vững—cấu trúc ký hiệu, hàm có thể kết hợp và khả năng mở rộng—vẫn sinh lời qua nhiều thập kỷ và codebase.
Những đánh đổi và hiểu lầm về Lisp
Lisp có tiếng dao động giữa “tuyệt vời” và “khó đọc”, thường dựa trên ấn tượng gián tiếp hơn là trải nghiệm thực tế. Sự thật thì bình thường hơn: Lisp chọn một số hướng mạnh mẽ trong môi trường phù hợp và gây bất tiện ở nơi khác.
“Quá nhiều ngoặc” (vấn đề đọc mã)
Với người mới, cú pháp đồng nhất của Lisp giống như nhìn “bên trong” chương trình hơn là bề mặt đánh bóng. Sự khó chịu đó có thực, nhất là nếu bạn quen ngôn ngữ nơi cú pháp tách rời các cấu trúc.
Tuy nhiên, lịch sử cho thấy cấu trúc Lisp cũng chính là điểm mạnh: mã và dữ liệu cùng hình dạng giúp chương trình dễ biến đổi, sinh và phân tích. Với hỗ trợ editor tốt (thụt lề, điều hướng cấu trúc), mã Lisp thường được đọc theo hình dạng hơn là đếm ngoặc.
Hiệu năng: huyền thoại, lịch sử và thực tế hiện nay
Một định kiến phổ biến là Lisp vốn chậm. Vài triển khai lịch sử thua kém ngôn ngữ cấp thấp, và tính năng động có thể tạo overhead.
Nhưng không chính xác khi coi “Lisp” như một hồ sơ hiệu năng duy nhất. Nhiều hệ thống Lisp đã hỗ trợ biên dịch, khai báo kiểu và tối ưu hóa nghiêm túc từ lâu. Cách hữu ích hơn là hỏi: bạn cần bao nhiêu quyền kiểm soát về layout bộ nhớ, độ trễ dự đoán hay throughput thô—và triển khai Lisp bạn chọn có phục vụ điều đó không?
Ecosystem và tuyển dụng: ràng buộc thực dụng, không phải thất bại đạo đức
Một phê bình hợp lý khác là phù hợp hệ sinh thái. Tùy dialect Lisp và miền, thư viện, tooling và pool nhân lực có thể nhỏ hơn các stack chính. Điều đó ảnh hưởng hơn là sự tinh tế ngôn ngữ nếu bạn cần giao hàng nhanh với đội lớn.
Kết luận cân bằng
Thay vì đánh giá Lisp qua tiếng đồn, hãy tách các ý tưởng cơ bản: cấu trúc đồng nhất, phát triển tương tác và macro như công cụ xây DSL. Ngay cả nếu bạn không deploy Lisp, những khái niệm đó vẫn có thể mài sắc cách bạn thiết kế ngôn ngữ và viết mã ở bất kỳ ngôn ngữ nào.
Bài học thực tế cho thiết kế ngôn ngữ và lập trình hàng ngày
McCarthy không chỉ để lại một ngôn ngữ lịch sử—ông để lại một bộ thói quen giúp phần mềm dễ thay đổi, giải thích và mở rộng.
6 bài học đáng “mượn”
- Ưu tiên lõi đơn giản hơn là bề mặt khéo léo. Một tập khối xây dựng trực giao nhỏ dễ học và khó phá vỡ hơn.\n2) Giữ hình dạng dữ liệu đồng nhất. Khi nhiều thứ chia sẻ cùng biểu diễn (như danh sách/cây), công cụ đơn giản hơn: printer, debugger, serializer và transformer có thể tái sử dụng.\n3) Xem chương trình như dữ liệu (và dữ liệu như chương trình) khi hữu ích. Nếu bạn có thể kiểm tra và biến đổi cấu trúc, bạn có thể xây refactor an toàn, migration và code generator.\n4) Tự động hóa việc nhàm chán. GC là ví dụ kinh điển, nhưng ý rộng hơn là đầu tư vào tự động hóa ngăn ngừa cả lớp lỗi.\n5) Tối ưu cho vòng phản hồi. Đánh giá tương tác (kiểu REPL) khuyến khích thí nghiệm nhỏ, xác minh nhanh và trực giác tốt hơn về hành vi.\n6) Đặt mở rộng là mục tiêu thiết kế hàng đầu. Macro trong Lisp là một cách; trong hệ sinh thái khác có thể là plugin, template, DSL hoặc transform thời biên dịch.
Bài tập nhỏ (10 phút)
Trước khi chọn thư viện hay kiến trúc, lấy một tính năng thực tế—ví dụ “quy tắc giảm giá” hoặc “phân tuyến ticket hỗ trợ”—và vẽ nó dưới dạng cây. Sau đó viết lại cây đó thành danh sách lồng nhau (hoặc JSON). Hỏi: các nút là gì, các lá là gì, và bạn cần những phép biến đổi nào?
Mang ý tưởng Lisp vào bất kỳ stack nào
Ngay cả không dùng Lisp, bạn có thể nhận tư duy: xây biểu diễn giống AST, dùng sinh mã cho phần glue lặp lại và chuẩn hóa pipeline theo hướng dữ liệu (parse → transform → evaluate). Nhiều đội nhận lợi ích đơn giản bằng cách làm rõ các biểu diễn trung gian.
Nếu bạn thích nguyên tắc REPL nhưng vẫn triển khai tính năng trong các stack chính, bạn cũng có thể mượn tinh thần qua tooling: vòng lặp lặp nhanh, snapshot/rollback và lập kế hoạch rõ ràng trước khi thực thi. Koder.ai, ví dụ, có chế độ lập kế hoạch cộng snapshots và rollback để giữ lặp nhanh an toàn—một tiếng vang vận hành của nguyên tắc Lisp “thay đổi nhanh nhưng giữ kiểm soát”.
Di sản lâu dài của McCarthy là thế này: lập trình mạnh mẽ hơn khi chúng ta biến suy luận thành có thể lập trình—và giữ con đường từ ý tưởng tới hệ thống có thể chạy càng trực tiếp càng tốt.
Câu hỏi thường gặp
“Tư duy biểu tượng” có ý nghĩa thực tiễn là gì?
Tư duy biểu tượng thể hiện khái niệm và quan hệ trực tiếp (ví dụ: “khách hàng”, “là-một”, “phụ-thuộc”, “nếu…thì…”), rồi áp dụng các quy tắc và phép biến đổi lên những biểu diễn đó.
Nó hữu ích nhất khi vấn đề của bạn đầy cấu trúc, ngoại lệ và ý nghĩa (động cơ luật, lập kế hoạch, trình biên dịch, cấu hình, logic workflow), chứ không chỉ tính toán số học.
Đóng góp cốt lõi của John McCarthy ngoài việc đặt tên “AI” là gì?
McCarthy thúc đẩy quan điểm rằng suy luận có thể được biểu đạt dưới dạng chương trình—không chỉ là phép tính.
Quan điểm này đã định hình:
- cách chúng ta biểu diễn tri thức (ký hiệu có cấu trúc)
- cách chúng ta tìm giải pháp (tìm kiếm, lập kế hoạch, áp dụng quy tắc)
- những gì chúng ta mong đợi từ ngôn ngữ (mạnh về biểu đạt, dễ mở rộng, lặp nhanh)
Tại sao danh sách trong Lisp lại quan trọng cho việc biểu diễn tri thức?
Danh sách là một cách tối giản và linh hoạt để biểu diễn “vật được tạo từ các phần”. Vì phần tử của danh sách có thể chính là danh sách, bạn tự nhiên có được cấu trúc cây.
Điều đó giúp dễ dàng để:
- lấy các phần (ví dụ: một nút “chủ ngữ”)
- viết lại các nhánh con (phép biến đổi)
- mô hình hóa quy tắc và biểu thức một cách nhất quán
S-expressions là gì, và tại sao chúng quan trọng?
S-expressions mang đến cho bạn một hình dạng đồng nhất cho mã và dữ liệu: danh sách lồng nhau.
Sự đồng nhất này làm hệ thống đơn giản hơn vì:
- parsing đều đặn hơn
- phép biến đổi khá thẳng hướng (duyệt cây, viết lại nút)
- công cụ có thể tái sử dụng cùng một biểu diễn cho nhiều nhiệm vụ (định dạng, phân tích, sinh mã)
Sự khác nhau thực tế giữa macro và hàm là gì?
Macro tự động hóa các cấu trúc mã lặp lại, không chỉ các phép tính lặp lại.
Dùng macro khi bạn muốn:
- tạo một DSL nhỏ (ví dụ: cho quy tắc hoặc test)
- thực thi các mẫu (logging, đo thời gian, instrumentation)
- loại bỏ boilerplate trong khi giữ chỗ gọi rõ ràng
Nếu bạn chỉ cần logic có thể tái sử dụng, hàm thường là lựa chọn tốt hơn.
Tại sao thu gom rác (garbage collection) được coi là tính năng tăng năng suất trong lịch sử Lisp?
Garbage collection (GC) tự động thu hồi bộ nhớ không còn khả năng truy cập, giảm các loại lỗi (con trỏ treo, free 2 lần).
Nó đặc biệt hữu ích khi chương trình tạo nhiều cấu trúc ngắn hạn (danh sách/cây/AST), vì bạn có thể thử nghiệm và refactor mà không cần thiết kế hệ thống sở hữu bộ nhớ thủ công ngay từ đầu.
REPL thay đổi cách bạn phát triển phần mềm như thế nào?
REPL rút ngắn vòng lặp “suy nghĩ → thử → quan sát”. Bạn có thể định nghĩa một hàm, chạy nó, chỉnh sửa, rồi chạy lại ngay lập tức.
Để đạt được lợi ích tương tự trong các ngăn xếp không phải Lisp:
- dùng shell tương tác/notebook
- thêm trình chạy test nhanh và chế độ watch
- giữ ví dụ và fixture gần mã nguồn
Related reading: /blog/the-repl-and-fast-feedback-loops
Ý tưởng của Lisp đã lan tỏa vào lập trình chính thống như thế nào?
Nhiều quy trình hiện đại tái sử dụng cùng những ý tưởng nền tảng:
- pipeline hàm thuần túy (
map/filter, composition) - công cụ dựa trên AST (linters, formatters, codegen)
- DSL và khả năng mở rộng (macro, template, plugin)
- khám phá tương tác (notebooks, console devtools)
Ngay cả khi bạn không deploy Lisp, bạn rất có thể dùng các thói quen xuất phát từ Lisp hàng ngày.
Những đánh đổi và hiểu lầm thực tế về Lisp là gì?
Những đánh đổi thực tế gồm:
- Cú pháp lạ: nhiều ngoặc khiến người mới bỡ ngỡ, nhưng với hỗ trợ editor tốt (thụt lề, điều hướng cấu trúc), mã Lisp thường đọc theo hình dạng chứ không phải đếm ngoặc.\n- Ecosystem/tuyển dụng: một số dialect có ít thư viện và pool nhân lực nhỏ hơn các stack chính.\n- Hiệu năng biến thiên: “Lisp” không phải một hồ sơ hiệu năng cố định—một số triển khai biên dịch tốt và tối ưu hóa ổn, nhưng cần kiểm thử theo yêu cầu về độ trễ/throughput.
Cách thực tế là đánh giá các ý tưởng cơ bản (cấu trúc đồng nhất, phát triển tương tác, macro) hơn là nghe theo tiếng tăm.
Làm sao tôi áp dụng bài học McCarthy/Lisp vào codebase hiện đại mà không dùng Lisp?
Thử bài tập 10 phút sau:
- Chọn một tính năng thực tế (ví dụ: “quy tắc giảm giá”).
- Vẽ nó như một cây (nút = khái niệm/quy tắc; lá = literal/trường).\n3) Viết nó dưới dạng dữ liệu lồng nhau (JSON hoặc cấu trúc dạng danh sách).\n4) Liệt kê các phép biến đổi cần thiết (validate, rewrite, evaluate, explain).
Thao tác này thường lộ ra nơi nên dùng mẫu “mã như dữ liệu”, engine quy tắc hoặc cấu hình giống DSL để đơn giản hóa hệ thống.