Khi nào nên sử dụng macro hay không sử dụng [đã đóng]


12

Khi nào tôi nên sử dụng macro trong chương trình của mình hay không?

Câu hỏi này được lấy cảm hứng từ một câu trả lời thông tin của @tarsius. Tôi tin rằng nhiều người có kinh nghiệm tuyệt vời để chia sẻ những người khác. Tôi hy vọng câu hỏi này trở thành một trong những câu hỏi chủ quan tuyệt vời và giúp các lập trình viên Emacs.

Câu trả lời:


13

Nguyên tắc

Sử dụng macro cho cú pháp , nhưng không bao giờ cho ngữ nghĩa.

Sử dụng macro cho một số đường cú pháp là tốt, nhưng sẽ là một ý tưởng tồi khi đưa logic vào một cơ thể vĩ mô bất kể nó nằm trong chính macro hay trong bản mở rộng. Nếu bạn cảm thấy cần phải làm điều đó, thay vào đó hãy viết một hàm và "macro đồng hành" cho đường cú pháp. Nói một cách đơn giản, nếu bạn có một lettrong macro bạn đang tìm kiếm rắc rối.

Tại sao?

Macro có rất nhiều nhược điểm:

  • Chúng mở rộng tại thời gian biên dịch và do đó phải được viết cẩn thận để duy trì khả năng tương thích nhị phân trên nhiều phiên bản. Chẳng hạn, việc loại bỏ một hàm hoặc biến được sử dụng trong việc mở rộng macro sẽ phá vỡ mã byte của tất cả các gói phụ thuộc đang sử dụng macro này. Nói cách khác, nếu bạn thay đổi việc triển khai macro theo cách không tương thích, tất cả các gói phụ thuộc phải được biên dịch lại. Và khả năng tương thích nhị phân không phải lúc nào cũng rõ ràng
  • Bạn phải cẩn thận để duy trì một trật tự đánh giá trực quan . Thật dễ dàng để viết một macro đánh giá một biểu mẫu quá thường xuyên, hoặc không đúng lúc, hoặc ở sai vị trí, v.v.
  • Bạn phải xem ra ngữ nghĩa ràng buộc : Macros thừa hưởng bối cảnh ràng buộc của chúng từ trang mở rộng , điều đó có nghĩa là bạn không biết tiên nghiệm về các biến từ vựng tồn tại và liệu ràng buộc từ vựng có khả dụng hay không. Một macro phải làm việc với cả ngữ nghĩa ràng buộc.
  • Liên quan đến điều này, bạn phải coi chừng khi viết các macro tạo ra các bao đóng . Hãy nhớ rằng, bạn nhận được các liên kết từ trang mở rộng, không phải từ định nghĩa từ vựng, vì vậy hãy theo dõi bạn để biết những gì bạn đóng và cách bạn tạo bao đóng (hãy nhớ, bạn không biết liệu liên kết từ vựng có hoạt động ở trang mở rộng hay không bạn thậm chí có sẵn đóng cửa).

1
Các ghi chú về ràng buộc từ vựng và mở rộng vĩ mô là đặc biệt thú vị; cảm ơn lunaryorn (Tôi chưa bao giờ kích hoạt ràng buộc từ vựng cho bất kỳ mã nào tôi đã viết, vì vậy đó không phải là xung đột mà tôi từng nghĩ đến.)
phils 17/03/2016

6

Từ kinh nghiệm đọc, gỡ lỗi và sửa đổi mã Elisp của tôi và người khác, tôi đề nghị tránh macro càng nhiều càng tốt.

Điều rõ ràng về các macro là chúng không đánh giá các đối số của chúng. Điều đó có nghĩa là nếu bạn có một biểu thức phức tạp được bao bọc trong một macro, bạn không biết liệu nó có được đánh giá hay không, và trong bối cảnh nào, trừ khi bạn tra cứu định nghĩa macro. Điều này có thể không phải là một vấn đề lớn đối với bản thân bạn, vì bạn biết định nghĩa về vĩ mô của riêng bạn (hiện tại, có thể không phải là một vài năm sau đó).

Nhược điểm này không áp dụng cho các hàm: tất cả các đối số được đánh giá trước khi gọi hàm. Điều đó có nghĩa là bạn có thể xây dựng sự phức tạp của eval trong một tình huống gỡ lỗi không xác định. Bạn thậm chí có thể bỏ qua các định nghĩa về các hàm mà bạn chưa biết, miễn là chúng trả về dữ liệu đơn giản và bạn cho rằng chúng không chạm vào trạng thái toàn cầu.

Để điều chỉnh lại nó theo một cách khác, macro trở thành một phần của ngôn ngữ. Nếu bạn đang đọc mã với các macro không xác định, bạn gần như đang đọc mã bằng ngôn ngữ mà bạn không biết.

Các ngoại lệ cho các đặt phòng trên:

