Bình luận của Hofstadter về Lisp (1983)
(gist.github.com/jackrusher)- Bài viết năm 1983 của Douglas Hofstadter giới thiệu Lisp, ngôn ngữ được dùng rộng rãi trong nghiên cứu AI, như một ngôn ngữ thanh nhã và linh hoạt được xây dựng trên một hạt nhân toán học nhỏ
- Tính tương tác của Lisp thể hiện ở read-eval-print loop: khi người dùng nhập một biểu thức, trình thông dịch đọc, đánh giá, in kết quả rồi chờ đầu vào tiếp theo
- Đối tượng Lisp được tổ chức quanh nguyên tử (atom) và danh sách (list); nil có vị trí ngoại lệ vì vừa là danh sách rỗng vừa là nguyên tử
quote,eval,car,cdr,cons,setq,set,lambda,def,condcho phép xử lý mã và dữ liệu bằng cùng một cấu trúc danh sách, nhờ đó có thể dần dần tạo ra các hàm mới- Hợp thành hàm, biểu thức điều kiện, side effect và phong cách applicative, cùng tư duy đệ quy được kết nối qua các ví dụ; hàm
powerở cuối và ẩn dụ “porpuquine” gợi ý kiểu đệ quy của Lisp
AI và vị trí của Lisp trong nghiên cứu
- Vào thời điểm năm 1983, AI là lĩnh vực nghiên cứu nhằm hiện thực hóa trên máy tính các hành vi như tính linh hoạt, lẽ thường, sự thấu hiểu, tính sáng tạo, tự nhận thức và hài hước
- Ở Mỹ có khoảng 2.000 người chuyên tham gia vào AI, và ở nước ngoài cũng có số nhà nghiên cứu với quy mô tương tự
- Các nhà nghiên cứu AI có nhiều bất đồng lớn về con đường tốt nhất để đi tới AI, nhưng trong lựa chọn ngôn ngữ lập trình thì hầu như cùng dùng Lisp
- Tên Lisp bắt nguồn từ “list processing”, nhưng không phải là một từ viết tắt hoàn chỉnh
- Sức hấp dẫn của Lisp nằm ở chỗ nó là một ngôn ngữ “crisp” và “elegant”
- Trong khi nhiều ngôn ngữ chứa rất nhiều tính năng tùy ý, một số ngôn ngữ như Lisp và Algol được tổ chức quanh một hạt nhân tự nhiên như một nhánh của toán học
- Hạt nhân của Lisp có “crystalline purity”; sự thuần khiết này đóng góp không chỉ cho cảm quan thẩm mỹ mà còn cho tính linh hoạt
Từ logic toán học đến ngôn ngữ xử lý danh sách
- Gốc rễ sâu xa của Lisp nằm trong logic toán học
- Những ý tưởng mà Thoralf Skolem, Kurt Godel, Alonzo Church để lại trong logic học vào thập niên 1920–1930 về sau được đưa vào Lisp
- Lập trình máy tính thực sự bắt đầu trong thập niên 1940, còn các ngôn ngữ bậc cao như Lisp xuất hiện trong thập niên 1950
- Ngôn ngữ xử lý danh sách đầu tiên không phải Lisp mà là IPL (Information Processing Language)
- IPL được Herbert Simon, Allen Newell, J. C. Shaw phát triển vào giữa thập niên 1950
- John McCarthy đã tạo ra Lisp, một ngôn ngữ xử lý danh sách mang tính đại số, trong giai đoạn 1956–1958 dựa trên các ý tưởng sẵn có
- Lisp nhanh chóng lan rộng trong các nhà nghiên cứu trẻ quanh MIT Artificial Intelligence Project
- Nó được triển khai trên IBM 704 và lan sang các nhóm AI khác
- Nhiều dialect xuất hiện, nhưng cùng chia sẻ một hạt nhân trung tâm thanh nhã
Lisp tương tác và REPL
- Khác với nhiều ngôn ngữ bậc cao khác, Lisp được trình bày như một ngôn ngữ tương tác
- Người dùng chạy Lisp trên terminal, thấy prompt rồi nhập biểu thức
- Ví dụ: nhập
(plus 2 2)thì4được in ra và prompt mới xuất hiện
- Ví dụ: nhập
- Hoạt động cốt lõi của trình thông dịch Lisp là read-eval-print loop
- Đọc biểu thức
- Đánh giá biểu thức
- In giá trị thích hợp
- Báo rằng nó đã sẵn sàng đọc biểu thức tiếp theo
- Nhờ cấu trúc này, người dùng có thể nhập từng “điều ước” và kiểm tra kết quả ngay lập tức
- Trong nhiều ngôn ngữ không tương tác, phải viết trước toàn bộ chương trình rồi mới chạy, nên nhiều bước phải khớp hoàn hảo với nhau, việc sửa lỗi cũng gián tiếp và tốn kém
- Lisp cho phép phát triển và gỡ lỗi tăng dần bằng cách chạy từng biểu thức một
Polish notation và dấu ngoặc
- Biểu thức số học của Lisp dùng Polish notation, trong đó toán tử đứng trước toán hạng
- Ví dụ:
(times (plus 6 3) (difference 6 3))được đánh giá thành 27
- Ví dụ:
- Cách ký hiệu này do nhà logic học Ba Lan Jan Lukasiewicz tạo ra trước thời máy tính
- Biểu thức Lisp có rất nhiều dấu ngoặc
- Các biểu thức dài thường kết thúc bằng nhiều dấu ngoặc đóng
- Ban đầu có thể gây áp lực, nhưng khi quen rồi thì cấu trúc logic trở nên trực quan
- Các ví dụ được trình bày dưới dạng pretty printing, dùng thụt lề để bộc lộ cấu trúc
Nguyên tử, danh sách, nil
- Cốt lõi của Lisp là các cấu trúc có thể thao tác; mọi chương trình hoạt động bằng cách tạo, sửa đổi và phá hủy cấu trúc
- Có hai loại cấu trúc
- Nguyên tử (atom): đối tượng cơ bản không thể chia nhỏ hơn
- Danh sách (list): cấu trúc gồm nhiều phần tử theo một thứ tự nhất định
- Mọi đối tượng Lisp đều là nguyên tử hoặc danh sách, với đúng một ngoại lệ là nil
- nil vừa là nguyên tử vừa là danh sách
- nil biểu thị danh sách rỗng
()vànilcó cùng nghĩa, nhưng nil được dùng thường xuyên hơn
- Phần tử của danh sách có thể là nguyên tử hoặc cũng có thể là danh sách
- Ví dụ:
(zonk blee strill (croak flonk))là danh sách có bốn phần tử, trong đó phần tử cuối lại là danh sách hai phần tử
- Ví dụ:
- Bản thân câu lệnh Lisp cũng là danh sách
(plus 2 2)cũng là một danh sách- Trình thông dịch Lisp có thể thao tác danh sách và nguyên tử để tạo rồi đánh giá các “lệnh” mới
Giá trị, quote, eval
- Nguyên tử có thể có giá trị
- Nguyên tử số có giá trị vĩnh viễn là chính nó
- Giá trị của nil là nil
- t cũng là một nguyên tử đặc biệt có giá trị vĩnh viễn là chính nó
setqgán giá trị cho nguyên tử(setq pie 4)làm cho giá trị củapiethành 4(setq pie (plus 2 2))cũng làm cho giá trị củapiethành 4
- Giá trị của nguyên tử không chỉ có thể là số mà có thể là bất kỳ đối tượng Lisp nào
- Có thể là nguyên tử, cũng có thể là danh sách
- quote chặn việc đánh giá và khiến danh sách hoặc nguyên tử được xử lý nguyên trạng như dữ liệu
(setq pie (plus 2 2))đánh giá biểu thức bên trong rồi lưu 4 vàopie(setq pi '(plus 2 2))lưu chính danh sách(plus 2 2)vàopi
evalđánh giá lại biểu thức được lưu dưới dạng giá trị- Nếu giá trị của
pilà(plus 2 2), thì(eval pi)trả về 4 - Trình thông dịch thường chỉ đánh giá một bước, nhưng dùng
evalcó thể yêu cầu đánh giá bổ sung
- Nếu giá trị của
car, cdr, cons
- Mọi danh sách không phải nil đều có ít nhất một phần tử, và phần tử đầu được gọi là car
- car của
(eval pi)làeval - car của
(plus 2 2)làplus
- car của
- car không nhất thiết phải là tên hàm
- car của
((1)(2 2) (3 3 3))là(1)
- car của
- Danh sách còn lại sau khi bỏ car là cdr
- cdr của
(a b c d)là(b c d) - Tiếp tục giảm thành
(c d),(d), rồi nil
- cdr của
- nil không có car cũng không có cdr; nếu cố lấy car hoặc cdr của nil thì phải xảy ra lỗi
constạo danh sách mới bằng cách gắn một car mới vào trước danh sách hiện có- Khi
xlà(cake cookie),(cons 'pie x)tạo ra(pie cake cookie) - Lúc này
xhiện có không bị thay đổi
- Khi
- Kết quả thay đổi tùy theo có quote hay không
- Nếu giá trị của
pielà 4,(cons pie x)trả về(4 cake cookie) (cons 'pie x)gắn chính tên nguyên tửpievào đầu
- Nếu giá trị của
Khác biệt giữa setq và set
- Trình thông dịch Lisp không phải lúc nào cũng đánh giá đối số, và
setqlà ngoại lệ tiêu biểu - Đối số thứ nhất của
setqkhông được đánh giá mà được xử lý nguyên trạng như tên biến- Chữ q trong
setqnghĩa là quote, và đối số thứ nhất được xử lý như thể đã được quote
- Chữ q trong
settương tựsetqnhưng cũng đánh giá đối số thứ nhất- Khi giá trị của x là k,
(set x 7)không đổi x mà đổi giá trị của k thành 7
- Khi giá trị của x là k,
- Ví dụ đưa ra thứ tự sau
(setq a 'b)(setq b 'c)(setq c 'a)(set a c)(set c b)
- Sau khi thực thi, giá trị của a, b, c lần lượt đều trở thành
a setkhông được dùng thường xuyên, và kiểu nhầm lẫn này cũng không hay xảy ra
Định nghĩa hàm và lambda
- Sức mạnh lớn của lập trình nằm ở khả năng định nghĩa các phép toán phức hợp mới từ những phép toán sẵn có, rồi lặp lại việc đó để tích lũy một repertoire phép toán phức tạp hơn
- Trong Lisp, ta định nghĩa hàm mới bằng các hàm đã biết
raclà ví dụ về hàm trả về phần tử cuối của danh sách- Muốn lấy phần tử cuối, có thể đảo ngược danh sách rồi lấy car
- Định nghĩa:
(def rac (lambda (lyst) (car (reverse lyst))))
(lyst)saulambdabiểu thị tham số của hàm, tức biến giả- Sau khi định nghĩa bằng
def,ractrở thành một hàm có thể dùng nhưcar(rac '(your brains))trả vềbrains
- Hàm
readers-digest-condensed-versiontạo một danh sách ngắn chỉ chứa phần tử đầu và phần tử cuối từ một danh sách dài- Có ví dụ: nếu coi toàn bộ Finnegans Wake của James Joyce là một danh sách từ, ta thu được
(riverrun the)
- Có ví dụ: nếu coi toàn bộ Finnegans Wake của James Joyce là một danh sách từ, ta thu được
- Phép ngược tên
rejoycelà một hàm tưởng tượng tạo ra một tiểu thuyết có phần đầu và phần cuối được xác định bằng hai từ; việc viết hàm này được để lại làm bài tập cho độc giả
Giá trị trả về, side effect, phong cách applicative
- Một số người cho rằng với Lisp và các ngôn ngữ liên quan, hai mục tiêu sau là đáng mong muốn và khả thi
- Mọi câu lệnh đều trả về một giá trị
- Tác dụng của câu lệnh chỉ xảy ra thông qua giá trị trả về
- Các dialect Lisp được đề cập trong bài đáp ứng điều kiện thứ nhất, nhưng không nhất thiết đáp ứng cả điều kiện thứ hai
(reverse x)không thay đổi bản thân x, mà tạo và trả về một danh sách mới giống x nhưng có thứ tự đảo ngược- Cùng cách như
(plus 2 2)không làm thay đổi giá trị của 2
- Cùng cách như
conscũng không thay đổi danh sách hiện có mà trả về một danh sách mới- Ngược lại,
setqlà ví dụ về lệnh để lại side effect bằng cách thay đổi binding của biến- Side effect ngoài thay đổi binding biến còn có thể bao gồm cả nhập/xuất
- Những người ủng hộ lập trình applicative chỉ ưa cách các hàm tính toán và truyền giá trị mà không có side effect
- Trong phong cách này, binding được phép là lambda binding, chỉ tạm thời phát sinh trong lúc gọi hàm
- Khi việc tính toán của hàm kết thúc, binding của biến giả biến mất
- Người kể trong bài xem phong cách applicative là thanh nhã, nhưng cho rằng nó không thực tế khi xây dựng các chương trình lớn theo phong cách AI
- Trong lập trình applicative nghiêm ngặt, ngay cả
defcũng trở thành một trường hợp cực đoan không được phép, vì nó lưu định nghĩa hàm vĩnh viễn vào bộ nhớ
Biểu thức điều kiện cond và phán đoán
- Để Lisp làm được những việc thú vị hơn, nó phải có khả năng ra quyết định tùy theo điều xảy ra trong quá trình trung gian, và lúc này cần biểu thức điều kiện
- Ví dụ:
(cond ((eq x 1) 'land) ((eq x 2) 'sea))- Nếu x là 1 thì
land - Nếu x là 2 thì
sea - Các trường hợp khác trả về nil
- Nếu x là 1 thì
eqlà hàm Lisp trả vềtnếu giá trị của hai đối số bằng nhau, và trả vềnilnếu khác nhau- Câu lệnh
condbắt đầu bằng tên hàmcondvà có nhiều mệnh đề- Mỗi mệnh đề là một danh sách 2 phần tử gồm điều kiện và kết quả
- Nó kiểm tra các điều kiện theo thứ tự; khi tìm thấy điều kiện đầu tiên trả về giá trị không phải nil, nó đánh giá kết quả tương ứng và trả về làm giá trị của toàn bộ
cond - Các mệnh đề sau đó không được kiểm tra
- Nếu đặt một mệnh đề catch-all có điều kiện là
tở cuối, có thể trả về kết quả mặc định thay vì nil- Ví dụ có một
condtrả vềairnếu cả điều kiện land và sea đều thất bại
- Ví dụ có một
Lũy thừa và cấu trúc đệ quy
- Cuối bài xuất hiện các định nghĩa hàm có mẫu rất rõ
squarelàk * kcubelàk * square(k)4th-powerlàk * cube(k)- Cứ tiếp tục theo cùng cách như vậy
- Câu hỏi đặt ra là liệu có thể định nghĩa một hàm hai tham số
powerbao quát toàn bộ mẫu này trong một lần hay không(power 9 3)là 729(power 7 4)phải trả về 2.401
- Các công cụ cần thiết đều đã được trình bày trong phần nội dung, và điều còn lại đòi hỏi sự nhanh trí của độc giả
- Câu chuyện về Glazunkian porpuquine ở cuối xử lý cấu trúc đệ quy như một ngụ ngôn
- Gai của porpuquine là các porpuquine nhỏ hơn
- Ở Outer Glazunkia luôn có 9 chiếc gai, ở Inner Glazunkia luôn có 7 chiếc gai
- Porpuquine 0 inch không có gai, qua đó chặn sự thoái lui vô hạn
- “buying power” hay “power” của con vật này liên quan đến số mũi của các porpuquine 0 inch nhỏ bên trong nó, và bài kết thúc bằng ẩn dụ nối tiếp với bài toán
powerđã nêu trước đó
Bối cảnh tái đăng nguyên văn
- Phần đầu Gist cho biết đây là bài giới thiệu Lisp của Douglas Hofstadter mà người đăng gặp trong một số cũ của Scientific American vào giữa thập niên 1980
- Các ví dụ vẫn có thể chạy trong Emacs nếu cài một số alias
plus→+quotient→/times→*difference→-
- Phần cuối cho biết nếu thích phần hướng dẫn Lisp độc đáo của Hofstadter, có thể đọc thêm các bài tương tự trong sách Metamagical Themas của ông
1 bình luận
Bình luận trên Hacker News
Tôi đã bị rối bởi tên hàm
"oval"và"snot"trong ví dụ, rồi vài giây sau mới nhận ra đó lần lượt là lỗi đánh máy của"eval"và"snoc"Mã gốc lẽ ra phải được đọc là
(cond ((eq (eval pi) pie) (eval (snoc pie pi))) (t (eval (snoc (rac pi) pi))))Tôi đã tải bài Scientific American gốc từ https://www.jstor.org/stable/24968822 để kiểm tra, và quả thật
oval/snotlà lỗi đánh máy củaeval/snoc"oval"và"snot"đúng là lỗi đánh máy của"eval"và"snoc"và
snoccó vẻ làconsviết ngược, cònraclàcarviết ngượcBài này một lần nữa cho thấy nguyên nhân cốt lõi khiến Lisp không được phổ biến là cách người ta giải thích về Lisp
Những bài viết kiểu này về Lisp khi tôi còn nhỏ thực sự không dạy cho tôi bất cứ thứ gì có thể dùng được, cũng không cho thấy việc X nào đó sẽ trở nên dễ hơn bao nhiêu so với assembly/C/Pascal
Nếu là tôi bây giờ, tôi sẽ cho thấy kiểu như: “Một trình kiểm tra chính tả mất 7 tháng để viết bằng assembly? Với Lisp trên một máy vi tính dùng bộ nhớ chuyển trang ngân hàng thì đó gần như là việc vặt, và thậm chí còn có thể làm cho garbage collection hoạt động được trên một CPU tệ hại”
Quá nhiều bài viết và giáo trình về Lisp chỉ lặp đi lặp lại chuyện danh sách, đệ quy và AI, mà không chỉ ra cách làm những việc hữu ích, khiến tôi cảm thấy đã lãng phí thời gian như thể đang lập trình bằng nhíp, hạt gạo và hồ dán
Trong số đầu tiên của tạp chí Byte mà tôi đọc khi còn nhỏ có mã viết phép vi phân ký hiệu và rút gọn đại số bằng Lisp; dù khó theo kịp, tôi vẫn cảm nhận được có gì đó rất cuốn hút
Chắc chắn nó sẽ không dễ hơn nếu viết bằng Basic, và về sau nhìn lại thì đoạn mã đó cũng không hẳn xuất sắc, nhưng phải đến khi gặp XLisp trên PC cuối thập niên 80 cùng với SICP tôi mới thực sự bị Lisp mê hoặc
Tôi thực sự rất thích văn phong của Hofstadter. Ông ấy nắm bắt rất tốt cảm giác khi khám phá ra Lisp
Tôi là một đứa trẻ học lập trình trong thập niên 80, và đến đầu thời trung học phổ thông rồi đại học thì đã biết sơ qua BASIC, Fortran, Pascal, COBOL; chúng có khác nhau, nhưng về cơ bản vẫn có điểm chung
Thế rồi môn khoa học máy tính đầu tiên ở UC Berkeley lại dùng Scheme, một phương ngữ của Lisp, và đó là một cú sốc hoàn toàn
Như Hofstadter nói, nó cho cảm giác gần với toán học nhất, gợi nhớ rất nhiều đến các lớp lý thuyết toán, và là ngôn ngữ đầu tiên tôi thấy thực sự đẹp
Tôi đặc biệt thích câu trích dẫn: “Lisp và Algol được tạo ra quanh một hạt nhân tự nhiên như thể thuộc về một nhánh của toán học. Ở trung tâm của Lisp có một sự thuần khiết kết tinh, không chỉ chạm đến cảm quan thẩm mỹ mà còn khiến nó linh hoạt hơn rất nhiều so với hầu hết các ngôn ngữ khác”
Ngay cả từ góc nhìn của một người từng muốn trở thành nhà tô pô đại số, Scheme gần hơn với việc biểu đạt một cách thanh nhã và tối giản phong cách lập trình đã lan rộng trong các ngôn ngữ dùng kiểu động và garbage collection
Nó cho cảm giác như một “lý thuyết”, theo nghĩa là nó có vẻ hoàn chỉnh, và nếu nghĩ ra cách giải quyết vấn đề trong Scheme rồi chuyển sang ngôn ngữ kiểu động khác thì vẫn có thể đi đến một lời giải thanh nhã
So với Lisp truyền thống thời đó, Scheme là một ngôn ngữ đã được đơn giản hóa và sắp xếp lại ở các mặt như lexical scope, không gian tên thống nhất cho hàm và biến
Năm 1983 là thời điểm khoảng 20 năm sau khi lĩnh vực này bắt đầu, vậy mà người ta đã nói về AI có thể lập trình máy tính để có tính linh hoạt, lẽ thường, trực giác, sáng tạo, tự nhận thức, hài hước
Khá thú vị khi danh sách này nghe như đúng là danh sách những thứ mà LLM thực sự làm không tốt
Dù vậy, ít nhất cũng đã có một chút tiến bộ khác 0 theo hướng đó
Có lẽ còn hơn thế, nhưng nói ra vậy chính tôi cũng thấy hơi kỳ
Dù phần lớn vẫn chưa dẫn đến kết quả cụ thể, đây vẫn là những thứ rất đáng đọc
Hai bài đồng hành được nhắc đến cùng bài này nằm trong các chương 17~19 của cuốn Metamagical Themas của Hofstadter, cùng với các bài khác từ chính loạt đăng trên Scientific American
[0]: https://www.goodreads.com/book/show/181239.Metamagical_Thema...
Tên sách cũng là tiêu đề của loạt bài, được tạo thành từ một phép đảo chữ của “Mathematical Games”, chuyên mục mà Martin Gardner từng viết trên Scientific American, sau đó Hofstadter tiếp quản
Bài viết nói rằng áp dụng
carhoặccdrlênnilthì đáng lẽ phải gây lỗi như chia cho 0, nhưng trong Lisp hiện đại nhiều khi không còn như vậy nữaTrong Lisp nguyên bản do John McCarthy định nghĩa,
CARvàCDRkhông được định nghĩa choNIL: <https://dl.acm.org/doi/pdf/10.1145/367177.367199>Nhưng Common Lisp và Emacs Lisp định nghĩa
(car nil)và(cdr nil)lànil: <https://www.lispworks.com/documentation/HyperSpec/Body/f_car...>, <https://www.gnu.org/software/emacs/manual/html_node/elisp/Li...>Trong
LISP 2156,(status lispversion)trả về/2156, còn(car nil)và(cdr nil)đều trả vềNILcarvàcdrlại nằm ở vị trí cốt lõiPhép toán cơ bản và nổi tiếng nhất của “List Processor” thực ra không hoạt động trên danh sách, mà được tạo ra để tác động lên cons, tức một phần tử biểu diễn máy cụ thể mà Lisp dùng để tạo cấu trúc dữ liệu
cons cũng không phải lúc nào cũng được diễn giải là danh sách, và danh sách rất quan trọng trong trường hợp cơ sở của hàm đệ quy trên danh sách thì lại không được biểu diễn bằng cons
Sau 60 năm, phần lớn chương trình Lisp vẫn đầy các phép toán cons, nên có lẽ tên chính xác hơn phải là “Cons Processor”
Điều này gợi nhớ rằng Lisp ra đời trong thời kỳ ngôn ngữ và triển khai phải gắn chặt với nhau, nên thành tựu xây dựng ngôn ngữ máy tính trên nền tảng logic toán học lại càng thấy ấn tượng hơn
CARhoặcCDRcủanilsẽ gây lỗicar/cdrcủanillà lỗi, nên với người mới như tôi thì mã nguồn có cảm giác khá thiếu công thái họcDù vậy, tôi vẫn thích Guile Scheme hơn Common Lisp nên cũng thấy hơi tiếc
nilthì không báo lỗi mà lại trả vềnilhay khôngNó làm tôi nghĩ đến SQL, và cá nhân tôi xem đây là một lựa chọn không tốt, nên nếu chưa có tên nào khác thì tôi muốn gọi nó là “bleeding nils/NULLs”
Đặc biệt còn tệ hơn nếu
niltrong so sánh boolean lại không tương đương vớifalseTrong Ruby và Elixir,
nilđược xử lý nhưfalse, còn Elixir tách riêngandchỉ nhận boolean thuần với&&coinillàfalseKiểu thiết kế này lúc đầu có thể làm mã trông gọn gàng hơn, nhưng một
nilbị xử lý sai vốn là lỗi thật sự có thể chỉ lộ ra ở một phần hoàn toàn khác sau nhiều tầng call stack, khiến việc debug khó hơn rất nhiềuTôi vốn đã biết những thông tin về Lisp trong bài này, nhưng đọc vẫn thấy rất thú vị. Hofstadter có lối hành văn thật cuốn hút
Đặc biệt thú vị là trò đùa ông tạo ra
rejoyce, phép toán ngược củareaders-digest-condensed-version, sao cho khi chạy(rejoyce 'Stately 'Yes)thì một nàng tiên Lisp sẽ tạo ra toàn bộ Ulysses như thể do James Joyce viết ngay từ đầuMất khá nhiều thời gian, nhưng rốt cuộc chúng ta cũng đã chạm đến điểm đó; và dù AI của năm 2024 không hoàn toàn giống điều ông tưởng tượng vào năm 1983, việc tái tạo văn bản từ một seed ngắn quả thật là công việc mà AI ngày nay làm khá hợp
Lisp vẫn được xem là một trong những ngôn ngữ chủ chốt có thể chứa đựng khái niệm vòng lặp kỳ lạ mà Hofstadter bàn đến
Lisp không phải là ngôn ngữ duy nhất có tính homoiconic, nhưng trong số các ngôn ngữ mà người ta thực sự biết cách dùng, nó thuộc nhóm lớn nhất mà
evalkhông nhận chuỗi cần phải được phân tích cú phápTôi không thích việc mọi người đồng nhất Lisp với toàn bộ lập trình hàm
Không phải tôi ghét lập trình hàm, nhưng tính biểu tượng của Lisp thú vị hơn nhiều, và việc có thể tạo ra mã được vận hành bằng các đoạn
(go tag)khiến cả lập trình kiểu GOTO cũng trở nên cực kỳ dễ dàng, điều đó hấp dẫn mãi không thôiKhả năng metaprogramming kiểu dùng XSLT và XML để sinh ra XSLT thay vì tự viết mã lặp đi lặp lại đã từng được dùng khá rộng rãi
Tuy nhiên, cú pháp của nó còn là vấn đề lớn hơn cả cú pháp Lisp
Không dễ để tạo ra một ngôn ngữ có cú pháp thuận tiện cho việc dùng hằng ngày mà đồng thời cũng không quá khó để xử lý như một cây cú pháp trừu tượng, và Lisp là một trong số rất ít ví dụ thành công tương đối
functoolskhó làm được — là gì: https://docs.python.org/3/library/functools.htmlTôi đang xây dựng AGI biểu tượng mà không dùng Lisp, nên muốn nghe những gợi ý từ các chuyên gia
Theo hiểu biết của tôi, phía Python có các hàm áp dụng hàm khác lên iterable như
filter(),map(),reduce(), các wrapper định tuyến lời gọi như@singledispatch, các hàm hỗ trợ điều khiển luồng hoặc tiện ích hiệu năng như@cachevàpartial(), cùng các tính năng bọc hàm tùy ý nhưwraps()Phần lớn những thứ này trông giống như các tiện ích để gọi hàm theo những cách khác thường, và dường như chưa đạt đến mức được ca ngợi trong Lisp, nơi “tự phản chiếu” được xem như mối quan tâm hạng nhất
Tôi muốn biết chính xác Lisp thực sự cung cấp điều gì mà các tính năng trên không có
Họ dùng Lisp như một phương tiện để truyền đạt khái niệm, và các khái niệm đó thường xoay quanh đệ quy hàm
Scheme và văn hóa xung quanh nó cũng góp phần vào điều này
Dù Scheme không chỉ là ngôn ngữ hàm, nó nhấn mạnh lập trình thuần túy hơn các họ Lisp trước đó, ngôn ngữ lõi cung cấp cấu trúc đệ quy đuôi thay cho cấu trúc lặp, và yêu cầu các implementation tối ưu hóa lời gọi đuôi
Ngay cả trong Common Lisp, cũng có thể định nghĩa
defaliasbằng macro để dùng giống nhưdefaliascủa Emacs: https://stackoverflow.com/questions/24252539/defining-aliase...Tôi rất thích bài viết về Lisp của Hofstadter đăng trên Metamagical Themas, và đã chuyển đổi, sắp xếp lại phần mã trong bài cuối của loạt đó cho phù hợp với Clojure để dùng cho nhóm học tập ở chỗ làm
[1] http://johnj.com/posts/oodles/