Điều gì về LISP, nếu có bất cứ điều gì, giúp thực hiện các hệ thống vĩ mô dễ dàng hơn?


21

Tôi đang học Scheme từ SICP và tôi có ấn tượng rằng một phần lớn của những gì tạo ra Scheme và, hơn nữa, LISP đặc biệt là hệ thống vĩ mô. Nhưng, vì các macro được mở rộng tại thời gian biên dịch, tại sao mọi người không tạo ra các hệ thống macro tương đương cho C / Python / Java / bất cứ điều gì? Ví dụ, người ta có thể liên kết pythonlệnh với expand-macros | pythonhoặc bất cứ điều gì. Mã vẫn có thể di động cho những người không sử dụng hệ thống macro, người ta sẽ chỉ mở rộng các macro trước khi xuất bản mã. Nhưng tôi không biết bất cứ điều gì như thế ngoại trừ các mẫu trong C ++ / Haskell, mà tôi thu thập không thực sự giống nhau. Điều gì về LISP, nếu có bất cứ điều gì, giúp thực hiện các hệ thống vĩ mô dễ dàng hơn?


3
"Mã vẫn có thể di động được cho những người không sử dụng hệ thống macro, người ta sẽ chỉ mở rộng các macro trước khi xuất bản mã." - chỉ để cảnh báo bạn, điều này có xu hướng không hoạt động tốt. Những người khác sẽ có thể chạy mã, nhưng trong thực tế, mã mở rộng vĩ mô thường khó hiểu và thường khó sửa đổi. Nó có hiệu lực "được viết xấu" theo nghĩa là tác giả đã không chỉnh sửa mã mở rộng cho mắt người, họ đã điều chỉnh nguồn thực. Hãy thử nói với một lập trình viên Java, bạn chạy mã Java của mình thông qua bộ tiền xử lý C và xem chúng chuyển sang màu gì ;-)
Steve Jessop

1
Các macro cần phải thực thi, tại thời điểm đó, bạn đã viết một trình thông dịch cho ngôn ngữ.
Mehrdad

Câu trả lời:


29

Nhiều Lispers sẽ cho bạn biết rằng điều làm cho Lisp trở nên đặc biệt là tính đồng nhất , có nghĩa là cú pháp của mã được biểu diễn bằng cách sử dụng các cấu trúc dữ liệu giống như các dữ liệu khác. Ví dụ: đây là một hàm đơn giản (sử dụng cú pháp Scheme) để tính toán cạnh huyền của một tam giác vuông góc với độ dài cạnh đã cho:

(define (hypot x y)
  (sqrt (+ (square x) (square y))))

