2010-02-21 43 views
60

Tôi đã nghe nói rằng Lisp cho phép bạn xác định lại chính ngôn ngữ đó, và tôi đã cố gắng nghiên cứu nó, nhưng không có lời giải thích rõ ràng ở bất cứ đâu. Có ai có một ví dụ đơn giản?Làm thế nào để Lisp cho phép bạn xác định lại chính ngôn ngữ đó?

+3

Bạn đã đọc sách nào trên LISP không bao gồm các macro? –

+1

Nhiều câu hỏi SO khác trên Lisp và macro bao gồm cùng một nền tảng: http://stackoverflow.com/questions/267862/what-makes-lisp-macros-so-special –

+11

nên được mở lại: Lisp cung cấp nhiều hơn macro để xác định lại ngôn ngữ: đọc macro, chức năng lớp học đầu tiên, chức năng tư vấn, giao thức đối tượng siêu kết nối, kết hợp phương thức CLOS, v.v. –

Trả lời

86

Người dùng Lisp gọi Lisp là ngôn ngữ lập trình lập trình. Nó được sử dụng cho tính toán biểu tượng - tính toán với các ký hiệu.

Macro chỉ là một cách để khai thác mô hình tính toán biểu tượng. Tầm nhìn rộng hơn là Lisp cung cấp các cách dễ dàng để mô tả các biểu thức biểu tượng: các thuật ngữ toán học, các biểu thức logic, các câu lệnh lặp, các quy tắc, các mô tả ràng buộc và hơn thế nữa. Macro (biến đổi của các dạng nguồn của Lisp) chỉ là một ứng dụng của tính toán biểu tượng.

Có một số khía cạnh nhất định: Nếu bạn hỏi về 'định nghĩa lại' ngôn ngữ, thì xác định lại đúng nghĩa là xác định lại một số cơ chế ngôn ngữ hiện có (cú pháp, ngữ nghĩa, ngữ dụng). Nhưng cũng có phần mở rộng, nhúng, loại bỏ các tính năng ngôn ngữ.

Trong truyền thống Lisp, đã có nhiều nỗ lực để cung cấp các tính năng này. Một phương ngữ Lisp và một triển khai nhất định có thể chỉ cung cấp một tập con của chúng.

