Can Emacs Lisp assign a lambda form to a variable like Scheme?

不想你离开。 提交于 2020-04-14 07:23:11

问题


While investigating Emacs Lisp's symbol cells, I found out that for an example function like

(defun a (&rest x)
    x)

I can call (symbol-function 'a), which returns (lambda (&rest x) x). I can then use it if I want

> ((lambda (&rest x) x) 1 2 3 4 5)
(1 2 3 4 5)

which has the same functionality as the original function above. Now, this reminds me of Scheme where a lambda expression is the body of the function and is assigned to a variable name with Scheme's all-purpose define. For example

(define atom?
    (lambda (x)
        (and (not (pair? x)) (not (null? x)))))

simply assigns the lambda expression to atom? -- and now atom? is a function. So can elisp do this, i.e., assign a lambda expression to a symbol and then use it as a function? I've tried

(setq new-a (lambda (&rest x) x))

which gives (void-function new-a) if I try to use it as a function. Is there a way to imitate the Scheme world on this issue? It seems like there must be a way. Why else would the function cell of a contain (lambda (&rest x) x) if we couldn't turn this lambda expression into a function?


回答1:


An important difference between scheme and emacs lisp (and indeed most other lisps) is that scheme has a single namespace whereas emacs lisp has separate namespaces for functions and variables. The first position in a list form that is evaluated names a function and that name is looked up in the function name space. In scheme, all names live in the same space, the value bound to the name is looked up and used whereever it appears.

This means that in emacs lisp you can something like this:

(defun f (x) (+ x x))
(setq f 2)
(f f) ;=> 4

This is not possible in scheme, here there would be only one f and if you set its value, it would change from (say) a function to a number.

There are different ways of handling this in emacs lisp.

One is to use functions such as funcall and apply, these takes a function and some arguments and apply the function to the arguments, as in:

(setq f (lambda (x) (+ x x)))
(funcall f 2) ;=> 4

Another approach is to manipulate what the function name f means. There is a function called fset that allows you to attach functions to names (in the function namespace):

(fset 'f (lambda (x) (+ x x x)))
(f 2) ;=> 6

Note that fset works on names (aka symbols) so the name f needs to be quoted, otherwise it would be read as the value of a variable. That is why the function to a variable is called setq, the "q" stands for "quoted" so setq is actually a special function that quotes its first argument, so that the programmer does not have to do it. There is an equivalent normal function called set that does not do any quoting, as in:

(setq x 1)  ; x is 1
(set 'x 2)  ; x is 2
(setq x 'x) ; x is the symbol x
(set x 3)   ; x is now 3

The last form may look confusing but as set is a normal form, it will look up the value of the variable x, that value is the symbol x and that then names the variable that will be changed (i.e. x). Thus one advantage of set is that it is possible to set variables whose name you do not know but rather commutes.




回答2:


This is an addendum to the other answer. The other answer explains the difference between lisp-1s (lisps which have a single namespace for function and variable bindings) and lisp-2s (lisps which have a separate namespace for function bindings).

I want to explain why a lisp-2 might make things better, and especially why it did so historically.

First of all let's consider a little bit of Scheme code:

(define (foo x)
  (let ([car (car x)])
    ... in here (car ...) is probably not going to get the car
    (bar car)))


(define (bar thing)
  ... but in here, car is what you expect ...)

So, in foo I have bound car to the car of the argument. That's probably terrible style in Scheme, and it means that, in the body of that binding, car probably does not do what you expect when used as a function. But this problem only matters within the lexical scope of the binding of car: it doesn't matter within bar for instance.

Now, in Common Lisp, I can write equivalent code:

(defun foo (x)
  (let ((car (car x)))
    ... (car ...) is fine in here ...
    (bar car)))

(defun bar (thing)
  ... and here ...)

So this is a little bit better, perhaps: within the body of the binding of car it's still fine to use car as a function, and indeed the compiler can make very strong assumptions that car is the function defined by the language and CL has wording in the standard which ensures that this is always true.

And this means that, stylistically, in CL, something like this is probably OK. IN particular I often do things like:

(defmethod manipulate-thing ((thing cons))
  (destructuring-bind (car . cdr) thing
    ...use car & cdr...))

And I think this is fine: in Scheme the equivalent would be horrible.

So that's one reason why a lisp-2 is quite convenient. However there's a much stronger one which doesn't apply to CL but does apply to elisp.

Consider, in elisp, this code:

(defun foo (x)
  (let ((car (car x))
        (cdr (cdr x)))
    (bar car cdr)))

(defun bar (thing-1 thing-2)
  ...)

Now there's a critical thing to know about elisp: by default it is dynamically-scoped. What that means, is that, when bar is called from foo, the bindings of car and car are visible in bar.

So for instance if I redefine bar as:

(defun bar (thing-1 thing-2)
  (cons cdr thing-1))

Then:

ELISP> (foo '(1 . 2))
(2 . 1)

So, now, think of what would happen if elisp was a lisp-1: any function called from foo will find that (car x) does not do what it expects! This is a disaster: it means that if I bind the name of a function – any function, including functions I might not know exist – as a variable, then any code in the dynamic scope of that binding will not do what it should.

So, for a Lisp with dynamic scope, as elisp historically had and still does have by default, being a lisp-1 was a disaster. Well, historically, very many lisp implementations did have dynamic scope (at least in interpreted code: it was common for compiled code to have different scoping rules, and scoping rules were often somewhat incoherent in general). So for those implementations, being a lisp-2 was a really significant advantage. And, of course, once a lot of code which assumed lisp-2-ness existed, it was much easier for languages which aimed at compatibility, such as CL, to remain lisp-2s, even though the advantages in a lexically-scoped language are less clear.


As a note: I have used a lisp, long ago, which was both dynamically-scoped (in the interpreter at least?) and a lisp-1. And I had at least one very bad experience (I think involving needing to hard-reset a multiuser machine which had become catatonic because it was paging so much, which made me unpopular with all the other users) as a result of that.




回答3:


There's two ways a language can be described, one more abstract and another one more specific.

One is illustrated by saying that, in Scheme,

(define (f x) (+ x x x))

causes the evaluation of

(f y)

to be the same as the evaluation of

((lambda (x) (+ x x x)) y)

to be the same as the evaluation of

(let ((x y)) (+ x x x))

to be the same as the evaluation of

(+ y y y)

Notice we haven't said anything about how all this is implemented.


The other way is to refer to the specifics of a particular implementation in the machine.

Thus, for Common Lisp / Emacs Lisp, we begin by talking about actual bona fide memory objects in the language's run-time system, called symbols.

A symbol has this and that -- it is like a structure with several fields which can be filled with some data or be left empty. A symbol's memory representation, an actual structure in memory, has a field called "variable cell", and it has a field called "function cell", and what have you.

When we call (fset 'f (lambda (x) (+ x x x))), we store the result of evaluating the (lambda (x) (+ x x x)) form in the symbol F's "function cell".

If we call (+ f 2) after that, F's "variable cell" is looked into to find out its value as a variable, causing the "undefined variable" error.

If we call (f 2), F's "function cell" is looked into to find out its value as a function (this is what (symbol-function 'f) is also doing). It is found to hold the result of evaluating (lambda (x) (+ x x x)), and so the function call equivalent to ((lambda (x) (+ x x x)) 2) is made.


edit: And if you want to call the function stored in a symbol's "variable cell" as a function, you need to use funcall, which accesses the symbol's value as a variable, and uses it as a function. In Common Lisp (CLISP), another Lisp-2 language:

[14]> (setq a (lambda (x) (+ x x x)))
#<FUNCTION :LAMBDA (X) (+ X X X)>
[15]> (funcall a 3)
9
[16]> (symbol-value 'a)
#<FUNCTION :LAMBDA (X) (+ X X X)>
[17]> (let ((x (symbol-value 'a))) (funcall x 3))
9
[18]> (let ((x 1)) (setf (symbol-function 'x) (symbol-value 'a)) (x 3))
9
  • setf is Common Lisp's "set place" primitive
  • (setq a <val>) is the same as (setf (symbol-value 'a) <val>)
  • symbol-value accesses the symbol's variable cell (its value as a variable)
  • symbol-function accesses the symbol's function cell (its value as a function)
  • (funcall x 3) gets (symbol-value 'x) and calls the result with 3 as an argument
  • (x 3) gets (symbol-function 'x) and calls the result with 3 as an argument


来源:https://stackoverflow.com/questions/60200613/can-emacs-lisp-assign-a-lambda-form-to-a-variable-like-scheme

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!