3

So let's say I want to call some function. If I've defined the function with a defun, I just use the name of the function at the beginning of a list followed by it's arguments like so (I will be using "=>" in examples to show the output of entering the code into the CLisp REPL):

(defun f (a) (lambda (b) (+ a b))) => F
(f 12) => #<FUNCTION :LAMBDA (B) (+ A B)>

Simple enough. The first element of the list must be the name of a function, special operator, or macro. Except, the following is also valid:

((lambda (a) (+ a 12)) 1) => 13

A quick google-search reveals that LAMBDA is both a symbol and a macro. Trying to expand the macro yeilds:

(macroexpand '(lambda (a) (+ a 12))) => #'(LAMBDA (A) (+ A 12))

This is not helpful. I have no way to differentiate between the macro LAMBDA and the symbol LAMBDA, and I'm totally unclear as to why I can use a lambda expression as a function-name but not, say, #'f, which, to my knowledge, should evaluate to a valid function-designator for the function F in the same way that #'(LAMBDA (A) (+ A 12)) does and yet:

(#'f 12) => *** - EVAL: #'F is not a function name; try using a symbol instead

Is LAMBDA a special exception to the otherwise hard-set rule that the first element of an evaluated expression must be the name of some operation, or is there some more consistent ruleset that I'm misunderstanding?

Charlim
  • 521
  • 4
  • 12

3 Answers3

7

Lambda expressions and function names

A lambda expression is not a function name. function names in Common Lisp are defined to be either symbols or (setf symbol). A lambda expression is basically built-in syntax to describe an anonymous function.

Note that lambda expressions on their own are not meaningful in Common Lisp. They are only appearing in a lambda form (see below) and inside a form with the special operator function.

Lists as Forms

Is LAMBDA a special exception to the otherwise hard-set rule that the first element of an evaluated expression must be the name of some operation, or is there some more consistent ruleset that I'm misunderstanding?

The Common Lisp specification defines that there are only four list-based forms. A form is a valid Lisp piece of code.

  • special forms (the form begins with a special operator)
  • macro forms (the form begins with a macro operator)
  • function forms (the form begins with a function operator)
  • lambda forms (the form begins with a lambda expression)

See the Common Lisp HyperSpec: Conses as Forms.

Note that there is no mechanism in Common Lisp to extend this. There are only these four types of list-based forms. One could think of extensions: arrays as functions, CLOS objects as functions, different types of functions like fexprs, variables, ... None of those are supported by Common Lisp syntax for list-based forms and there is no portable mechanism to add those.

LAMBDA

LAMBDA has two different purposes in Common Lisp:

  • it's the head of a lambda expression.
  • as a macro LAMBDA. This expands (lambda ....) into (function (lambda ....))

The macro LAMBDA was added to Common Lisp after the first language definition CLtL1 as a convenience to be able to write (lambda (x) x) instead of (function (lambda (x) x)) or #'(lambda (x) x). Thus it's an abbreviation for the function special operator form and makes code look a bit simpler and more Scheme-like.

Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
  • In addition, adding LAMBDA as a macro made ISLisp a subset of Common Lisp. Not surprising that the showrunner of ISLisp, Kent Pitman, also proposed LAMBDA macros. – John Cowan Jan 24 '20 at 21:00
1

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

Eduardo V.
  • 21
  • 2
0

One can imagine the following translation happening to evaluate a function (ie the car does not name a macro or special operator) expression:

(foo arg1 arg2 ...)
~~~>
(funcall (function foo) arg1 arg2 ...)

And understand function as the special operator that translates the thing that goes in the first element of an expression into an actual function operator one can call. It is function which converts (mostly at compile time) a lambda expression into a closure that may be called.

Finally note that #'foo is shorthand for (function foo).

This is not how things actually work, in practice or in specification, as (function #'(setf foo)) is perfectly valid and may evaluate to a function of one exists, but ((setf foo) arg1 arg2 ...) cannot be a valid function call.

This does however explain why an expression like (#'f x) is invalid and the reason is that (function (function f)) is not a valid expression.

Dan Robertson
  • 4,315
  • 12
  • 17
  • I think it is slightly misleading: it tries to understand a Lisp-2 by Lisp-1 mechanisms - like getting a function and passing it in the value space to a calling mechanism. – Rainer Joswig Jan 17 '19 at 11:38