Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

emacs lisp - eval unexpectedly different than eval-print-last-sexp

Tags:

emacs

lisp

Forgive my verbosity. I'm not exactly sure how to describe my situation.

Given the following...

(defun three () 3)
(defun four () 4)
(defun makeplusser (x)
  (list 'defun (make-symbol (format "%s+" x)) '(y) 
    (list '+ (list x) 'y)))

In my scratch buffer, I type the following then hit C-j (eval-print-last-sexp)...

(makeplusser 'three)

Which gets me this...

(defun three+ (y) (+ (three) y))

... Which I highlight, hit C-j, and am able to use...

(three+ 4) ; => 7

When I do this...

(eval (makeplusser 'four)) ; => four+

It dumps the function name "four+" to the buffer which leads me to believe it's been properly defun'd. When I try to use it...

(four+ 3)

I get the following error message:

Debugger entered--Lisp error: (void-function four+) (four+ 3)
eval((four+ 3))

Questions are:

  1. Why does (eval-print-last-sexp) behave so differently than using (eval) ?
  2. How can I get my "plusser" functions without having to evaluate the generated defun in my buffer? I'd like to be able to just (mapcar #'eval (mapcar #'makeplusser '(three four))) or something but that isn't working the way I expect.
like image 235
Ishpeck Avatar asked Mar 21 '12 13:03

Ishpeck


2 Answers

1- Your problem is not caused by differences between eval-print-last-sexp and eval.

For example, if you eval the following snippet (for example using C-x C-e), everything works

(setq exp (list 'defun 'four+ '(y) (list '+ '(four) 'y)))
(eval exp)
(four+ 4)

whereas evaluating in the same way the following snippet doesn't work as expected:

(setq exp (makeplusser 'four))
(eval exp)
(four+ 4)

even though the value of exp is identical in both cases

2- The actual problem you are facing is related to the way you create the symbol for your function: make-symbol creates an uninterned symbol, which will not be visible from outside the function call. You should create an interned symbol instead, like this :

(defun makeplusser (x)
  (list 'defun (intern (format "%s+" x)) '(y) 
    (list '+ (list x) 'y)))
(eval (makeplusser 'four))

3- For such things, you should consider writing a macro instead of evaluating the result of a function:

(defmacro makeplusser (x)
  (let ((name   (intern (format "%s+" x)))
        (argsym (make-symbol "arg")))
    `(defun ,name (,argsym)
       (+ (,x) ,argsym))))
like image 180
François Févotte Avatar answered Sep 27 '22 17:09

François Févotte


Disclaimer: I don't know Emacs Lisp, but I know Lisp.

What I believe you are running into is read/print confusion with uninterned symbols. That is to say, I suspect that (make-symbol ...) in Emacs Lisp, just like the same-named function in other dialects such as Common Lisp, creates a new symbol object which has nothing to do with a symbol of the same name that is scanned from a printed notation (from a file, terminal, string, edit buffer, ...).

Your code works when you grab the printed output of your code-generating function (a.k.a S-expression), because the printed notation of the symbol four is read-back into Lisp and interned, causing that notation to become the same object as the name of the function four.

But (make-symbol "four") is a different symbol object. When you use eval on the code which comes out of your function, you are using the data structure directly and so your mistake is not covered up by reducing the code to text and reading it back. The symbol does not get converted to the token four and back to the object four via interning. eval will see your original symbol that came from make-symbol: the same machine pointer to the same piece of memory.

(In Common Lisp, the "uninterned symbol" coming from (make-symbol "four") is normally printed with a hash-dot notation, like #:four so you can spot them. (Actually #: means symbol with no home package, not uninterned, but that's very obscure Common Lisp subtlety.))

Anyway, look for a function called intern. (intern "four") will look up the existing symbol with that name and return it, rather than make a new one.

;; two symbol interns for same name result in the same object
;; we are comparing the same pointer to itself
(eq (intern "foo") (intern "foo")) -> t

;; two symbol constructions result in two different object
;; two different pointers to separately allocated objects
(eq (make-symbol "foo") (make-symbol "foo")) -> nil

Also:

You really must learn backquote if you want to write code-generating code. Otherwise you're doing it in a 1960's way rather than the modern 1970's way:

;; don't let your friends do this:
(defun makeplusser (x)
  (list 'defun (make-symbol (format "%s+" x)) '(y) 
    (list '+ (list x) 'y)))

;; teach them this:
(defun makeplusser (x)
 `(defun ,(intern (format "%s+" x)) (y) 
    (+ (,x) y)))

;; even more clearly, perhaps
(defun makeplusser (func-to-call)
  (let ((func-name (format "%s+" x)))
    `(defun ,func-name (arg)
       (+ (,func-to-call) arg))))

When to use make-symbol

Use make-symbol when you need to create a guaranteed unique symbol (not the same object as any other symbol) even if it happens to have the same name as other symbols. Other Lisp dialects also have a function called gensym which is like make-symbol but it also appends an incrementing numeric stamp to the name to make it easier to distinguish these "gensyms" when multiple ones occur in the same context. gensym is much more commonly used than make-symbol in Lisps that have it. Unique symbols are useful in code generation (macros) for generating unique labels for things that must be absolutely invisible to the surrounding code, such as temporary local variables within the inserted block of code. Your function's argument should be a unique symbol:

;; even more clearly, perhaps
(defun makeplusser (func-to-call)
  (let ((func-name (format "%s+" x))
        (arg-sym (make-symbol "arg"))
    `(defun ,func-name (,arg)
       (+ (,func-to-call) ,arg))))

The reason is: Emacs Lisp is dynamically scoped. If we call the argument y, there is a risk that the user function which is called, like (four) might contain a reference to y, where the programmer's intent is to reach his or her own variable y. But your generated function accidentally captures the reference!

By using a gensym for the argument, we avoid that problem; there is no chance that the user's code can refer to the argument: we have achieved "hygiene" or "transparency".

like image 44
Kaz Avatar answered Sep 27 '22 17:09

Kaz