Skip to content

Instantly share code, notes, and snippets.

@LaloHao
Last active October 11, 2020 03:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save LaloHao/fd6499b68cc98cf440aad6447ebd9b89 to your computer and use it in GitHub Desktop.
Save LaloHao/fd6499b68cc98cf440aad6447ebd9b89 to your computer and use it in GitHub Desktop.
Why is a Common-Lisp Lambda expression a valid function name?
<i>[hao@wendy:~]$ ecl
;;; Loading "/nix/store/j48bf40ssnzgil3qmdc759y0navk079d-ecl-readline/lib/init.lsp"
n;;; Loading "/nix/store/j48bf40ssnzgil3qmdc759y0navk079d-ecl-readline/lib/ecl-readline.fas"
ix-;;; Loading "/nix/store/j48bf40ssnzgil3qmdc759y0navk079d-ecl-readline/lib/ecl-completions.fas"
sECL (Embeddable Common-Lisp) 16.1.3 (git:UNKNOWN)
Copyright (C) 1984 Taiichi Yuasa and Masami Hagiya
Copyright (C) 1993 Giuseppe Attardi
Copyright (C) 2000 Juan J. Garcia-Ripoll
Copyright (C) 2016 Daniel Kochmanski
ECL is free software, and you are welcome to redistribute it
under certain conditions; see file 'Copyright' for details.
Type :h for Help.
Top level in: #<process TOP-LEVEL>.
h+CL-USER[1]> (macroexpand '(lambda (a) (+a 12)))
#'(LAMBDA (A) (+A 12))
T
+CL-USER[2]> (equal (macroexpand '(lambda (a) (+ a 12))) '(lambda (a) (+ a 12)))
NIL
+CL-USER[3]> (map 'list #'type-of (list (macroexpand '(lambda (a) (+ a 12))) '(lambda (a) (+ a 12))))
(CONS CONS)
+CL-USER[4]> (map 'list #'functionp (list (macroexpand '(lambda (a) (+ a 12))) '(lambda (a) (+ a 12))))
(NIL NIL)
+CL-USER[5]> (map 'list #'car (list (macroexpand '(lambda (a) (+ a 12))) '(lambda (a) (+ a 12))))
#'LAMBDA
+CL-USER[6]> (mapcar #'car (list (macroexpand '(lambda (a) (+ a 12))) '(lambda (a) (+ a 12))))
#'LAMBDA
+CL-USER[7]> (list (macroexpand '(lambda (a) (+ a 12))) '(lambda (a) (+ a 12))))
(#'(LAMBDA (A) (+ A 12)) (LAMBDA (A) (+ A 12)))
;;; Warning: Ignoring an unmatched right parenthesis.
+CL-USER[8]> (ignore-errors (list *print-escape* *print-readably* *print-case* *print-circle* *print-level* *print-length* *print-pretty*))
(T NIL :UPCASE NIL NIL NIL T)
+CL-USER[9]> (let ((*print-readably* t)) (list (macroexpand '(lambda (a) (+ a 12))) '(lambda (a) (+ a 12)))))
(#'(LAMBDA (A) (+ A 12)) (LAMBDA (A) (+ A 12)))
;;; Warning: Ignoring an unmatched right parenthesis.
+CL-USER[10]> (setq *print-pretty* nil)
NIL
+CL-USER[11]> (setq the-funs (list (macroexpand '(lambda (a) (+ a 12))) '(lambda (a) (+ a 12))))
(#'(LAMBDA (A) (+ A 12)) (LAMBDA (A) (+ A 12)))
+CL-USER[12]> *print-pretty*
NIL
+CL-USER[13]> (format "~S" the-funs)
Condition of type: FORMAT-ERROR
Error in format: Cannot output to a non adjustable string.
(#'(LAMBDA (A) (+ A 12)) (LAMBDA (A) (+ A 12)))
^
Available restarts:
1. (RESTART-TOPLEVEL) Go back to Top-Level REPL.
Broken at SI:BYTECODES. [Evaluation of: (FORMAT "~S" THE-FUNS)] In: #<process TOP-LEVEL>.
+** BREAK [LEVEL 2]> :r1
+CL-USER[14]> (format t "~{~S~}~%" the-funs)
#'(LAMBDA (A) (+ A 12))(LAMBDA (A) (+ A 12))
NIL
+CL-USER[15]> (princ (car the-funs))
#'(LAMBDA (A) (+ A 12))
#'(LAMBDA (A) (+ A 12))
+CL-USER[16]> *print-readably*
NIL
+CL-USER[17]> (setq *print-readably* t)
T
+CL-USER[18]> (princ (car the-funs))
#'(LAMBDA (A) (+ A 12))
#'(LAMBDA (A) (+ A 12))
+CL-USER[19]> (print (car the-funs))
#'(LAMBDA (A) (+ A 12))
#'(LAMBDA (A) (+ A 12))
+CL-USER[20]> (prin1 (car the-funs))
#'(LAMBDA (A) (+ A 12))
#'(LAMBDA (A) (+ A 12))
+CL-USER[21]> (type-of (car the-funs)))
CONS
;;; Warning: Ignoring an unmatched right parenthesis.
+CL-USER[22]> (car (car the-funs))
FUNCTION
+CL-USER[23]> (values #1=(cadr the-funs) (type-of #1#))
(LAMBDA (A) (+ A 12))
CONS
+CL-USER[24]> (values #1=(car (cadr the-funs)) (type-of #1#))
LAMBDA
SYMBOL
+CL-USER[25]> *print-readably*
T
+CL-USER[26]> *print-pretty*
NIL
+CL-USER[27]> (mapcar #'prin1 the-funs)
#'(LAMBDA (A) (+ A 12))(LAMBDA (A) (+ A 12))
(#'(LAMBDA (A) (+ A 12)) (LAMBDA (A) (+ A 12)))
+CL-USER[28]> (mapcar #'pprint the-funs)
#'(LAMBDA (A) (+ A 12))
(LAMBDA (A) (+ A 12))
(NIL NIL)
+CL-USER[29]> (mapcar #'print-unreadable-object the-funs)
Condition of type: UNDEFINED-FUNCTION
The function PRINT-UNREADABLE-OBJECT is undefined.
Available restarts:
1. (RESTART-TOPLEVEL) Go back to Top-Level REPL.
Broken at SI:BYTECODES. [Evaluation of: (MAPCAR #'PRINT-UNREADABLE-OBJECT THE-FUNS)] In: #<process TOP-LEVEL>.
+** BREAK [LEVEL 2]> :r1
+CL-USER[30]> (mapcar #'print-unreadable-object the-funs)
print-unreadable-object
+CL-USER[30]> (mapcar (lambda (f) (format nil "~S~%" f)) the-funs)
("#'(LAMBDA (A) (+ A 12))
" "(LAMBDA (A) (+ A 12))
")
+CL-USER[31]> (mapcar (lambda (f) (format nil "~A~%" f)) the-funs)
("#'(LAMBDA (A) (+ A 12))
" "(LAMBDA (A) (+ A 12))
")
+CL-USER[32]> (let (*print-pretty*) (mapcar #'print the-funs))
#'(LAMBDA (A) (+ A 12))
(LAMBDA (A) (+ A 12))
(#'(LAMBDA (A) (+ A 12)) (LAMBDA (A) (+ A 12)))
+CL-USER[33]> (with-standard-io-syntax (mapcar #'print the-funs))
#'(LAMBDA (A) (+ A 12))
(LAMBDA (A) (+ A 12))
(#'(LAMBDA (A) (+ A 12)) (LAMBDA (A) (+ A 12)))
+CL-USER[34]>
Condition of type: SEGMENTATION-VIOLATION
Detected access to an invalid or protected memory address.
Available restarts:
1. (CONTINUE) Ignore signal
2. (RESTART-TOPLEVEL) Go back to Top-Level REPL.
Broken at SI:BYTECODES. [Evaluation of: (SI:TOP-LEVEL T)] In: #<process TOP-LEVEL>.
+** BREAK [LEVEL 2]> :r2
+CL-USER[35]> (quit)
<i>[hao@wendy:~]$ sbcl
This is SBCL 2.0.0.nixos, an implementation of ANSI Common Lisp.
More information about SBCL is available at <http://www.sbcl.org/>.
SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses. See the CREDITS and COPYING files in the
distribution for more information.
* (setq fs (list (macroexpand '(lambda (x) (1+ x))) '(lambda (x) (1+ x))))
; in: SETQ FS
; (SETQ FS (LIST (MACROEXPAND '(LAMBDA # #)) '(LAMBDA (X) (1+ X))))
;
; caught WARNING:
; undefined variable: COMMON-LISP-USER::FS
;
; compilation unit finished
; Undefined variable:
; FS
; caught 1 WARNING condition
(#'(LAMBDA (X) (1+ X)) (LAMBDA (X) (1+ X)))
* (mapcar #'print the-funs)
debugger invoked on a UNBOUND-VARIABLE in thread
#<THREAD "main thread" RUNNING {10005184C3}>:
The variable THE-FUNS is unbound.
Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.
restarts (invokable by number or by possibly-abbreviated name):
0: [CONTINUE ] Retry using THE-FUNS.
1: [USE-VALUE ] Use specified value.
2: [STORE-VALUE] Set specified value and use it.
3: [ABORT ] Exit debugger, returning to top level.
(SB-INT:SIMPLE-EVAL-IN-LEXENV THE-FUNS #<NULL-LEXENV>)
0] :r1
:R1
0] 3
* (mapcar #'print fs)
#'(LAMBDA (X) (1+ X))
(LAMBDA (X) (1+ X))
(#'(LAMBDA (X) (1+ X)) (LAMBDA (X) (1+ X)))
* (let (*print-pretty*) (print (lambda (x) (1+ x))))
#<FUNCTION (LAMBDA (X)) {52B1CABB}>
#<FUNCTION (LAMBDA (X)) {52B1CABB}>
* (let (*print-pretty*) (print ' (lambda (x) (1+ x))))
(LAMBDA (X) (1+ X))
(LAMBDA (X) (1+ X))
* (mapcar #'functionp fs)
(NIL NIL)
* (mapcar #'type-of fs)
(CONS CONS)
* (mapcar #'car fs)
#'LAMBDA
* (values (car (car fs)) (car (cdr fs)))
FUNCTION
(LAMBDA (X) (1+ X))
* (defun walk-tree (fun tree)
(subst-if t
(constantly nil)
tree
:key fun))
WALK-TREE
* (walk-tree 'print (lambda (x) (1+ x)))
#<FUNCTION (LAMBDA (X)) {52B1CD5B}>
#<FUNCTION (LAMBDA (X)) {52B1CD5B}>
* (walk-tree 'print '(lambda (x) (1+ x))))
(LAMBDA (X) (1+ X))
LAMBDA
((X) (1+ X))
(X)
X
NIL
((1+ X))
(1+ X)
1+
(X)
X
NIL
NIL
(LAMBDA (X) (1+ X))
*
debugger invoked on a SB-INT:SIMPLE-READER-ERROR in thread
#<THREAD "main thread" RUNNING {10005184C3}>:
unmatched close parenthesis
Stream: #<SYNONYM-STREAM :SYMBOL SB-SYS:*STDIN* {1000025913}>
Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.
restarts (invokable by number or by possibly-abbreviated name):
0: [ABORT] Exit debugger, returning to top level.
(SB-IMPL::READ-RIGHT-PAREN #<SYNONYM-STREAM :SYMBOL SB-SYS:*STDIN* {1000025913}> #<unused argument>)
0] 0
* (defun walk-tree-atoms (fun tree)
(tree-equal tree tree
:test (lambda (element-1 element-2)
(declare (ignore element-2))
(funcall fun element-1)
t)))
WALK-TREE-ATOMS
* (walk-tree-atoms 'print (lambda (x) (1+ x)))
#<FUNCTION (LAMBDA (X)) {52B1CF9B}>
T
* (walk-tree-atoms 'print '(lambda (x) (1+ x)))
LAMBDA
X
NIL
1+
X
NIL
NIL
T
* (quit)

I think the problem lies in the lisp printer, the implementation is showing us exactly the same thing for both things, while internally it seems it's really a list vs a function.

The usual and confusing pattern shows:

<i>[hao@wendy:~]$ ecl
;;; Loading "/nix/store/j48bf40ssnzgil3qmdc759y0navk079d-ecl-readline/lib/init.lsp"
n;;; Loading "/nix/store/j48bf40ssnzgil3qmdc759y0navk079d-ecl-readline/lib/ecl-readline.fas"
ix-;;; Loading "/nix/store/j48bf40ssnzgil3qmdc759y0navk079d-ecl-readline/lib/ecl-completions.fas"
sECL (Embeddable Common-Lisp) 16.1.3 (git:UNKNOWN)
Copyright (C) 1984 Taiichi Yuasa and Masami Hagiya
Copyright (C) 1993 Giuseppe Attardi
Copyright (C) 2000 Juan J. Garcia-Ripoll
Copyright (C) 2016 Daniel Kochmanski
ECL is free software, and you are welcome to redistribute it
under certain conditions; see file 'Copyright' for details.
Type :h for Help.  
Top level in: #<process TOP-LEVEL>.
h+CL-USER[1]> (macroexpand '(lambda (a) (+a 12)))
#'(LAMBDA (A) (+A 12))
T
+CL-USER[2]> (equal (macroexpand '(lambda (a) (+ a 12))) '(lambda (a) (+ a 12)))
NIL
+CL-USER[3]> (map 'list #'type-of (list (macroexpand '(lambda (a) (+ a 12))) '(lambda (a) (+ a 12))))
(CONS CONS)
+CL-USER[4]> (map 'list #'functionp (list (macroexpand '(lambda (a) (+ a 12))) '(lambda (a) (+ a 12))))
(NIL NIL)
+CL-USER[5]> (map 'list #'car (list (macroexpand '(lambda (a) (+ a 12))) '(lambda (a) (+ a 12))))
#'LAMBDA

And it seems the only difference is just a hashquote?

+CL-USER[8]> (ignore-errors (list *print-escape* *print-readably* *print-case* *print-circle* *print-level* *print-length* *print-pretty*))
(T NIL :UPCASE NIL NIL NIL T)
+CL-USER[9]> (let ((*print-readably* t)) (list (macroexpand '(lambda (a) (+ a 12))) '(lambda (a) (+ a 12)))))
(#'(LAMBDA (A) (+ A 12)) (LAMBDA (A) (+ A 12)))
;;; Warning: Ignoring an unmatched right parenthesis.
+CL-USER[10]> (setq *print-pretty* nil)
NIL
+CL-USER[11]> (setq the-funs (list (macroexpand '(lambda (a) (+ a 12))) '(lambda (a) (+ a 12))))
(#'(LAMBDA (A) (+ A 12)) (LAMBDA (A) (+ A 12)))
+CL-USER[12]> *print-pretty* 
NIL

I was trying to force the printer to explicitly show the (function (lambda ...)) part of the definition [22], but i cant remember the last time i was doing exactly the same thing trying to find out why the subtetly arised just when starting to gain trust on lisp

+CL-USER[21]> (type-of (car the-funs)))
CONS
;;; Warning: Ignoring an unmatched right parenthesis.
+CL-USER[22]> (car (car the-funs))
FUNCTION

What we are seing actually is a list/cons (below) and a function (above)

+CL-USER[23]> (values #1=(cadr the-funs) (type-of #1#))
(LAMBDA (A) (+ A 12))
CONS

And maybe the difference isn't just visually a printed character; Here's SBCL:

<i>[hao@wendy:~]$ sbcl
This is SBCL 2.0.0.nixos, an implementation of ANSI Common Lisp.
More information about SBCL is available at <http://www.sbcl.org/>.

SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses.  See the CREDITS and COPYING files in the
distribution for more information.
* (setq fs (list (macroexpand '(lambda (x) (1+ x))) '(lambda (x) (1+ x))))
; in: SETQ FS
;     (SETQ FS (LIST (MACROEXPAND '(LAMBDA # #)) '(LAMBDA (X) (1+ X))))
; 
; caught WARNING:
;   undefined variable: COMMON-LISP-USER::FS
; 
; compilation unit finished
;   Undefined variable:
;     FS
;   caught 1 WARNING condition
(#'(LAMBDA (X) (1+ X)) (LAMBDA (X) (1+ X)))

* (let (*print-pretty*) (print (lambda (x) (1+ x))))

#<FUNCTION (LAMBDA (X)) {52B1CABB}> 
#<FUNCTION (LAMBDA (X)) {52B1CABB}>
* (let (*print-pretty*) (print ' (lambda (x) (1+ x))))

(LAMBDA (X) (1+ X)) 
(LAMBDA (X) (1+ X))

The quote showed more explicitly here

#<FUNCTION (LAMBDA (X)) {52B1CABB}> 
(LAMBDA (X) (1+ X))

The tree, and the types are somewhat similar to what ECL said.

* (mapcar #'functionp fs) 
(NIL NIL)
* (mapcar #'type-of fs)
(CONS CONS)
* (mapcar #'car fs)
#'LAMBDA
* (values (car (car fs)) (car (cdr fs)))
FUNCTION
(LAMBDA (X) (1+ X))

Finally, (dont be taking) a random tree walk through the forest confirms that we can walk the list, but the function is just closured?

* (defun walk-tree (fun tree)
  (subst-if t
            (constantly nil)
            tree
            :key fun))
WALK-TREE
* (walk-tree 'print (lambda (x) (1+ x)))

#<FUNCTION (LAMBDA (X)) {52B1CD5B}> 
#<FUNCTION (LAMBDA (X)) {52B1CD5B}>
* (walk-tree 'print '(lambda (x) (1+ x))))

(LAMBDA (X) (1+ X)) 
LAMBDA 
((X) (1+ X)) 
(X) 
X 
NIL 
((1+ X)) 
(1+ X) 
1+ 
(X) 
X 
NIL 
NIL 
(LAMBDA (X) (1+ X))
* (defun walk-tree-atoms (fun tree)
  (tree-equal tree tree
              :test (lambda (element-1 element-2)
                      (declare (ignore element-2))
                      (funcall fun element-1)
                      t)))
WALK-TREE-ATOMS
* (walk-tree-atoms 'print (lambda (x) (1+ x)))

#<FUNCTION (LAMBDA (X)) {52B1CF9B}> 
T
* (walk-tree-atoms 'print '(lambda (x) (1+ x)))

LAMBDA 
X 
NIL 
1+ 
X 
NIL 
NIL 
T
* (quit)

<i>[hao@wendy:~]$ 

The walking functions were taken from LispTips, which sadly seems to be taken down, i just had a flashback when i read this, you can still look the archive here: https://web.archive.org/web/20191204131626/https://lisptips.com/post/43404489000/the-tree-walkers-of-cl

You can also look at the whole session, i pasted it in a gist: https://gist.github.com/LaloHao/fd6499b68cc98cf440aad6447ebd9b89

@LaloHao
Copy link
Author

LaloHao commented Oct 11, 2020

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment