2009-08-02 28 views
7

Cân nhắc javascript này:Bạn có thể chỉ cho tôi cách viết lại các hàm trong lisp không?

function addX(n) 
{ 
    return 3 + n; 
} 
alert(addX(6)); //alerts 9 
eval('var newFunc = ' + addX.toString().replace("3", "5") + ';'); 
alert(newFunc(10)); //alert 15 

Vui lòng bỏ qua thực tế là nó sử dụng đáng ngờ và phương pháp, nguy hiểm, khó theo dõi trong một codebase lớn, và vân vân. Nó cho phép bạn sửa đổi các chức năng, trên bay, dựa trên đầu vào từ người sử dụng. Tôi không thể chứng minh điều đó, nhưng tôi cũng dễ dàng như vậy.

Tôi hy vọng bạn có thể chỉ cho tôi cách thực hiện điều này trong lisp. Tôi đã đọc rất nhiều hướng dẫn, đọc rất nhiều về macro, asked a broader question, đã thử rất nhiều thứ nhưng cuối cùng đã xuất hiện ngắn.

Tôi muốn biết làm thế nào, trong lisp, tôi có thể sửa đổi chức năng này tại thời gian chạy để thay thế thêm 5. Hoặc bất cứ điều gì khác người dùng có thể nhập.

(define (addX n) 
    (+ 3 n)) 

Tôi không tìm kiếm currying! Tôi biết tôi có thể làm điều này:

(define addXCurry 
    (lambda (x) 
    (lambda (n) 
     (+ x n)))) 

(define add5 (addXCurry 5)) 
(add5 10) 

Nhưng điều này đang tạo ra một nhà máy chức năng.
Tôi đang sử dụng một ví dụ đơn giản bởi vì tôi muốn hoàn toàn hiểu được cách khó khăn trên một cái gì đó đơn giản, đủ.


Sửa Cảm ơn tất cả các bạn cho câu trả lời của bạn. Tôi đoán rằng việc gác máy lớn của tôi xung quanh các macro (như tôi đã hiểu), là tôi chưa từng thấy một sự tách biệt hoàn toàn với sự sửa đổi từ văn bản. Ví dụ javascript rất đơn giản - nhưng bạn có thể thực hiện các tác vụ viết lại phức tạp hơn dựa trên đầu vào của người dùng.

Các macro tôi đã xem đều dựa trên "thời gian biên dịch" (hoặc thời gian viết lập trình giả định). Giống như trong C++ bạn không thể có tham số mẫu động - nó phải được biết tại thời gian biên dịch.

(Có vẻ như) Trong lisp bạn không thể thay đổi một cách cơ bản một thủ tục tại thời gian chạy theo cách bạn có thể trong javascript vì bạn đã mất nguồn. Bạn có thể eval và xác định lại nó, nhưng bạn không thể lặp qua các phần tử của danh sách (danh sách là định nghĩa hàm), kiểm tra từng phần tử và quyết định có thay đổi hay không. Ngoại lệ này có vẻ là những ví dụ trong câu trả lời của Rainer, đó là mặt đất run rẩy.

+0

http://stackoverflow.com/q/16380479/849891 là về cách nó được thực hiện trong Lisp một cách thích hợp, AFAIK - với đóng cửa, làm cho các chức năng vào các đối tượng đáp ứng với thông điệp: '(fun 'call args ...) 'hoặc' (fun 'reset-x-to 5) '(hoặc, trong C, sử dụng các biến tĩnh bên trong các hàm). Và không, về cơ bản nó không phải là về cà ri, thậm chí nếu nó có thể được thực hiện với một cú pháp, hời hợt, hời hợt. –

Trả lời

15

Phần khó là Common Lisp (và một số Lisps khác) giúp loại bỏ các mã nguồn. Đặc biệt là khi một trình biên dịch có liên quan. Theo mặc định mã nguồn đã biến mất và tất cả còn lại là mã máy sau đó. Làm thế nào để khôi phục nguồn Lisp và hình dạng gì?

Lý do đằng sau điều này: tại sao chương trình CL phải được yêu cầu để giữ nguồn? Nó có thể được biên dịch hoàn toàn thành mã máy hoặc mã C và không có trình biên dịch/EVAL khi chạy. Chương trình có thể chạy mà không có nhiều môi trường phát triển (không có trình biên dịch, v.v.). Môi trường Lisp chung cũng không được yêu cầu để có thể 'uncompile' mã cho một số loại mã nguồn được xây dựng lại.

Ngoài ra, điều này thường phức tạp.

(let ((n 0) (step 2)) (defun foo() (incf n step))) 

Nguồn gốc ở trên và cách bạn có thể thay đổi BƯỚC? Hàm này phụ thuộc vào sự ràng buộc từ vựng.

Một biến chứng:

(defun foo (n) (+ n #.(random 1.0))) 

Làm thế nào để phục hồi đó? Mỗi lần Lisp đọc văn bản nguồn, một số ngẫu nhiên sẽ được đọc.

Một biến chứng:

(setf (symbol-function 'foo) (compute-function)) 

Bạn có thể thiết lập giá trị chức năng với một số chức năng tùy tiện tính hoặc một chức năng được xác định trước (như SIN). Làm thế nào để khôi phục chúng, nếu chúng được biên dịch thành mã máy, nạp như mã máy vv?

Nếu triển khai Lisp chung giữ mã nguồn, FUNCTION-LAMBDA-EXPRESSION sẽ truy xuất nó.

Có hai cách để giải quyết vấn đề này:

a) Cho Lisp biết mã nguồn hoặc ghi nhớ nguồn.

Cung cấp nguồn.

(let* ((fs (copy-list '(lambda (n) (+ n 3)))) 
    (fc (compile nil fs))) 
    (print (funcall fc 6)) 
    (setf (third (third fs)) 5) 
    (setf fc (compile nil fs)) 
    (funcall fc 6)) 

được mở rộng ví dụ:

Viết macro DEFINE rằng cả hai nhớ nguồn và định nghĩa hàm.

(defmacro define (&rest source) 
    `(progn (setf (get ',(first source) :source) (list* 'defun ',source)) 
    (defun ,@source))) 

Phía trên đặt mã nguồn vào danh sách đặc tính biểu tượng trong: SOURCE.

Bây giờ chúng ta có thể viết một hàm mà sửa đổi nguồn và biên dịch nó:

(defun modify (fname modifier) 
    (let ((source (get fname :source))) 
    (when source 
     (setf (get fname :source) (funcall modifier source)) 
     (eval (get fname :source)) 
     (compile fname)))) 

Ví dụ định nghĩa:

(define addx (n) (+ n 3)) 

Ví dụ viết lại:

(modify 'addx (lambda (source) (setf (third (fourth source)) 6) source)) 

b) Một số Common Lisp triển khai thực hiện một hàm gọi là FUNCTION-LAMBDA-EXPRESSION (được định nghĩa trong ANSI Common Lisp)).

Hàm này trả về ba giá trị: nguồn là dữ liệu Lisp, đóng-p và tên. Nó sẽ cho phép bạn thay đổi nguồn, biên dịch nó và đặt tên cho hàm mới bằng cách sử dụng COMPILE. Ví dụ về mã còn lại là tập thể dục.

Sự cố: trong Common Lisp macro DEFUN định nghĩa các hàm. Những gì vĩ mô làm đằng sau hậu trường (cuốn sách giữ cho IDE, viết lại mã, ...) là thực hiện phụ thuộc. Vì vậy, mã được trả về bởi FUNCTION-LAMBDA-EXPRESSION (nếu thực thi trả về mã nguồn) có thể trông khác nhau đối với mỗi lần thực hiện.

Đây là một LispWorks dụ:

CL-USER 12 > (function-lambda-expression #'addx) 

(LAMBDA (N) 
    (DECLARE (SYSTEM::SOURCE-LEVEL #<EQ Hash Table{0} 217874D3>)) 
    (DECLARE (LAMBDA-NAME ADDX)) 
    (+ N 3)) 
NIL 
ADDX 

Vì vậy, bạn có thể thao tác các biểu hiện nguồn và thay đổi nó.

1

Cách bạn đang viết javascript của bạn cho thấy rằng bạn đang tìm kiếm một vĩ mô:

(define-syntax addX 
    (syntax-rules() 
    ((addX a) (+ 3 a)) 
    ((addX a b) (+ a b)))) 

vì vậy đây mang đến cho bạn:

> (addX 2) 
5 
> (addX 2 5) 
7 

vĩ mô mang lại cho bạn những gì bạn muốn ở chỗ nó không phải là một nhà máy chức năng currying và nó sẽ không viết lại tên của macro để bạn đi cùng với những thay đổi (mặc dù điều này có thể được thực hiện với một cái gì đó phức tạp hơn), nhưng cung cấp cho bạn những gì bạn muốn ở chỗ nó cho phép bạn để thay đổi chức năng khi đang bay.

3

mã thủ tục của chúng tôi:

(define add-x-code 
    '(lambda (n) 
    (+ 3 n))) 

Đánh giá nó vào một chức năng:

(define add-x (eval add-x-code)) 
> (add-x 5) 
8 

Thay đổi nó:

(define add-x-2 (eval (replace 3 7 add-x-code))) 

> (add-x-2 5) 
12 

Điều đó có vẻ giống như những gì bạn đã làm trong mã JavaScript . Cho dù đó là một ý tưởng tốt không phải là những gì bạn hỏi, vì vậy tôi sẽ để nó ra.

(thủ tục thay thế đơn giản Tôi whipped lên :)

(define (replace x y list) 
    (map (lambda (x.) 
     (if (equal? x. x) 
      y 
      (if (list? x.) 
       (replace x y x.) 
       x.))) 
     list)) 
+0

Xin cảm ơn! Điều này sẽ yêu cầu viết tất cả các chức năng như danh sách và đánh giá tất cả chúng ngay từ đầu - đúng không? –

+0

Vâng, đó là lý do tại sao các macro là cách tự nhiên hơn để thực hiện điều đó. Nhưng sau đó, bạn muốn một cách khó khăn phải không? – Pinochle

+0

Tôi nghĩ vậy. Tôi không biết một cách đơn giản trong đó người ta có thể kết hợp mã với thủ tục sao cho khớp nối có thể được sử dụng bình thường như một quy trình nhưng cũng được thăm dò mã từ nó, như trong JavaScript. –

7

Trường hợp ngoại lệ có vẻ là ví dụ trong câu trả lời của Rainer, rung lắc mặt đất.

Tại sao? Đó là kết luận hợp lý. Bạn không thể dựa vào trình biên dịch bảo quản nguồn, vì vậy bạn chỉ cần lưu trữ nó.

Sau đó bạn có thể đúng làm việc với định nghĩa của hàm (trái ngược với Javascript nơi bạn chỉ cần hack một biểu diễn chuỗi, là một ví dụ chính của một điều run rẩy).

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