Vì đang là năm 2023 nên dĩ nhiên phải học Common Lisp
(log.schemescape.com)- Trong mạch suy nghĩ về một ngôn ngữ vẫn có thể dùng được trong tương lai, lần lựa chọn này gần với việc trực tiếp thử nghiệm một quy trình phát triển hiện đại dù ngôn ngữ đã cũ hơn là tính thực dụng
- Janet là một ngôn ngữ script nhỏ thuộc họ Lisp/Clojure, nhắm đến phạm vi tương tự Lua, và đã thu hút sự chú ý nhờ thư viện chuẩn cùng metaprogramming dựa trên macro
- Tính di động, khả năng nhúng và Parsing Expression Grammars của Janet trông có vẻ hấp dẫn, nhưng hiện tại chỉ khớp một phần hạn chế với nhu cầu; các lựa chọn sẵn có như CHICKEN Scheme, CLISP, SBCL và Packrat cũng đã đủ thuyết phục
- Phát triển theo REPL của Common Lisp có điểm cốt lõi là ở trạng thái lỗi lúc chạy, có thể xem stack, biến và dữ liệu sống, chỉnh sửa chúng rồi tiếp tục phần thực thi đã bị gián đoạn
- Dù phải trả giá để học một ngôn ngữ mới và thư viện chuẩn mới, tác giả cho rằng vẫn đáng để tự mình trải nghiệm môi trường mà quy trình này đã được tính đến ngay từ khi thiết kế
Vì sao sự quan tâm chuyển từ Janet sang Common Lisp
- Tác giả vốn muốn viết mã có thể dùng lâu dài nên thường suy nghĩ về future-proof programming languages, nhưng khi tạo ra thứ gì đó để chia sẻ thì lại chọn cách tiếp cận thực dụng: ưu tiên ngôn ngữ phổ biến, có tính di động và tiện dụng
- Mặt khác, tác giả cũng tìm hiểu các ngôn ngữ và công cụ khác để vui và thử nghiệm
- Gần đây, sau khi đọc cuốn sách miễn phí Janet for Mortals, tác giả bắt đầu quan tâm đến Janet
- Janet là một ngôn ngữ script tương đối nhỏ, lấy cảm hứng từ Lisp/Clojure và nhắm đến một ngách tương tự Lua
- Nó cung cấp thư viện chuẩn thực thụ, metaprogramming kiểu Lisp và khả năng thực thi ở thời điểm biên dịch thông qua macro
- Ngoài chất Lisp nói chung, những điểm hấp dẫn nhất ở Janet là tính di động, khả năng nhúng và Parsing Expression Grammars
- Hiện tại tác giả không cần nhúng ngôn ngữ, nên khả năng nhúng không phải là lý do quyết định
- Về tính di động, CHICKEN Scheme, CLISP, và Steel Bank Common Lisp cũng có vẻ ổn
- Với bài toán phân tích cú pháp, Packrat có vẻ là một lựa chọn hợp lý
- Kết quả là số lý do để chọn Janet thay vì một Lisp đã có từ trước ngày càng ít đi
Quy trình phát triển theo REPL đã khiến tác giả học Common Lisp
- Một bài viết nói về quy trình phát triển theo REPL gần như là độc nhất của Common Lisp đã trở thành động lực học tập
- Tình huống ví dụ là khi hàm
foogọi hàmbarchưa được định nghĩa, và lỗi xảy ra trong lúc chạyfoo- Trong các môi trường Lisp cũ hoặc Smalltalk, việc
foobị gián đoạn sẽ dẫn vào breakloop - breakloop là một REPL đầy đủ nằm trong môi trường động của hàm bị gián đoạn
- Có thể di chuyển qua lại trong call stack đã bị tạm dừng và kiểm tra các biến nhìn thấy ở từng stack frame
- Có thể kiểm tra và sửa dữ liệu sống của chương trình đang chạy
- Nếu nguyên nhân là do một biến hoặc giá trị trường bị sai, có thể sửa giá trị đó rồi tiếp tục hàm đã bị gián đoạn
- Cũng có thể định nghĩa hàm
barcòn thiếu ngay trong REPL rồi tiếp tụcfoođể nhận được kết quả hợp lý
- Trong các môi trường Lisp cũ hoặc Smalltalk, việc
- Tác giả từng thấy nhiều tính năng kiểu “edit and continue”, nhưng việc Common Lisp được tạo ra với kiểu quy trình này ngay từ đầu là điều đem lại cảm giác mới mẻ
- Trải nghiệm gỡ lỗi trước đây của tác giả đã chuyển từ
printf debuggingsang dùng debugger thực thụprintf debugginglà cách tạm chèn mã log, biên dịch lại rồi xem kết quả chạy- Trong những tình huống không thể sửa chương trình hoặc chạy lại, tác giả buộc phải học debugger
- Debugger thực sự là công cụ đúng đắn, nhưng việc thiết lập môi trường gỡ lỗi có thể rất đau đầu hoặc đôi khi là bất khả thi
- Common Lisp có vẻ như là môi trường đẩy việc gỡ lỗi tiến thêm một bước
- Tác giả từng có trải nghiệm sửa bộ nhớ trong debugger để thử các bản sửa tiềm năng
- Nhưng điểm đặc biệt hấp dẫn là có thể viết lại và vá mã ngay bên trong tiến trình đang chạy
- Mục tiêu khi học Common Lisp là tự mình kiểm chứng xem cách xây dựng chương trình một cách tương tác này có phải là một lối làm việc thú vị hay không
- Tác giả không chắc việc học một ngôn ngữ mới và thư viện chuẩn mới có phải là lựa chọn tốt nhất chỉ để khám phá một workflow mới hay không, nhưng cũng chưa thấy lựa chọn thay thế nào rõ ràng hơn
- Những ngôn ngữ khác cũng có thể có tích hợp REPL-trình soạn thảo tương tự
- Tuy vậy, tác giả cho rằng các tính năng như thế có thể nhiều lỗi hơn vì là thứ được thêm vào sau, chứ không được hỗ trợ ngay từ đầu
- Ngay cả khi cuối cùng không hài lòng, ít nhất tác giả vẫn đã thử nó trong điều kiện tốt nhất với bộ công cụ tiêu chuẩn
- Những tài liệu giúp học Common Lisp đã được tổng hợp trong bài viết riêng Common Lisp resources
1 bình luận
Ý kiến trên Hacker News
Những điểm tôi thích ở Common Lisp cho đến nay là hệ thống điều kiện rất gọn gàng, và nhờ restart, có thể dễ dàng điều khiển cả những đoạn mã nằm ở xa. Phát triển theo hướng REPL cũng rất tiện trong những tình huống chưa biết rõ chuyện gì sẽ xảy ra, chẳng hạn khi phân tích một nguồn dữ liệu lạ, vì có thể sửa mã và tiếp tục mà không mất ngữ cảnh
Có nhiều implementation và khả năng tương tác cũng tốt, nên trong một trường hợp tôi đã có thể thay đổi qua lại giữa SBCL để lấy tốc độ và CLISP để giảm mức dùng bộ nhớ. Một trong những lý do tôi nghiêng về CL hơn Scheme khi học Lisp cũng là vì có nhiều implementation tương thích
Dù tôi còn mới với Emacs, phần tích hợp Common Lisp vẫn rất tuyệt, và chạy tốt ngay cả trên chiếc netbook rất chậm dùng để phát triển. Trong thời đại có máy tính nhanh, VS Code và language server như hiện nay thì đây không còn là lợi thế quá lớn, nhưng rõ ràng nó có cảm giác retro-future
Cũng có vài điểm tiếc nuối. Trình quản lý gói phổ biến nhất QuickLisp thì ổn, nhưng chưa đạt đến mức tính năng mà ta đã quen trong các hệ sinh thái ngôn ngữ hiện đại. Bản thân ngôn ngữ như bị cố định trong thời gian, nên rất nhiều thứ như thread, đồng bộ hóa, đối số dòng lệnh đều cần thư viện tương tác chung. Và tôi thật sự mong SBCL hỗ trợ build tĩnh hoàn toàn để có thể phân phối binary cho cả các bản phân phối Linux không dùng glibc
Nếu một tác vụ quan trọng như tải mã nguồn mà không hỗ trợ mã hóa hay xác thực, tôi nghĩ QL khó có thể là lựa chọn cho những người quan tâm đến bảo mật
Việc SBCL được host trên SourceForge cũng là vấn đề. SourceForge từng chèn mã độc vào kho tải xuống trong quá khứ, nên tôi cũng xem đây là vấn đề bảo mật. Ngày nay dường như không có lý do thuyết phục nào để tiếp tục dùng SourceForge, nên tôi không rõ vì sao một dự án quan trọng vẫn còn ở đó
Tôi thật sự thích Lisp, đặc biệt là Common Lisp, nhưng những điều này khiến tôi rời xa, và dường như cũng không có nhiều sự quan tâm đến việc sửa chúng
https://www.timmons.dev/posts/static-executables-with-sbcl.h...
https://www.timmons.dev/posts/static-executables-with-sbcl-v...
https://cons.io
Gerbil/Gambit Scheme có thể tạo binary tĩnh hoàn toàn như một lựa chọn thay thế cho CL
Có thể dùng như thư viện Quicklisp thông thường, như core image khởi động tức thì, hoặc như binary. Script cũng có thể chạy gần như ngay lập tức, nên có vài điểm giống Babashka. Vẫn đang trau chuốt chi tiết và chưa phải v1.0
Phần trong blog về “xử lý lỗi runtime bằng cách sửa mã bị hỏng ngay tại chỗ mà không cần khởi động lại” lẽ ra nên được minh họa ở đây: https://www.youtube.com/watch?v=jBBS4FeY7XM
Khi chạy một phép tính dài và nặng rồi gặp lỗi ở bước cuối, thay vì chạy lại từ đầu, bạn vào debugger tương tác, chuyển đến dòng lỗi, biên dịch hàm đã sửa, rồi quay lại debugger và tiếp tục thực thi từ một điểm trong stack frame. Sau đó có thể thấy chương trình chạy qua
M-để sao chép nó vào REPL theo tôi tốt hơn nhiều so vớitap>mà Clojure cung cấp. Ngay cả khi dùng công cụ như Portal, tôi vẫn thấytap>gần giống một dạng pretty-print được tô điểm hơnVề hiệu năng, Common Lisp cũng cho phép chỉ định kiểu tĩnh, và SBCL có thể tạo ra mã rất hiệu quả khi làm như vậy. Chạy
DISASSEMBLEtrên mã tự viết để xem thực sự thứ gì được phát ra rồi tối ưu hóa là việc hữu ích. Đặc biệt các gói như SB-SIMD và Loopus thật sự giúp ích rất nhiều cho ứng dụng tính toán số:quacklà true thì hãy xử lý nó như vịt”Nhưng trong suốt một năm qua, tôi thật sự nhớ cách phát triển của Common Lisp, và trên đời này là hệ thống điều kiện. Tôi thậm chí không phải hacker CL thành thạo đến vậy, nhưng nó đã nhanh chóng trở thành thói quen. Ước gì CLOS và các kiểu dữ liệu nguyên thủy của CL hòa hợp với nhau tốt hơn vẻ ngoài của chúng
Trong Cider thì chắc chắn có thể làm dễ dàng, và tôi nghĩ các công cụ chính trên những editor khác cũng làm được tương tự ở mức đủ tốt
DISASSEMBLECó các dự án hỗ trợ như https://github.com/Bronsa/tools.decompiler, và cũng có JitWatch của OpenJDK (https://github.com/AdoptOpenJDK/jitwatch). Các JVM khác cũng có những công cụ tương tự
Không trực quan bằng Lisp, nhưng vẫn làm được
Tạo ra được một thứ bí hiểm nào đó chạy tốt và hấp dẫn thì cũng hay, nhưng giờ sẽ chẳng ai muốn chạm vào nó ngoài những người có thể mô tả lương của mình bằng số lượng chữ số 0
https://www.digitalocean.com/community/tutorials/java-repl-j...
https://github.com/waf/CSharpRepl
https://pub.dev/packages/interactive
Chưa kể Ruby, Python, PHP, Lua; ngay cả C++ cũng có REPL dù còn thô sơ: https://github.com/root-project/cling
Mỗi lần tôi nói rằng REPL trong thế giới Lisp không còn là yếu tố khác biệt nữa thì lại bị downvote
https://github.com/kaveh808/kons-9
Tuy nhiên việc định nghĩa lại và ghi đè như vậy rốt cuộc sẽ làm ô nhiễm namespace, và đến một lúc nào đó sẽ có thứ gì đó hỏng khiến phải reload server
Nó cũng có nhiều nhược điểm, nhưng không tệ hơn các nhược điểm thuần kỹ thuật của hầu hết mọi ngôn ngữ và môi trường phát triển khác. Python và JS được dùng rộng rãi hơn và có nhiều thư viện hơn, nhưng tôi cảm thấy trải nghiệm phát triển tệ hơn nhiều. Tôi đã dùng C#, TS, Py, Hs và cả những ngôn ngữ bí hiểm hơn trong nhiều năm, nhưng mỗi khi bị mắc kẹt trong các ngôn ngữ đó hoặc nổi giận nghiêm trọng vì những thứ rõ ràng là tệ, tôi lại quay về CL(SBCL + Emacs + Slime). Khi đó tôi thấy nhẹ nhõm và có niềm tin rằng trên đời vẫn còn những điều tốt đẹp
Hiện chúng tôi đang gọi vốn cho một sản phẩm mà chúng tôi đã ngu ngốc bootstrap bằng TypeScript, nhưng phiên bản phát hành sẽ được làm lại bằng CL. Điều đó có nghĩa là trong 3–5 năm tới trước khi ra mắt, chúng tôi sẽ làm việc bằng CL, bên trong CL, và tôi rất vui vì sẽ được làm những việc thú vị như triển khai DSL, sinh code, làm macro, triển khai bộ phân tích kiểu tĩnh
Khi làm việc bằng C#, bạn nhớ điều gì ở Lisp?
Suy nghĩ cực đoan của tôi là lý do lập trình hàm không phổ biến rộng rãi là vì nó rõ ràng ổn cho việc viết chương trình, nhưng phần lớn phần mềm ngày nay không phải là “chương trình chạy từ dòng lệnh trên PC/server của tôi cho đến khi kết thúc”, mà là “chương trình khởi động rồi phản hồi input của người dùng cho đến khi người dùng đóng lại”, hoặc “chương trình khởi động rồi phản hồi mạng hay các I/O tự động khác cho đến khi phần mềm khác bảo nó dừng”
Những việc như vậy khó hơn nhiều nếu làm theo phong cách hàm thuần túy, hoặc ít nhất là như vậy trong các triển khai hàm có quan điểm mạnh mà tôi từng dùng. Vì khi đó không còn chỉ đơn giản là đánh giá một biểu thức nữa, mà là khởi tạo trạng thái, phản ứng với I/O, cập nhật trạng thái hoặc thực hiện thêm I/O, thậm chí dùng song song hóa để giám sát, nhận dữ liệu, thực hiện thêm I/O và cập nhật trạng thái
Tất nhiên không phải là không thể làm những việc này bằng Lisp, nhưng từ vài học kỳ tiếp xúc với lập trình hàm thời đại học và kinh nghiệm dùng các tính năng hàm một cách chuyên nghiệp trong C++/Scala để giải các vấn đề như vậy, tôi thấy việc làm cho lập trình hàm vận hành tốt trong các ứng dụng kiểu này khá khó, và sự không phù hợp đó khiến tôi ngần ngại đi sâu hơn vào lập trình hàm
Ngoài Common Lisp, tôi cũng thường xuyên gặp việc phải viết mã tạo ra mã. Ở lĩnh vực này Lisp có vẻ sẽ tỏa sáng, nhưng tôi chưa có cơ hội thử. Ví dụ các dự án gần đây tôi làm trước khi nghĩ đến Common Lisp gồm tạo mã kiểm tra một JSON Schema cụ thể trong trình tạo site tĩnh, và tạo JSX từ Markdown để dùng cho nội dung cốt truyện của một game lập trình
Chưa kể vô số macro C tôi đã dùng suốt thời gian qua
Cũng không cần vứt bỏ hết các tính năng hay của những phong cách khác. Một phần của lập trình hàm đang ngày càng phổ biến hơn trong các ngôn ngữ thông thường. Ví dụ Rust cũng dùng các mẫu hàm trong nhiều trường hợp
Và Lisp cũng có thể được dùng theo phong cách hướng đối tượng hoặc mệnh lệnh nếu muốn. Nó không phải Haskell
Tôi xem Lisp là điểm khởi đầu, còn Haskell mới là thứ thật sự. Khuyên nên học phong cách đó, dù không viết mã production thực tế
(let ((pair (cons 1 nil)))(setf (cdr pair) pair)(list (first pair) (second pair) (third pair)));; => (1 1 1)Nếu muốn “biên dịch mãi mãi” thì viết bằng ANSI C
Nếu muốn “tồn tại mãi mãi” thì viết bằng Python 2.7 và biến nó thành xương sống của các template hạ tầng cho toàn tổ chức. Có module Ansible tùy chỉnh thì được cộng điểm
Điều tôi mong là Lisp giúp biểu đạt suy nghĩ thành mã dễ hơn bằng cách giảm thiểu hoặc trừu tượng hóa những thứ lặt vặt và boilerplate. Với tôi, biểu thức Lisp trông như cách tự nhiên nhất để diễn đạt suy nghĩ
Tôi chủ yếu quan tâm đến viết ứng dụng GUI, nên muốn học Clog hoặc tìm hay phát triển một wrapper tốt trên một GUI toolkit nào đó
(do-it (do-it first))(do-it first)hoạt động tốt, nhưng nếu lời gọi(do-it (do-it first))thất bại thì sao?Sẽ rất hay nếu có thể giành quyền điều khiển tại điểm hỏng và sửa định nghĩa
do-it. Nhưng nếu sửa thì kết quả của(do-it first)cũng thay đổiKhi đó vị trí hiện tại của máy trở thành một điểm không còn có thể đi tới bằng mã hiện tại nữa
Tôi thật sự không hiểu cơ chế này hoạt động ra sao trong trường hợp một sửa đổi giúp chương trình tiếp tục lại làm thay đổi chính trạng thái mà ta đã nhận quyền điều khiển để sửa