2017-02-05 6 views
3

В чем принципиальное отличие функций, определенных с помощью defun и setf, как показано ниже, и является ли один метод предпочтительным для другого вне соображений стиля?Является ли defun или setf предпочтительным для создания определений функций в общем lisp и почему?

Использование defun:

* (defun myfirst (l) 
    (car l)) 
MYFIRST 

* (myfirst '(A B C)) 

A 

Использование setf:

* (setf (fdefinition 'myfirst) #'(lambda (l) (car l))) 

#<FUNCTION (LAMBDA (L)) {10021B477B}> 
* (myfirst '(A B C)) 

A 

Если, как в соответствии с Wikipedia:

названные функции создаются путем сохранения лямбда-выражение в символе, используя defun macro

Использование setf создать переменную по-другому требует использования funcall:

* (defvar myfirst) 

MYFIRST 
* (setf myfirst (lambda (l) (car l))) 

#<Interpreted Function (LAMBDA (X) (+ X X)) {48035001}> 
* (funcall myfirst '(A B C)) 

A 

Я понимаю, что этот тип переменной отличается от предыдущего в том, что эта переменная не найдена в такое же пространство имен, как и связанный символ defun, как описано в Why multiple namespaces?.

ответ

7

Прежде всего, нельзя недооценивать важность стиля. Мы пишем код не только для запуска компьютеров, но, что гораздо важнее, для чтения людьми. Кодекс, читаемый и понятный для людей, является очень важным аспектом разработки программного обеспечения.

Во-вторых, да, есть большая разница между (setf fdefinition) и defun.

«малые» различия в том, что defun могут также установить doc string имени функции (на самом деле, в зависимости от того, как работает ваш imeplementation, он может сделать это с лямбда также), и создает именованный block (видел в macroexpansions ниже), которые в противном случае вам нужно было бы создать, если хотите.

Большая разница в том, что компилятор «знает» около defun и обработает его соответствующим образом.

Например, если ваш файл

(defun foo (x) 
    (+ (* x x) x 1)) 
(defun bar (x) 
    (+ (foo 1 2 x) x)) 

тогда компилятор может предупредить вас, что вы называете foo в bar с неправильным количеством аргументов:

ВНИМАНИЕ: в BAR в строках 3 ..4: FOO вызывается с тремя аргументами, но для этого требуется 1 аргумент. [FOO был определен в строках 1..2]

Если заменить defun foo с (setf (fdefinition 'foo) (lambda ...)), компилятор вряд ли с ним обращаться так же тщательно.Кроме того, вы, вероятно, получите предупреждение вдоль линий

Следующие функции использовались, но не определено: FOO

Вы можете исследовать то, что defun делает в вашей реализации по macroexpanding это:

(macroexpand-1 '(defun foo (x) "doc" (print x))) 

CLISP расширяет его

(LET NIL (SYSTEM::REMOVE-OLD-DEFINITIONS 'FOO) 
(SYSTEM::EVAL-WHEN-COMPILE 
    (SYSTEM::C-DEFUN 'FOO (SYSTEM::LAMBDA-LIST-TO-SIGNATURE '(X)))) 
(SYSTEM::%PUTD 'FOO 
    (FUNCTION FOO 
    (LAMBDA (X) "doc" (DECLARE (SYSTEM::IN-DEFUN FOO)) (BLOCK FOO (PRINT X))))) 
(EVAL-WHEN (EVAL) 
    (SYSTEM::%PUT 'FOO 'SYSTEM::DEFINITION 
    (CONS '(DEFUN FOO (X) "doc" (PRINT X)) (THE-ENVIRONMENT)))) 
'FOO) 

SBCL делает:

(PROGN 
(EVAL-WHEN (:COMPILE-TOPLEVEL) (SB-C:%COMPILER-DEFUN 'FOO NIL T)) 
(SB-IMPL::%DEFUN 'FOO 
        (SB-INT:NAMED-LAMBDA FOO 
         (X) 
        "doc" 
        (BLOCK FOO (PRINT X))) 
        (SB-C:SOURCE-LOCATION))) 

Дело в том, что defun имеет много «под капотом», и по причине. setf fdefinition, с другой стороны, больше «то, что вы видите, это то, что вы получаете», т. Е. Никакой магии.

Это не означает, что setf fdefinition не место в современной кодовой базе. Вы можете использовать его, например, чтобы реализовать «бедняк trace» (непроверенный):

(defun trace (symbol) 
    (setf (get symbol 'old-def) (fdefinition symbol) 
     (fdefinition symbol) 
     (lambda (&rest args) 
      (print (cons symbol args)) 
      (apply (get symbol 'old-def) args)))) 
(defun untrace (symbol) 
    (setf (fdefinition symbol) (get symbol 'old-def)) 
    (remprop symbol 'odd-def)) 
+1

DEFUN также создать именованный блок. Нужно добавить это к лямбде. –