Một vài cách để xác định lại/thay đổi/mở rộng chức năng theo quy định của hiện thực lớn Common Lisp:

  • s-biểu hiện cú pháp. Cú pháp của biểu thức s không cố định. Trình đọc (hàm READ) sử dụng cái gọi là đọc các bảng để chỉ định các hàm sẽ được thực thi khi một ký tự được đọc. Người ta có thể sửa đổi và tạo ra các bảng đọc. Điều này cho phép bạn ví dụ để thay đổi cú pháp của danh sách, ký hiệu hoặc các đối tượng dữ liệu khác. Một cũng có thể giới thiệu cú pháp mới cho các kiểu dữ liệu mới hoặc hiện có (như bảng băm). Cũng có thể thay thế hoàn toàn cú pháp biểu thức s và sử dụng một cơ chế phân tích cú pháp khác. Nếu trình phân tích cú pháp mới trả về các biểu mẫu Lisp, không có thay đổi cần thiết cho Trình thông dịch hoặc Trình biên dịch. Một ví dụ điển hình là một macro đọc có thể đọc các biểu thức infix. Trong một macro đọc như vậy, biểu thức infix và các quy tắc ưu tiên cho các toán tử đang được sử dụng. Các macro đọc khác với các macro thông thường: các macro đọc hoạt động trên cấp ký tự của cú pháp dữ liệu Lisp.

  • thay thế chức năng. Các chức năng cấp cao nhất được gắn với các biểu tượng. Người dùng có thể thay đổi ràng buộc này. Hầu hết các triển khai có một cơ chế để cho phép điều này ngay cả đối với nhiều hàm dựng sẵn. Nếu bạn muốn cung cấp một thay thế cho hàm ROOM được tích hợp sẵn, bạn có thể thay thế định nghĩa của nó. Một số triển khai sẽ gây ra lỗi và sau đó cung cấp tùy chọn để tiếp tục thay đổi. Đôi khi nó là cần thiết để mở khóa một gói. Điều này có nghĩa là các chức năng nói chung có thể được thay thế bằng các định nghĩa mới. Có những hạn chế đối với điều đó. Một là trình biên dịch có thể nội tuyến chức năng trong mã. Để xem một hiệu ứng thì người ta cần phải biên dịch lại mã sử dụng mã đã thay đổi.

  • chức năng cố vấn. Thường thì người ta muốn thêm một số hành vi vào các chức năng. Điều này được gọi là 'tư vấn' trong thế giới Lisp. Nhiều triển khai Common Lisp sẽ cung cấp một cơ sở như vậy.

  • gói tùy chỉnh. Các gói nhóm các ký hiệu trong không gian tên. Gói COMMON-LISP là trang chủ của tất cả các biểu tượng là một phần của tiêu chuẩn ANSI Common Lisp. Lập trình viên có thể tạo các gói mới và nhập các biểu tượng hiện có. Vì vậy, bạn có thể sử dụng trong các chương trình của bạn một gói EXTENDED-COMMON-LISP cung cấp nhiều hơn hoặc các cơ sở khác nhau. Chỉ cần thêm (IN-PACKAGE "EXTENDED-COMMON-LISP"), bạn có thể bắt đầu phát triển bằng cách sử dụng phiên bản mở rộng của Common Lisp. Tùy thuộc vào không gian tên được sử dụng, phương ngữ Lisp bạn sử dụng có thể trông hơi nhỏ hoặc thậm chí hoàn toàn khác nhau. Trong Genera trên máy Lisp có một số phương ngữ Lisp cạnh nhau theo cách này: ZetaLisp, CLtL1, ANSI Common Lisp và Symbolics Common Lisp.

  • CLOS và các đối tượng động. Hệ thống đối tượng Lisp chung đi kèm với thay đổi được tích hợp sẵn. Giao thức Meta-Object mở rộng các khả năng này. Bản thân CLOS có thể được mở rộng/định nghĩa lại trong CLOS. Bạn muốn thừa kế khác nhau. Viết một phương thức. Bạn muốn các cách khác nhau để lưu trữ các cá thể. Viết một phương thức. Các khe cần có thêm thông tin. Cung cấp một lớp học cho điều đó. Bản thân CLOS được thiết kế sao cho nó có thể thực hiện toàn bộ một 'vùng' của các ngôn ngữ lập trình hướng đối tượng khác nhau. Ví dụ điển hình là thêm những thứ như nguyên mẫu, tích hợp với hệ thống đối tượng nước ngoài (như Mục tiêu C), thêm sự kiên trì, ...

  • Lisp forms. Việc giải thích các hình thức Lisp có thể được định nghĩa lại với các macro. Macro có thể phân tích cú pháp mã nguồn mà nó bao quanh và thay đổi nó. Có nhiều cách khác nhau để kiểm soát quá trình chuyển đổi. Các macro phức tạp sử dụng bộ tập lệnh mã, hiểu cú pháp của các biểu mẫu Lisp và có thể áp dụng các phép biến đổi.Macro có thể tầm thường, nhưng cũng có thể rất phức tạp như macro LOOP hoặc ITERATE. Các ví dụ điển hình khác là các macro cho SQL nhúng và thế hệ HTML nhúng. Macro cũng có thể được sử dụng để di chuyển tính toán để biên dịch thời gian. Kể từ khi trình biên dịch là chính nó là một chương trình Lisp, tính toán tùy ý có thể được thực hiện trong quá trình biên dịch. Ví dụ, một macro Lisp có thể tính toán một phiên bản được tối ưu hóa của một công thức nếu các thông số nhất định được biết đến trong quá trình biên dịch.

  • Ký hiệu. Lisp thường cung cấp các macro biểu tượng. Macro biểu tượng cho phép thay đổi ý nghĩa của biểu tượng trong mã nguồn. Một ví dụ điển hình là: (với các khe (foo) bar (+ foo 17)) Ở đây biểu tượng FOO trong nguồn kèm theo WITH-SLOTS sẽ được thay thế bằng một cuộc gọi (thanh giá trị khe 'foo).

  • tối ưu hóa, với cái gọi là macro trình biên dịch có thể cung cấp các phiên bản hiệu quả hơn của một số chức năng. Trình biên dịch sẽ sử dụng các macro trình biên dịch đó. Đây là một cách hiệu quả để người dùng tối ưu hóa chương trình.

  • Điều kiện Xử lý - xử lý điều kiện mà kết quả từ việc sử dụng ngôn ngữ lập trình theo một cách nhất định. Lisp thường cung cấp một cách nâng cao để xử lý lỗi. Hệ thống điều kiện cũng có thể được sử dụng để xác định lại các tính năng ngôn ngữ. Ví dụ, một trong những có thể xử lý các lỗi chức năng không xác định với một cơ chế tự động tải bằng văn bản. Thay vì hạ cánh trong trình gỡ lỗi khi một hàm không xác định được Lisp nhìn thấy, trình xử lý lỗi có thể thử tự động tải lại hàm và thử lại thao tác sau khi tải mã cần thiết.

  • Biến đặc biệt - chèn các ràng buộc biến vào mã hiện có. Nhiều phương ngữ Lisp, như Common Lisp, cung cấp các biến đặc biệt/động. Giá trị của chúng được tra cứu khi chạy trên stack. Điều này cho phép kèm theo mã để thêm các ràng buộc biến có ảnh hưởng đến mã hiện có mà không thay đổi nó. Một ví dụ điển hình là một biến như * standard-output *. Người ta có thể rebind biến và tất cả các đầu ra bằng cách sử dụng biến này trong phạm vi năng động của các ràng buộc mới sẽ đi đến một hướng mới. Richard Stallman lập luận rằng điều này rất quan trọng đối với anh ta rằng nó đã được thực hiện mặc định trong Emacs Lisp (mặc dù Stallman biết về ràng buộc từ vựng trong Scheme và Common Lisp).

Lisp có các tiện ích này và nhiều hơn nữa, vì nó đã được sử dụng để triển khai nhiều ngôn ngữ và mô hình lập trình khác nhau. Một ví dụ điển hình là việc triển khai nhúng một ngôn ngữ logic, Prolog nói. Lisp cho phép mô tả các thuật ngữ Prolog với các biểu thức s và với một trình biên dịch đặc biệt, các thuật ngữ Prolog có thể được biên dịch thành mã Lisp. Đôi khi cú pháp Prolog thông thường là cần thiết, sau đó một trình phân tích cú pháp sẽ phân tích các thuật ngữ Prolog điển hình thành các dạng của Lisp, mà sau đó sẽ được biên dịch. Các ví dụ khác cho các ngôn ngữ được nhúng là các ngôn ngữ dựa trên quy tắc, các biểu thức toán học, các thuật ngữ SQL, trình biên dịch Lisp nội tuyến, HTML, XML và nhiều hơn nữa.

14

Tôi sẽ đi vào đường ống trong Đề án đó khác với Common Lisp khi nói đến định nghĩa cú pháp mới. Nó cho phép bạn xác định các mẫu bằng cách sử dụng define-syntax được áp dụng cho mã nguồn của bạn bất cứ nơi nào chúng được sử dụng. Chúng trông giống như các hàm, chỉ chúng chạy ở thời gian biên dịch và biến đổi AST.

Dưới đây là ví dụ về cách let có thể được xác định theo điều khoản lambda. Dòng có let là mẫu phù hợp và dòng có lambda là mẫu mã kết quả.

(define-syntax let 
    (syntax-rules() 
    [(let ([var expr] ...) body1 body2 ...) 
    ((lambda (var ...) body1 body2 ...) expr ...)])) 

Lưu ý rằng đây KHÔNG phải là thay thế văn bản. Bạn thực sự có thể xác định lại lambda và định nghĩa trên cho let sẽ vẫn hoạt động, bởi vì nó đang sử dụng định nghĩa lambda trong môi trường nơi let được xác định. Về cơ bản, nó mạnh mẽ như macro nhưng sạch như các chức năng.

4

Macro là lý do thông thường để nói điều này. Ý tưởng là bởi vì mã chỉ là một cấu trúc dữ liệu (một cây, nhiều hơn hoặc ít hơn), bạn có thể viết các chương trình để tạo ra cấu trúc dữ liệu này. Tất cả mọi thứ bạn biết về cách viết các chương trình tạo và thao tác các cấu trúc dữ liệu, do đó, thêm vào khả năng mã của bạn một cách rõ ràng.

Macro không hoàn toàn xác định lại ngôn ngữ, ít nhất là theo tôi biết (Tôi thực sự là một Schemer; tôi có thể sai), bởi vì có một hạn chế. Macro chỉ có thể lấy một mã con duy nhất của mã của bạn và tạo một cây con duy nhất để thay thế nó. Vì vậy, bạn không thể viết các macro toàn bộ chương trình chuyển đổi, như là mát mẻ như vậy sẽ được.

Tuy nhiên, các macro khi chúng đứng yên vẫn có thể làm được rất nhiều thứ - chắc chắn hơn bất kỳ ngôn ngữ nào khác sẽ cho phép bạn làm. Và nếu bạn đang sử dụng trình biên dịch tĩnh, sẽ không khó để thực hiện một phép chuyển đổi toàn bộ chương trình, do đó, sự hạn chế này ít hơn rất nhiều.

+0

rằng cực đại "mã là dữ liệu" chỉ đúng trong triển khai sớm, nhưng bây giờ có rất nhiều minutiae về gói và môi trường kèm theo và cái gì không, vì vậy nó không chỉ là biểu tượng trong danh sách nữa mà còn biến trong cây cú pháp trừu tượng - tức là * mã *. –

2

Câu trả lời này đặc biệt liên quan đến Common Lisp (CL sau đây), mặc dù các phần của câu trả lời có thể áp dụng cho các ngôn ngữ khác trong gia đình lisp.

Vì CL sử dụng biểu thức S và (chủ yếu) trông giống như một chuỗi các ứng dụng hàm, không có sự khác biệt rõ ràng giữa mã dựng sẵn và mã người dùng. Sự khác biệt chính là "những thứ ngôn ngữ cung cấp" có sẵn trong một gói cụ thể trong môi trường mã hóa.

Với một chút cẩn thận, không khó để thay thế mã và sử dụng các thay thế đó.

Bây giờ, trình đọc "bình thường" (phần đọc mã nguồn và biến thành ký hiệu nội bộ) dự kiến ​​mã nguồn ở định dạng khá cụ thể (biểu thức S được lồng tiếng) nhưng khi trình đọc được điều khiển bởi một cái gì đó được gọi là "bảng đọc" và chúng có thể được tạo và sửa đổi bởi nhà phát triển, nó cũng có thể thay đổi cách mã nguồn được cho là trông như thế nào.

Hai điều này ít nhất nên cung cấp một số lý do là tại sao Common Lisp có thể được coi là ngôn ngữ lập trình có thể lập trình lại. Tôi không có một ví dụ đơn giản trong tay, nhưng tôi có một phần thực hiện một bản dịch của Common Lisp sang tiếng Thụy Điển (được tạo ra cho ngày 1 tháng 4, một vài năm trở lại).

2

Từ bên ngoài, nhìn vào ...

Tôi luôn luôn nghĩ rằng đó là vì Lisp cung cấp, cốt lõi của nó, cơ bản, khai thác hợp lý nguyên tử mà bất kỳ quá trình hợp lý có thể được xây dựng (và đã được xây dựng và cung cấp như các bộ công cụ và bổ trợ) từ các thành phần cơ bản.

Nó không phải là quá nhiều mà nó có thể xác định lại chính nó như là định nghĩa cơ bản của nó là dễ uốn mà nó có thể mất bất kỳ hình thức và không có hình thức được giả định/giả định vào cấu trúc. Như một phép ẩn dụ, nếu bạn chỉ có hợp chất hữu cơ, bạn có hóa chất hữu cơ, nếu bạn chỉ có oxit kim loại bạn luyện kim nhưng nếu bạn chỉ có các yếu tố bạn có thể làm mọi thứ nhưng bạn có thêm các bước đầu tiên để hoàn thành .... hầu hết trong số đó những người khác đã làm cho bạn ....

tôi nghĩ .....

2

mát dụ tại http://www.cs.colorado.edu/~ralex/papers/PDF/X-expressions.pdf

macro đọc xác định X-biểu thức để cùng tồn tại với S-biểu thức, ví dụ như ,

? (cx <circle cx="62" cy="135" r="20"/>) 
62 

plain vanilla Common Lisp tại http://www.AgentSheets.com/lisp/XMLisp/XMLisp.lisp ...

(eval-when (:compile-toplevel :load-toplevel :execute) 
    (when (and (not (boundp '*Non-XMLISP-Readtable*)) (get-macro-character #\<)) 
    (warn "~%XMLisp: The current *readtable* already contains a #/< reader function: ~A" (get-macro-character #\<)))) 

... tất nhiên các phân tích cú pháp XML là không đơn giản như vậy nhưng hooking nó vào người đọc lisp được.

3

Tham chiếu đến chương 'cấu trúc và diễn giải chương trình máy tính' 4-5 là những gì tôi đã bỏ lỡ từ các câu trả lời (link).

Các chương này hướng dẫn bạn xây dựng bộ đánh giá Lisp trong Lisp. Tôi thích đọc vì nó không chỉ cho thấy làm thế nào để xác định lại Lisp trong một bộ đánh giá mới, nhưng cũng cho phép bạn tìm hiểu về các chi tiết kỹ thuật của ngôn ngữ lập trình Lisp.

Các vấn đề liên quan