[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: never use `eval'
From: |
Pascal J. Bourguignon |
Subject: |
Re: never use `eval' |
Date: |
Fri, 17 Jul 2015 03:39:41 +0200 |
User-agent: |
Gnus/5.13 (Gnus v5.13) Emacs/24.3 (gnu/linux) |
Emanuel Berg <embe8573@student.uu.se> writes:
> Remember when I said the byte compiler should be more
> offensive and offer more elaborate style warnings?
>
> And this is a good example of that!
>
> Even in the Emacs help
>
> (describe-function 'eval)
>
> it doesn't say anything about the dangers of the
> "nil environment":
>
(eval FORM &optional LEXICAL)
> Evaluate FORM and return its value.
> If LEXICAL is t, evaluate using lexical scoping.
> LEXICAL can also be an actual lexical environment,
> in the form of an alist mapping symbols to
> their value.
Doesn't seem to work:
(setf lexical-binding t)
(let ((x 42))
(eval 'x t))
Debugger entered--Lisp error: (void-variable x)
(let ((x 42))
(eval 'x nil))
Debugger entered--Lisp error: (void-variable x)
> This book -
>
> @book{artificial-intelligence-and-the-design,
> title = {Artificial Intelligence and the Design of Expert Systems},
> author = {George Luger and William Stubblefield},
> publisher = {Benjamin-Cummings},
> year = 1989,
> ISBN = 0805301399
> }
>
> - has a chapter on LISP. One of the first things
> they mention is `eval', and they mention it in
> connection to the `quote', eval "undoing" quote so you
> get the feeling it is as indispensible. There are
> several examples using it.
Without lexical binding, there's no other environment than the global
environment, so it works better:
(setf lexical-binding nil)
(let ((x 42))
(eval 'x))
--> 42
Old lisp didn't have lexical binding (the famous funarg problem), and
this is why eval could have been more useful then.
But once lexical bindings were introduced in scheme and in Common Lisp,
and now in emacs lisp, you cannot be so lenient with eval.
What this means for the modern (emacs or Common) lisper? That you
should take old lisp code that uses eval with some precautions, such as,
using (setf lexical-binding nil) or making sure that all the variables
are declared special.
See for example, the definition of the DEFINE macro in:
http://www.informatimago.com/develop/lisp/com/informatimago/small-cl-pgms/wang.html
which declares all the variables with DEFPARAMETER, hence declaiming
them to be special and of the dynamic binding persuasion.
> Their other LISP isn't that good either by the way.
> There are several examples where they do (car l)
> several times in one execution branch instead of
> `let'ing it be a local identifier;
It should not be a problem, at that time, or nowadays. In the old days,
car and cdr were quite the optimized instructions: they corresponded to
7090 machine instructions, or to lisp machine instructions, so calling
(car x) several times was probably faster than storing the result in a
variable or even a register (because of the possible register spilling)
and calling it up again.
And nowadays, with honest compilers performing some common subexpression
elimination optimization, it should compile to exactly the same.
In the case of ccl, the code is quite similar, but indeed
(car x) (car x) will hit the (cache) memory each time, recomputing
the offset (assumedly in the pipeline so at zero cost) each time, while
using a temporary variable saves it in a register.
cl-user> (disassemble (compile nil (lambda (x) (list (car x) (car x) (car x)))))
L0
(leaq (@ (:^ L0) (% rip)) (% fn)) ; [0]
(pushq (% rbp)) ; [7]
(movq (% rsp) (% rbp)) ; [8]
(pushq (% save0)) ; [11]
(movq (% arg_z) (% save0)) ; [13]
(pushq (@ 5 (% save0))) ; [16]
(pushq (@ 5 (% save0))) ; [20]
(pushq (@ 5 (% save0))) ; [24]
(movl ($ 24) (% nargs)) ; [28]
(leaq (@ (:^ L53) (% fn)) (% temp2)) ; [33]
(nop) ; [40]
(nop) ; [43]
(jmpq (@ .SPCONSLIST)) ; [46]
L53
(leaq (@ (:^ L0) (% rip)) (% fn)) ; [53]
(popq (% save0)) ; [60]
(leaveq) ; [62]
(retq) ; [63]
nil
cl-user> (disassemble (compile nil (lambda (x) (let ((y (car x))) (list y y
y)))))
L0
(leaq (@ (:^ L0) (% rip)) (% fn)) ; [0]
(pushq (% rbp)) ; [7]
(movq (% rsp) (% rbp)) ; [8]
(pushq (% arg_z)) ; [11]
(pushq (% save0)) ; [12]
(movq (@ 5 (% arg_z)) (% save0)) ; [14]
(pushq (% save0)) ; [18]
(pushq (% save0)) ; [20]
(pushq (% save0)) ; [22]
(movl ($ 24) (% nargs)) ; [24]
(leaq (@ (:^ L45) (% fn)) (% temp2)) ; [29]
(nop) ; [36]
(jmpq (@ .SPCONSLIST)) ; [38]
L45
(leaq (@ (:^ L0) (% rip)) (% fn)) ; [45]
(popq (% save0)) ; [52]
(leaveq) ; [54]
(retq) ; [55]
nil
cl-user> (optimization)
(optimize (safety 0) (debug 0) (speed 3) (space 3) (compilation-speed 1))
But in the case of sbcl, it compiles to the exact same instructions
(the only difference being in the procedure entry, probably allocating
some space on the stack that is left unused in the let case).
diff -c /tmp/b /tmp/a
*** /tmp/b Fri Jul 17 03:29:50 2015
--- /tmp/a Fri Jul 17 03:29:56 2015
***************
*** 1,10 ****
! * (disassemble (compile nil (lambda (x) (list (car x) (car x) (car x)))))
; disassembly for (LAMBDA (X))
488B4AF9 MOV RCX, [RDX-7] ; no-arg-parsing entry
point
- 488B7AF9 MOV RDI, [RDX-7]
- 488B72F9 MOV RSI, [RDX-7]
488BD1 MOV RDX, RCX
49896C2440 MOV [R12+64], RBP
4D8B5C2418 MOV R11, [R12+24]
498D5B30 LEA RBX, [R11+48]
--- 1,10 ----
! * (disassemble (compile nil (lambda (x) (let ((y (car x))) (list y y y)))))
; disassembly for (LAMBDA (X))
488B4AF9 MOV RCX, [RDX-7] ; no-arg-parsing entry
point
488BD1 MOV RDX, RCX
+ 488BF9 MOV RDI, RCX
+ 488BF1 MOV RSI, RCX
49896C2440 MOV [R12+64], RBP
4D8B5C2418 MOV R11, [R12+24]
498D5B30 LEA RBX, [R11+48]
***************
*** 36,38 ****
--- 36,39 ----
488D5B07 LEA RBX, [RBX+7]
EBB3 JMP L0
NIL
+ *
Diff finished. Fri Jul 17 03:30:00 2015
* (disassemble (compile nil (lambda (x) (list (car x) (car x) (car x)))))
; disassembly for (LAMBDA (X))
; 02AB631F: 488B4AF9 MOV RCX, [RDX-7] ; no-arg-parsing
entry point
; 23: 488B7AF9 MOV RDI, [RDX-7]
; 27: 488B72F9 MOV RSI, [RDX-7]
; 2B: 488BD1 MOV RDX, RCX
; 2E: 49896C2440 MOV [R12+64], RBP
; 33: 4D8B5C2418 MOV R11, [R12+24]
; 38: 498D5B30 LEA RBX, [R11+48]
; 3C: 49395C2420 CMP [R12+32], RBX
; 41: 7642 JBE L2
; 43: 49895C2418 MOV [R12+24], RBX
; 48: 498D5B07 LEA RBX, [R11+7]
; 4C: L0: 488BC3 MOV RAX, RBX
; 4F: 488950F9 MOV [RAX-7], RDX
; 53: 4883C010 ADD RAX, 16
; 57: 488940F1 MOV [RAX-15], RAX
; 5B: 488978F9 MOV [RAX-7], RDI
; 5F: 4883C010 ADD RAX, 16
; 63: 488940F1 MOV [RAX-15], RAX
; 67: 488970F9 MOV [RAX-7], RSI
; 6B: 48C7400117001020 MOV QWORD PTR [RAX+1], 537919511
; 73: 49316C2440 XOR [R12+64], RBP
; 78: 7402 JEQ L1
; 7A: CC09 BREAK 9 ; pending
interrupt trap
; 7C: L1: 488BD3 MOV RDX, RBX
; 7F: 488BE5 MOV RSP, RBP
; 82: F8 CLC
; 83: 5D POP RBP
; 84: C3 RET
; 85: L2: 6A30 PUSH 48
; 87: 4C8D1C2570724200 LEA R11, [#x427270] ; alloc_tramp
; 8F: 41FFD3 CALL R11
; 92: 5B POP RBX
; 93: 488D5B07 LEA RBX, [RBX+7]
; 97: EBB3 JMP L0
NIL
* (disassemble (compile nil (lambda (x) (let ((y (car x))) (list y y y)))))
; disassembly for (LAMBDA (X))
; 02B12EAF: 488B4AF9 MOV RCX, [RDX-7] ; no-arg-parsing
entry point
; EB3: 488BD1 MOV RDX, RCX
; EB6: 488BF9 MOV RDI, RCX
; EB9: 488BF1 MOV RSI, RCX
; EBC: 49896C2440 MOV [R12+64], RBP
; EC1: 4D8B5C2418 MOV R11, [R12+24]
; EC6: 498D5B30 LEA RBX, [R11+48]
; ECA: 49395C2420 CMP [R12+32], RBX
; ECF: 7642 JBE L2
; ED1: 49895C2418 MOV [R12+24], RBX
; ED6: 498D5B07 LEA RBX, [R11+7]
; EDA: L0: 488BC3 MOV RAX, RBX
; EDD: 488950F9 MOV [RAX-7], RDX
; EE1: 4883C010 ADD RAX, 16
; EE5: 488940F1 MOV [RAX-15], RAX
; EE9: 488978F9 MOV [RAX-7], RDI
; EED: 4883C010 ADD RAX, 16
; EF1: 488940F1 MOV [RAX-15], RAX
; EF5: 488970F9 MOV [RAX-7], RSI
; EF9: 48C7400117001020 MOV QWORD PTR [RAX+1], 537919511
; F01: 49316C2440 XOR [R12+64], RBP
; F06: 7402 JEQ L1
; F08: CC09 BREAK 9 ; pending
interrupt trap
; F0A: L1: 488BD3 MOV RDX, RBX
; F0D: 488BE5 MOV RSP, RBP
; F10: F8 CLC
; F11: 5D POP RBP
; F12: C3 RET
; F13: L2: 6A30 PUSH 48
; F15: 4C8D1C2570724200 LEA R11, [#x427270] ; alloc_tramp
; F1D: 41FFD3 CALL R11
; F20: 5B POP RBX
; F21: 488D5B07 LEA RBX, [RBX+7]
; F25: EBB3 JMP L0
NIL
*
In the case of emacs, being an implementation targetting a lisp machine
VM, the multiple (cdr x) version looks definitely more optimized (less
VM instructions), as expected.
(disassemble (byte-compile (lambda (x) (let ((y (car x))) (list y y y)))))
byte code:
args: (x)
0 varref x
1 car
2 dup
3 varbind y
4 dup
5 varref y
6 list3
7 unbind 1
8 return
(disassemble (byte-compile (lambda (x) (list (car x) (car x) (car x)))))
byte code:
args: (x)
0 varref x
1 car
2 varref x
3 car
4 varref x
5 car
6 list3
7 return
> and, they speak
> appreciatively of all the different types of recursion
> (tail etc.) but at least so far hasn't mentioned (and
> will they?) anything of the drawbacks of recursion in
> terms of efficiency. (When I started out with Lisp,
> coming "closest" from SML of the languages I did
> then, I did recursion all the time by the way.)
Again, if you have tail call optimization, then there's no recursion
occuring, and efficiency is equivalent to iteration (because in effect,
the compiler generates an iteration).
Of course, CL implementations and emacs lisp don't necessarily implement
TCO, so recursion could be less efficient. But as long as response
times are below the 300 ms threshold, it's ok. ;-)
--
__Pascal Bourguignon__ http://www.informatimago.com/
“The factory of the future will have only two employees, a man and a
dog. The man will be there to feed the dog. The dog will be there to
keep the man from touching the equipment.” -- Carl Bass CEO Autodesk
- Re: How to mapcar or across a list?, (continued)
- Message not available
- Re: How to mapcar or across a list?, Pascal J. Bourguignon, 2015/07/15
- Re: How to mapcar or across a list?, Emanuel Berg, 2015/07/15
- never use `eval' (was: Re: How to mapcar or across a list?), Emanuel Berg, 2015/07/15
- Re: never use `eval' (was: Re: How to mapcar or across a list?), John Mastro, 2015/07/15
- Re: never use `eval' (was: Re: How to mapcar or across a list?), Emanuel Berg, 2015/07/15
- Re: never use `eval' (was: Re: How to mapcar or across a list?), John Mastro, 2015/07/15
- Message not available
- Re: never use `eval' (was: Re: How to mapcar or across a list?), Barry Margolin, 2015/07/15
- Message not available
- Re: never use `eval' (was: Re: How to mapcar or across a list?), Barry Margolin, 2015/07/15
- Re: never use `eval' (was: Re: How to mapcar or across a list?), Emanuel Berg, 2015/07/15
- Re: never use `eval' (was: Re: How to mapcar or across a list?), Emanuel Berg, 2015/07/16
- Message not available
- Re: never use `eval',
Pascal J. Bourguignon <=
- Re: never use `eval', Emanuel Berg, 2015/07/16
- Re: never use `eval', Barry Margolin, 2015/07/17
- Re: never use `eval', Pascal J. Bourguignon, 2015/07/17
- Message not available
- Re: never use `eval', Pascal J. Bourguignon, 2015/07/15
- Re: never use `eval', Emanuel Berg, 2015/07/15
Re: How to mapcar or across a list?, Pascal J. Bourguignon, 2015/07/15
Re: How to mapcar or across a list?, Barry Margolin, 2015/07/15