Một ý kiến không đồng tình: tính đồng nhất của Lisp là một điều hữu ích hơn nhiều so với hầu hết những người hâm mộ Lisp sẽ tin bạn.
Để hiểu các cú pháp cú pháp, điều quan trọng là phải hiểu các trình biên dịch. Công việc của trình biên dịch là biến mã có thể đọc được thành mã thực thi. Từ góc độ rất cao, điều này có hai giai đoạn tổng thể: phân tích cú pháp và tạo mã .
Phân tích cú pháp là quá trình đọc mã, diễn giải nó theo một bộ quy tắc chính thức và biến nó thành cấu trúc cây, thường được gọi là AST (Cây cú pháp trừu tượng). Đối với tất cả sự đa dạng giữa các ngôn ngữ lập trình, đây là một điểm chung đáng chú ý: về cơ bản mọi ngôn ngữ lập trình có mục đích chung đều phân tích thành một cấu trúc cây.
Việc tạo mã lấy AST của trình phân tích cú pháp làm đầu vào của nó và biến nó thành mã thực thi thông qua việc áp dụng các quy tắc chính thức. Từ góc độ hiệu suất, đây là một nhiệm vụ đơn giản hơn nhiều; nhiều trình biên dịch ngôn ngữ cấp cao dành 75% hoặc nhiều thời gian hơn cho việc phân tích cú pháp.
Điều cần nhớ về Lisp là nó rất, rất cũ. Trong số các ngôn ngữ lập trình, chỉ có FORTRAN cũ hơn Lisp. Quay trở lại trong ngày, phân tích cú pháp (phần chậm của biên dịch) được coi là một nghệ thuật đen tối và bí ẩn. Các bài báo gốc của John McCarthy về lý thuyết Lisp (hồi đó chỉ là một ý tưởng mà ông không bao giờ nghĩ có thể thực sự được thực hiện như một ngôn ngữ lập trình máy tính thực sự) mô tả một cú pháp có phần phức tạp và biểu cảm hơn so với "biểu thức S hiện đại ở mọi nơi "Ký hiệu. Điều đó đã xảy ra sau đó, khi mọi người đang cố gắng thực hiện nó. Vì lúc đó việc phân tích cú pháp không được hiểu rõ, nên về cơ bản, họ đã bỏ qua nó và đưa cú pháp vào cấu trúc cây đồng âm để làm cho công việc của trình phân tích cú pháp hoàn toàn tầm thường. Kết quả cuối cùng là bạn (nhà phát triển) phải thực hiện nhiều trình phân tích cú pháp ' s làm việc cho nó bằng cách viết AST chính thức trực tiếp vào mã của bạn. Homoiconicity không "làm cho macro dễ dàng hơn nhiều" cũng như làm cho việc viết mọi thứ trở nên khó khăn hơn nhiều!
Vấn đề với điều này là, đặc biệt là với kiểu gõ động, rất khó để các biểu thức S có thể mang nhiều thông tin ngữ nghĩa xung quanh chúng. Khi tất cả cú pháp của bạn là cùng một loại (danh sách các danh sách), sẽ không có nhiều ngữ cảnh được cung cấp bởi cú pháp và do đó, hệ thống macro có rất ít để làm việc.
Lý thuyết trình biên dịch đã đi một chặng đường dài kể từ những năm 1960 khi Lisp được phát minh, và trong khi những điều nó đạt được rất ấn tượng cho thời đại của nó, thì bây giờ chúng trông khá nguyên thủy. Để biết ví dụ về một hệ thống siêu lập trình hiện đại, hãy xem ngôn ngữ Boo (đáng buồn là không được đánh giá đúng mức). Boo được gõ tĩnh, hướng đối tượng và nguồn mở, vì vậy mọi nút AST có một loại với cấu trúc được xác định rõ mà nhà phát triển macro có thể đọc mã. Ngôn ngữ này có một cú pháp tương đối đơn giản được lấy cảm hứng từ Python, với các từ khóa khác nhau mang ý nghĩa ngữ nghĩa nội tại cho các cấu trúc cây được xây dựng từ chúng và siêu lập trình của nó có một cú pháp quasiquote trực quan để đơn giản hóa việc tạo các nút AST mới.
Đây là một macro tôi đã tạo ngày hôm qua khi tôi nhận ra rằng tôi đang áp dụng cùng một mẫu cho một loạt các vị trí khác nhau trong mã GUI, nơi tôi sẽ gọi BeginUpdate()
điều khiển UI, thực hiện cập nhật trong một try
khối và sau đó gọi EndUpdate()
:
macro UIUpdate(value as Expression):
return [|
$value.BeginUpdate()
try:
$(UIUpdate.Body)
ensure:
$value.EndUpdate()
|]
Các macro
lệnh là, trên thực tế, một macro tự , một trong đó có một cơ thể vĩ mô như là đầu vào và tạo ra một lớp học để xử lý vĩ mô. Nó sử dụng tên của macro như một biến MacroStatement
đại diện cho nút AST đại diện cho lệnh gọi macro. [| ... |] là một khối quasiquote, tạo AST tương ứng với mã bên trong và bên trong khối quasiquote, ký hiệu $ cung cấp tiện ích "unquote", thay thế trong một nút như được chỉ định.
Với điều này, bạn có thể viết:
UIUpdate myComboBox:
LoadDataInto(myComboBox)
myComboBox.SelectedIndex = 0
và mở rộng ra:
myComboBox.BeginUpdate()
try:
LoadDataInto(myComboBox)
myComboBox.SelectedIndex = 0
ensure:
myComboBox.EndUpdate()
Thể hiện macro theo cách này đơn giản và trực quan hơn so với macro Lisp, bởi vì nhà phát triển biết cấu trúc MacroStatement
và biết cách thức Arguments
và Body
các đặc tính hoạt động, và kiến thức vốn có có thể được sử dụng để diễn đạt các khái niệm liên quan đến rất trực quan đường. Nó cũng an toàn hơn, bởi vì trình biên dịch biết cấu trúc của nó MacroStatement
và nếu bạn cố mã hóa thứ gì đó không hợp lệ cho a MacroStatement
, trình biên dịch sẽ nắm bắt nó ngay lập tức và báo cáo lỗi thay vì bạn không biết cho đến khi có điều gì đó làm bạn thất vọng thời gian chạy.
Ghép các macro vào Haskell, Python, Java, Scala, v.v. không khó vì các ngôn ngữ này không đồng âm; thật khó khăn vì các ngôn ngữ không được thiết kế cho chúng và nó hoạt động tốt nhất khi hệ thống phân cấp AST của ngôn ngữ của bạn được thiết kế từ đầu để được kiểm tra và thao tác bởi một hệ thống vĩ mô. Khi bạn đang làm việc với một ngôn ngữ được thiết kế với lập trình siêu dữ liệu ngay từ đầu, các macro sẽ đơn giản hơn và dễ làm việc hơn!