Tôi sẽ nói thẳng: Tôi không hiểu "sử dụng cho cú pháp, không phải cho ngữ nghĩa" nghĩa là gì. Đây là lý do tại sao một phần của câu trả lời này sẽ giải quyết câu trả lời của lunaryon và phần khác sẽ là nỗ lực của tôi trong việc trả lời câu hỏi ban đầu.
Khi từ "ngữ nghĩa" được sử dụng trong lập trình, nó đề cập đến cách tác giả của ngôn ngữ đã chọn để giải thích ngôn ngữ của họ. Điều này thường tập trung vào một tập hợp các công thức biến đổi các quy tắc ngữ pháp của ngôn ngữ thành một số quy tắc khác mà người đọc được cho là quen thuộc. Ví dụ, Plotkin trong cuốn sách Phương pháp tiếp cận cấu trúc ngữ nghĩa hoạt động của mình sử dụng ngôn ngữ phái sinh logic để giải thích cú pháp trừu tượng nào đánh giá (ý nghĩa của việc chạy chương trình). Nói cách khác, nếu cú pháp là thứ cấu thành ngôn ngữ lập trình ở một mức độ nào đó, thì nó phải có một số ngữ nghĩa, nếu không, nó ... không phải là ngôn ngữ lập trình.
Nhưng, đến lúc này hãy quên đi những định nghĩa chính thức, sau tất cả, điều quan trọng là phải hiểu ý định. Vì vậy, có vẻ như lunaryon sẽ khuyến khích độc giả của mình sử dụng macro khi không có ý nghĩa mới nào được thêm vào chương trình, mà chỉ là một loại viết tắt. Đối với tôi điều này nghe có vẻ lạ và trái ngược hoàn toàn với cách sử dụng macro. Dưới đây là những ví dụ rõ ràng tạo ra ý nghĩa mới:
defun
và bạn bè, một phần thiết yếu của ngôn ngữ không thể được mô tả theo cùng thuật ngữ bạn sẽ mô tả các lệnh gọi hàm.
setf
các quy tắc mô tả cách thức hoạt động của các chức năng là không đủ để mô tả các tác động của một biểu thức như thế nào (setf (aref x y) z)
.
with-output-to-string
cũng thay đổi ngữ nghĩa của mã bên trong macro. Tương tự, with-current-buffer
và một loạt các with-
macro khác .
dotimes
và tương tự không thể được mô tả về các chức năng.
ignore-errors
thay đổi ngữ nghĩa của mã mà nó kết thúc.
Có một loạt các macro được định nghĩa trong cl-lib
gói, eieio
gói và một số thư viện gần như phổ biến khác được sử dụng trong Emacs Lisp, trong khi có thể trông giống như các dạng hàm có cách hiểu khác nhau.
Vì vậy, nếu có bất cứ điều gì, macro là công cụ để giới thiệu ngữ nghĩa mới vào một ngôn ngữ. Tôi không thể nghĩ ra một cách khác để làm điều đó (ít nhất là không phải trong Emacs Lisp).
Khi tôi không sử dụng macro:
Khi họ không giới thiệu bất kỳ cấu trúc mới nào, với ngữ nghĩa mới (nghĩa là chức năng sẽ thực hiện công việc tốt như vậy).
Khi đây chỉ là một loại tiện lợi (tức là tạo một macro có tên mvb
mở rộng ra cl-multiple-value-bind
chỉ để làm cho nó ngắn hơn).
Khi tôi hy vọng rất nhiều mã xử lý lỗi sẽ bị ẩn bởi macro (như đã lưu ý, macro rất khó gỡ lỗi).
Khi tôi rất thích macro hơn các chức năng:
Khi các khái niệm đặc thù miền bị che khuất bởi các lệnh gọi hàm. Ví dụ, nếu một người cần truyền cùng một context
đối số cho một loạt các hàm cần được gọi liên tiếp, tôi muốn bọc chúng trong một macro, trong đó context
đối số được ẩn.
Khi mã chung ở dạng hàm quá dài dòng (các cấu trúc lặp trần trong Emacs Lisp quá dài theo sở thích của tôi và không cần thiết phải đưa lập trình viên đến các chi tiết triển khai cấp thấp).
Hình minh họa
Dưới đây là phiên bản xuống nước nhằm cung cấp cho người ta một trực giác về ý nghĩa của ngữ nghĩa trong khoa học máy tính.
Giả sử bạn có một ngôn ngữ lập trình cực kỳ đơn giản chỉ với các quy tắc cú pháp sau:
variable := 1 | 0
operation := +
expression := variable | (expression operation expression)
với ngôn ngữ này, chúng tôi có thể xây dựng các "chương trình" như (1+0)+1
hoặc 0
hoặc ((0+0))
vv
Nhưng miễn là chúng tôi không cung cấp các quy tắc ngữ nghĩa, các "chương trình" này là vô nghĩa.
Giả sử bây giờ chúng ta trang bị ngôn ngữ của mình với các quy tắc (ngữ nghĩa):
0+0 0+1 1+0 1+1 (exp)
---, ---, ---, ---, -----
0 1+0 1 0 exp
Bây giờ chúng ta thực sự có thể tính toán bằng ngôn ngữ này. Nghĩa là, chúng ta có thể lấy biểu diễn cú pháp, hiểu nó một cách trừu tượng (nói cách khác, chuyển đổi nó thành cú pháp trừu tượng), sau đó sử dụng các quy tắc ngữ nghĩa để thao tác biểu diễn này.
Khi chúng ta nói về họ ngôn ngữ Lisp, các quy tắc đánh giá chức năng ngữ nghĩa cơ bản đại khái là các quy tắc của lambda-compus:
(f x) (lambda x y) x
-----, ------------, ---
fx ^x.y x
Các cơ chế trích dẫn và mở rộng vĩ mô còn được gọi là các công cụ lập trình meta, tức là chúng cho phép người ta nói về chương trình, thay vì chỉ lập trình. Họ đạt được điều này bằng cách tạo ra ngữ nghĩa mới bằng cách sử dụng "lớp meta". Ví dụ về một macro đơn giản như vậy là:
(a . b)
-------
(cons a b)
Đây không thực sự là một macro trong Emacs Lisp, nhưng nó có thể đã được. Tôi chỉ chọn vì đơn giản. Lưu ý rằng không có quy tắc ngữ nghĩa nào được xác định ở trên sẽ áp dụng cho trường hợp này vì ứng viên gần nhất (f x)
diễn giải f
là một hàm, trong khi a
không nhất thiết phải là hàm.