Is defun or setf preferred for creating function definitions in common lisp and why?
Asked Answered
R

1

3

What is the fundamental difference in the functions defined using defun and setf as below and is one method preferred over another outside of style considerations?

Using defun:

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

* (myfirst '(A B C))

A

Using setf:

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

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

A

If, as according to Wikipedia:

named functions are created by storing a lambda expression in a symbol using the defun macro

Using setf to create a variable in a different way requires the use of funcall:

* (defvar myfirst)

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

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

A

My understanding is that this type of variable is different than the previous in that this variable is not found in the same namespace as the defun bound symbol as described in Why multiple namespaces?.

Review answered 5/2, 2017 at 20:47 Comment(0)
N
6

First of all, one should never underestimate the importance of style. We write code not just for computers to run, but, much more importantly, for people to read. Making code readable and understandable for people is a very important aspect of software development.

Second, yes, there is a big difference between (setf fdefinition) and defun.

The "small" differences are that defun can also set the doc string of the function name (actually, depending on how your imeplementation works, it might do that with lambda also), and creates a named block (seen in the macroexpansions below) which you would otherwise have to create yourself if you want to.

The big difference is that the compiler "knows" about defun and will process it appropriately.

E.g., if your file is

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

then the compiler will probably warn you that you call foo in bar with the wrong number of arguments:

WARNING: in BAR in lines 3..4 : FOO was called with 3 arguments, but it requires 1 argument. [FOO was defined in lines 1..2 ]

If you replace the defun foo with (setf (fdefinition 'foo) (lambda ...)), the compiler is unlikely to handle it as carefully. Moreover, you will probably get a warning along the lines of

The following functions were used but not defined: FOO

You might want to examine what defun does in your implementation by macroexpanding it:

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

CLISP expands it to

(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 does:

(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)))

The point here is that defun has a lot "under the hood", and for a reason. setf fdefinition is, on the other hand, more of "what you see is what you get", i.e., no magic involved.

This does not mean that setf fdefinition has no place in a modern lisp codebase. You can use it, e.g., to implement a "poor man's trace" (UNTESTED):

(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))
Neruda answered 5/2, 2017 at 22:46 Comment(1)
DEFUN will also create a named block. One would have to add that to the lambda.Marla

© 2022 - 2024 — McMap. All rights reserved.