Bây giờ, homoiconicity nói rằng mã ở trên thực sự có thể biểu diễn dưới dạng cấu trúc dữ liệu (cụ thể là danh sách các danh sách) trong mã Lisp. Do đó, hãy xem xét các danh sách sau đây và xem cách chúng "kết dính" với nhau:

  1. (define #2# #3#)
  2. (hypot x y)
  3. (sqrt #4#)
  4. (+ #5# #6#)
  5. (square x)
  6. (square y)

Macro cho phép bạn coi mã nguồn là như vậy: danh sách các công cụ. Mỗi của những 6 "danh sách con" chứa hoặc con trỏ vào các danh sách khác, hay các biểu tượng (trong ví dụ này: define, hypot, x, y, sqrt, +, square).


Vậy, làm thế nào chúng ta có thể sử dụng cú pháp đồng âm để "chọn ra" cú pháp và tạo macro? Đây là một ví dụ đơn giản. Chúng ta hãy thực hiện lại letmacro, mà chúng ta sẽ gọi my-let. Như một lời nhắc nhở,

(my-let ((foo 1)
         (bar 2))
  (+ foo bar))

nên mở rộng thành

((lambda (foo bar)
   (+ foo bar))
 1 2)

Dưới đây là một thực hiện sử dụng Đề án "đổi tên rõ ràng" macro :

(define-syntax my-let
  (er-macro-transformer
    (lambda (form rename compare)
      (define bindings (cadr form))
      (define body (cddr form))
      `((,(rename 'lambda) ,(map car bindings)
          ,@body)
        ,@(map cadr bindings)))))

Các formtham số được gắn với hình thức thực tế, vì vậy ví dụ của chúng tôi, nó sẽ là (my-let ((foo 1) (bar 2)) (+ foo bar)). Vì vậy, hãy làm việc qua ví dụ:

  1. Đầu tiên, chúng tôi lấy các ràng buộc từ mẫu. cadrlấy một ((foo 1) (bar 2))phần của mẫu đơn
  2. Sau đó, chúng tôi lấy lại cơ thể từ mẫu. cddrlấy một ((+ foo bar))phần của mẫu đơn (Lưu ý rằng điều này được dự định để lấy tất cả các biểu mẫu con sau khi liên kết; vì vậy nếu biểu mẫu là

    (my-let ((foo 1)
             (bar 2))
      (debug foo)
      (debug bar)
      (+ foo bar))
    

    sau đó cơ thể sẽ là ((debug foo) (debug bar) (+ foo bar)).)

  3. Bây giờ, chúng tôi thực sự xây dựng lambdabiểu thức kết quả và gọi bằng cách sử dụng các ràng buộc và cơ thể chúng tôi đã thu thập. Backtick được gọi là "quasiquote", có nghĩa là coi mọi thứ bên trong quasiquote là các mốc dữ liệu theo nghĩa đen, ngoại trừ các bit sau dấu phẩy ("unquote").
    • Các (rename 'lambda)phương tiện để sử dụng lambdaràng buộc có hiệu lực khi macro này được xác định , thay vì bất kỳ lambdaràng buộc nào có thể xảy ra khi macro này được sử dụng . (Điều này được gọi là vệ sinh .)
    • (map car bindings)return (foo bar): mốc đầu tiên trong mỗi ràng buộc.
    • (map cadr bindings)trả về (1 2): mốc thời gian thứ hai trong mỗi ràng buộc.
    • ,@ không "nối", được sử dụng cho các biểu thức trả về một danh sách: nó làm cho các thành phần của danh sách được dán vào kết quả, thay vì chính danh sách đó.
  4. Kết hợp tất cả những thứ đó lại với nhau, kết quả là danh sách (($lambda (foo bar) (+ foo bar)) 1 2), $lambdaở đây đề cập đến việc đổi tên lambda.

Nói thẳng ra phải không? ;-) (Nếu điều đó không đơn giản với bạn, hãy tưởng tượng việc triển khai một hệ thống macro cho các ngôn ngữ khác sẽ khó khăn như thế nào.)


Vì vậy, bạn có thể có các hệ thống macro cho các ngôn ngữ khác, nếu bạn có cách để có thể "tách" mã nguồn theo cách không hề khó hiểu. Có một số nỗ lực ở đây. Ví dụ: sweet.js thực hiện điều này cho JavaScript.

Đối với các Schemers dày dạn đọc điều này, tôi cố tình chọn sử dụng các macro đổi tên rõ ràng như một sự thỏa hiệp giữa defmacrocác s được sử dụng bởi các phương ngữ Lisp khác và syntax-rules(đó sẽ là cách tiêu chuẩn để thực hiện macro như vậy trong Lược đồ). Tôi không muốn viết bằng các phương ngữ Lisp khác, nhưng tôi không muốn xa lánh những người không phải là Schemers, những người không quen sử dụng syntax-rules.

Để tham khảo, đây là my-letmacro sử dụng syntax-rules:

(define-syntax my-let
  (syntax-rules ()
    ((my-let ((id val) ...)
       body ...)
     ((lambda (id ...)
        body ...)
      val ...))))

syntax-casePhiên bản tương ứng trông rất giống nhau:

(define-syntax my-let
  (lambda (stx)
    (syntax-case stx ()
      ((_ ((id val) ...)
         body ...)
       #'((lambda (id ...)
            body ...)
          val ...)))))

Sự khác biệt giữa hai loại này là mọi thứ trong syntax-rulesđều #'được áp dụng ngầm định , do đó bạn chỉ có thể có các cặp mẫu / mẫu syntax-rules, do đó nó hoàn toàn khai báo. Ngược lại, trong syntax-casebit sau mẫu là mã thực tế, cuối cùng, phải trả về một đối tượng cú pháp ( #'(...)), nhưng cũng có thể chứa mã khác.


2
Một lợi thế mà bạn chưa đề cập: có, có những nỗ lực trong các ngôn ngữ khác, chẳng hạn như sweet.js cho JS. Tuy nhiên, trong lisps, viết một macro được thực hiện trong cùng ngôn ngữ như viết một hàm.
Florian Margaine 23/2/2015

Đúng vậy, bạn có thể viết các macro thủ tục (so với khai báo) bằng các ngôn ngữ Lisp, đây là những gì cho phép bạn làm những thứ thực sự tiên tiến. BTW, đây là những gì tôi thích về các hệ thống macro Scheme: có nhiều lựa chọn. Đối với các macro đơn giản, tôi sử dụng syntax-rules, đó hoàn toàn là khai báo. Đối với các macro phức tạp, tôi có thể sử dụng syntax-case, đó là một phần khai báo và một phần thủ tục. Và sau đó đổi tên rõ ràng, hoàn toàn là thủ tục. (Hầu hết các triển khai Đề án sẽ cung cấp một trong hai syntax-casehoặc ER. Tôi chưa thấy một chương trình nào cung cấp cả hai. Chúng tương đương với sức mạnh.)
Chris Jester-Young

Tại sao các macro phải sửa đổi AST? Tại sao họ không thể làm việc ở cấp cao hơn?
Elliot Gorokhovsky

1
Vậy thì tại sao LISP tốt hơn? Điều gì làm cho LISP đặc biệt? Nếu một người có thể thực hiện các macro trong js, chắc chắn người ta cũng có thể thực hiện chúng bằng bất kỳ ngôn ngữ nào khác.
Elliot Gorokhovsky

3
@ RenéG như tôi đã nói trong bình luận đầu tiên của tôi, một lợi thế lớn là bạn vẫn viết bằng cùng một ngôn ngữ.
Florian Margaine

23

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áptạ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 trykhối và sau đó gọi EndUpdate():

macro UIUpdate(value as Expression):
    return [|
        $value.BeginUpdate()
        try:
            $(UIUpdate.Body)
        ensure:
            $value.EndUpdate()
    |]

Các macrolệ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 MacroStatementvà biết cách thức ArgumentsBodycá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ó MacroStatementvà 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!


4
Niềm vui được đọc, cảm ơn bạn! Các macro không Lisp có kéo dài đến khi thay đổi cú pháp không? Bởi vì một trong những điểm mạnh của Lisp là cú pháp hoàn toàn giống nhau, do đó rất dễ dàng để thêm một hàm, câu lệnh có điều kiện, bất cứ điều gì vì tất cả chúng đều giống nhau. Trong khi với các ngôn ngữ không phải Lisp, một thứ khác với ngôn ngữ khác - if...không giống như một lời gọi hàm chẳng hạn. Tôi không biết Boo, nhưng hãy tưởng tượng Boo không có khớp mẫu, bạn có thể giới thiệu nó với cú pháp riêng của nó là macro không? Quan điểm của tôi là - bất kỳ macro mới nào trong Lisp đều cảm thấy tự nhiên 100%, trong các ngôn ngữ khác chúng hoạt động, nhưng bạn có thể thấy các mũi khâu.
greenoldman

4
Câu chuyện như tôi đã luôn đọc nó là một chút khác nhau. Một cú pháp thay thế cho biểu thức s đã được lên kế hoạch nhưng công việc trên nó bị trì hoãn vì các lập trình viên đã bắt đầu sử dụng biểu thức s và thấy chúng thuận tiện. Vì vậy, công việc về cú pháp mới cuối cùng đã bị lãng quên. Bạn có thể vui lòng trích dẫn nguồn chỉ ra những thiếu sót của lý thuyết trình biên dịch là lý do cho việc sử dụng biểu thức s không? Ngoài ra, gia đình Lisp tiếp tục phát triển trong nhiều thập kỷ (Scheme, Common Lisp, Clojure) và hầu hết các phương ngữ đã quyết định gắn bó với biểu thức s.
Giorgio

5
"Đơn giản và trực quan hơn": xin lỗi, nhưng tôi không thấy thế nào. "Updating.Arguments [0]" là không có ý nghĩa, tôi thà có một cuộc tranh luận tên và để cho các itselfs trình biên dịch kiểm tra nếu số lượng đối số trận đấu: pastebin.com/YtUf1FpG
coredump

8
"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." Tôi đã mong đợi tìm kiếm và áp dụng tối ưu hóa để chiếm phần lớn thời gian (nhưng tôi chưa bao giờ viết một trình biên dịch thực sự ). Am i thiếu cái gì ở đây?
Doval

5
Thật không may, ví dụ của bạn không cho thấy điều đó. Đó là nguyên thủy để thực hiện trong bất kỳ Lisp với macro. Trên thực tế đây là một trong những macro nguyên thủy nhất để thực hiện. Điều này khiến tôi nghi ngờ rằng bạn không biết nhiều về macro trong Lisp. "Cú pháp của Lisp bị kẹt trong những năm 1960": thực sự các hệ thống vĩ mô ở Lisp đã đạt được nhiều tiến bộ kể từ năm 1960 (Năm 1960 Lisp thậm chí không có macro!).
Rainer Joswig

3

Tôi đang học Scheme từ SICP và tôi có ấn tượng rằng một phần lớn của những gì tạo ra Scheme và, thậm chí hơn thế, LISP đặc biệt là hệ thống vĩ mô.

Làm sao vậy Tất cả các mã trong SICP được viết theo kiểu không có macro. Không có macro trong SICP. Chỉ trong một chú thích ở trang 373 là các macro đã được đề cập.

Nhưng, vì các macro được mở rộng tại thời gian biên dịch

Họ không nhất thiết phải như vậy. Lisp cung cấp các macro trong cả trình thông dịch và trình biên dịch. Do đó, có thể không có thời gian biên dịch. Nếu bạn có trình thông dịch Lisp, macro sẽ được mở rộng tại thời điểm thực thi. Vì nhiều hệ thống Lisp có trình biên dịch trên tàu, người ta có thể tạo mã và biên dịch nó khi chạy.

Hãy kiểm tra bằng cách sử dụng SBCL, một triển khai Lisp chung.

Hãy chuyển SBCL sang Trình thông dịch:

* (setf sb-ext:*evaluator-mode* :interpret)

:INTERPRET

Bây giờ chúng tôi xác định một macro. Macro in một cái gì đó khi nó được gọi cho mã mở rộng. Mã được tạo không in.

* (defmacro my-and (a b)
    (print "macro my-and used")
    `(if ,a
         (if ,b t nil)
         nil))

Bây giờ hãy sử dụng macro:

MY-AND
* (defun foo (a b) (my-and a b))

FOO

Xem. Trong trường hợp trên Lisp không làm gì cả. Macro không được mở rộng tại thời điểm định nghĩa.

* (foo t nil)

"macro my-and used"
NIL

Nhưng trong thời gian chạy, khi mã được sử dụng, macro được mở rộng.

* (foo t t)

"macro my-and used"
T

Một lần nữa, tại thời gian chạy, khi mã được sử dụng, macro được mở rộng.

Lưu ý rằng SBCL sẽ chỉ mở rộng một lần khi sử dụng trình biên dịch. Nhưng các triển khai Lisp khác nhau cũng cung cấp các thông dịch viên - như SBCL.

Tại sao các macro dễ dàng trong Lisp? Chà, chúng không thực sự dễ dàng. Chỉ có ở Lisps, và có rất nhiều, có hỗ trợ macro được tích hợp. Vì nhiều Lisps đi kèm với máy móc rộng rãi cho macro, có vẻ như điều đó thật dễ dàng. Nhưng các cơ chế vĩ mô có thể cực kỳ phức tạp.


Tôi đã đọc rất nhiều về Đề án trên web cũng như đọc SICP. Ngoài ra, không phải các biểu thức Lisp được biên dịch trước khi chúng được giải thích? Họ ít nhất phải được phân tích cú pháp. Vì vậy, tôi đoán "thời gian biên dịch" phải là "thời gian phân tích".
Elliot Gorokhovsky

@ RenéG Quan điểm của Rainer, tôi tin rằng, nếu bạn evalhoặc loadmã bằng bất kỳ ngôn ngữ Lisp nào, các macro trong đó cũng sẽ được xử lý. Trong khi đó, nếu bạn sử dụng một hệ thống tiền xử lý như được đề xuất trong câu hỏi của bạn evalvà tương tự sẽ không được hưởng lợi từ việc mở rộng vĩ mô.
Chris Jester-Young

@ RenéG Ngoài ra, "parse" được gọi readtrong Lisp. Sự khác biệt này rất quan trọng, bởi vì evalhoạt động trên thực tế các cấu trúc dữ liệu danh sách (như được đề cập trong câu trả lời của tôi), chứ không phải trên hình thức văn bản. Vì vậy, bạn có thể sử dụng (eval '(+ 1 1))và lấy lại 2, nhưng nếu bạn (eval "(+ 1 1)"), bạn lấy lại "(+ 1 1)"(chuỗi). Bạn sử dụng readđể có được từ "(+ 1 1)"(một chuỗi gồm 7 ký tự) đến (+ 1 1)(một danh sách có một ký hiệu và hai bản sửa lỗi).
Chris Jester-Young

@ RenéG Với sự hiểu biết đó, các macro không hoạt động vào readthời gian. Chúng hoạt động vào thời gian biên dịch theo nghĩa là nếu bạn có mã như thế (and (test1) (test2)), nó sẽ được mở rộng thành (if (test1) (test2) #f)(trong Lược đồ) chỉ một lần, khi mã được tải, thay vì mỗi lần mã được chạy, nhưng nếu bạn làm gì đó như (eval '(and (test1) (test2))), điều đó sẽ biên dịch (và mở rộng vĩ mô) biểu thức đó một cách thích hợp, trong thời gian chạy.
Chris Jester-Young

@ RenéG Homoiconicity là thứ cho phép các ngôn ngữ Lisp phát triển trên các cấu trúc danh sách thay vì dạng văn bản và cho các cấu trúc danh sách đó được chuyển đổi (thông qua macro) trước khi thực hiện. Hầu hết các ngôn ngữ chỉ evalhoạt động trên các chuỗi văn bản và khả năng sửa đổi cú pháp của chúng còn mờ nhạt và / hoặc cồng kềnh hơn nhiều.
Chris Jester-Young

1

Homoiconicity làm cho việc triển khai macro dễ dàng hơn nhiều. Ý tưởng rằng mã là dữ liệu và dữ liệu là mã làm cho nó có thể nhiều hơn hoặc ít hơn (chặn việc vô tình bắt các mã định danh, được giải quyết bằng các macro vệ sinh ) để tự do thay thế cái này cho cái kia. Lisp và Scheme làm cho điều này trở nên dễ dàng hơn với cú pháp biểu thức S của chúng có cấu trúc thống nhất và do đó dễ dàng biến thành AST tạo thành nền tảng của Macro cú pháp .

Các ngôn ngữ không có S-Expressions hoặc Homoiconicity sẽ gặp rắc rối khi triển khai Mac Syntactic mặc dù điều đó vẫn có thể được thực hiện. Dự án Kepler đang cố gắng giới thiệu họ với Scala chẳng hạn.

Vấn đề lớn nhất với việc sử dụng macro cú pháp ngoài tính không đồng âm, là vấn đề về cú pháp được tạo tùy ý. Chúng cung cấp sự linh hoạt và sức mạnh to lớn nhưng với mức giá mà mã nguồn của bạn có thể không còn dễ hiểu hay duy trì nữa.

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.