Các macro tiêu chuẩn thích push, pop, dolistlàm thực sự rất nhiều cho bạn, và được biết là lập trình viên khác. Về cơ bản chúng là một phần của thông số ngôn ngữ. Nhưng thực tế không thể chuẩn hóa vĩ mô của riêng bạn. Không chỉ khó để đưa ra một cái gì đó đơn giản và hữu ích như các ví dụ trên, mà còn khó hơn nữa là thuyết phục mọi lập trình viên học nó.

Ngoài ra, tôi không bận tâm về các macro như save-selected-window: họ lưu trữ và khôi phục trạng thái và đánh giá các đối số của họ theo cùng một cách progn. Khá đơn giản, thực sự làm cho mã đơn giản hơn.

Một ví dụ khác về các macro OK có thể là những thứ như defhydrahoặc use-package. Các macro này về cơ bản đi kèm với ngôn ngữ nhỏ của riêng họ. Và họ vẫn xử lý dữ liệu đơn giản hoặc hành động như thế progn. Ngoài ra, chúng thường ở mức cao nhất, do đó, không có biến nào trên ngăn xếp cuộc gọi để các đối số macro phụ thuộc vào, làm cho nó đơn giản hơn một chút.

Một ví dụ về một macro xấu, theo ý kiến ​​của tôi, là magit-section-actionmagit-section-casetrong Magit. Cái đầu tiên đã được gỡ bỏ, nhưng cái thứ hai vẫn còn.


4

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:

  1. 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.

  2. setfcá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).

  3. with-output-to-stringcũng thay đổi ngữ nghĩa của mã bên trong macro. Tương tự, with-current-buffervà một loạt các with-macro khác .

  4. dotimes và tương tự không thể được mô tả về các chức năng.

  5. ignore-errors thay đổi ngữ nghĩa của mã mà nó kết thúc.

  6. Có một loạt các macro được định nghĩa trong cl-libgói, eieiogó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:

  1. 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).

  2. Khi đây chỉ là một loại tiện lợi (tức là tạo một macro có tên mvbmở rộng ra cl-multiple-value-bindchỉ để làm cho nó ngắn hơn).

  3. 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:

  1. 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.

  2. 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)+1hoặc 0hoặ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 flà một hàm, trong khi akhông nhất thiết phải là hàm.


1
"Đường cú pháp" không thực sự là một thuật ngữ trong khoa học máy tính. Đó là thứ được các kỹ sư sử dụng để chỉ những thứ khác nhau. Vì vậy, tôi không có câu trả lời dứt khoát. Vì chúng ta đang nói về ngữ nghĩa, nên, quy tắc diễn giải cú pháp của biểu mẫu: (x y)đại khái là "đánh giá y, sau đó gọi x với kết quả đánh giá trước đó". Khi bạn viết (defun x y), y không được đánh giá và x cũng không. Cho dù bạn có thể viết một mã có hiệu lực tương đương bằng cách sử dụng các ngữ nghĩa khác nhau không từ chối đoạn mã này có ngữ nghĩa riêng.
wvxvw

1
Nhận xét thứ hai của bạn: Bạn có thể vui lòng giới thiệu cho tôi định nghĩa về macro bạn đang sử dụng để nói những gì bạn yêu cầu không? Tôi vừa cho bạn một ví dụ trong đó quy tắc ngữ nghĩa mới được giới thiệu bằng phương tiện macro. Ít nhất là trong ý nghĩa chính xác của CS. Tôi không nghĩ rằng tranh luận về các định nghĩa là hiệu quả và tôi cũng nghĩ rằng các định nghĩa được sử dụng trong CS là phù hợp nhất cho cuộc thảo luận này.
wvxvw 17/03/2016

1
Chà ... bạn tình cờ có ý tưởng của riêng bạn về từ "ngữ nghĩa" nghĩa là gì, thật là mỉa mai, nếu bạn nghĩ về nó :) Nhưng thực sự, những điều này có định nghĩa rất chính xác, bạn chỉ, tốt thôi .. bỏ qua những cái đó Cuốn sách tôi liên kết trong câu trả lời là một cuốn sách giáo khoa tiêu chuẩn về ngữ nghĩa như được dạy trong khóa học tương ứng CS. Nếu bạn chỉ đọc bài viết tương ứng của Wiki: en.wikipedia.org/wiki/Semantics_%28computer_science%29 bạn sẽ thấy những gì nó thực sự nói về. Ví dụ là quy tắc để diễn giải (defun x y)so với ứng dụng chức năng.
wvxvw 17/03/2016

Ồ, tôi không nghĩ rằng đây là cách để tôi có một cuộc thảo luận. Đó là một sai lầm khi thậm chí thử và bắt đầu một; Tôi đã xóa bình luận của tôi vì không có điểm nào trong tất cả điều này. Tôi xin lỗi vì đã lãng phí thời gian của bạn.
lunaryorn 17/03/2016
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.