0

I learned that a lambda and let works as if they already include a progn in their definition. That is (let ((x 1) (y 2) (z 3)) A B C) works as if it had a progn after binding the variables: (let ((x 1) (y 2) (z 3)) (<progn> A B C)). lambda seems to have an "inherent" progn as well, such that (funcall (lambda (x y z) A B C) 1 2 3) is equivalent to (let ((x 1) (y 2) (z 3)) A B C).

My question is:
a) What are the identifies between lambda, let and progn? (i.e. how would you implement lambda and progn just using let?);
b) Is (progn (progn (A) (B) (C))) always equivalent to (progn ((A) (B) (C)))?

1
  • 1
    Too many questions, and not very clear. Have you tried the Elisp manual first?
    – Drew
    Feb 24, 2020 at 17:25

1 Answer 1

2

lambda seems to have an "inherent" progn as well, such that (funcall (lambda (x y z) A B C) 1 2 3) is equivalent to (let ((x 1) (y 2) (z 3)) A B C).

"Equivalent" is a strong word. Calling a lambda with a set of arguments may have a similar effect to evaluating the lambda's body with the appropriate let bindings in place, but their corresponding semantics are quite different.

a) What are the identifies between lambda, let and progn?

Are you asking how they relate/compare to one another?

  • progn is pure sequential evaluation of Lisp forms
  • let is the same but with local variable binding at the start and unbinding at the end
  • lambda evaluates to an anonymous function under dynamic binding, and a closure under lexical-binding

how would you implement lambda and progn just using let?

That depends on what you mean by "implement using let". You can't use let alone to define a function, macro, or special form that will do the same thing as either lambda or progn, but some rewriting is possible in the case of progn:

;; Expressions of this form:
(progn BODY...)
;; can generally be written as:
(let () BODY...)

No such rewrite is possible for anonymous functions and closures in general, because their evaluation semantics and byte-compilation are quite different to those of let.

;; For example, this expression:
(lambda (x) (1+ x))
;; is not, under dynamic binding, the same as:
(list 'lambda '(x) '(1+ x))
;; and not, under lexical binding, the same as:
(list 'closure '(t) '(x) '(+ x y))

b) Is (progn (progn (A) (B) (C))) always equivalent to (progn ((A) (B) (C)))?

No, those two forms are never equivalent.

(progn (progn (A) (B) (C))) is valid Elisp which evaluates (A), (B), and (C) in that order, and returns the result of evaluating (C).

(progn ((A) (B) (C))) is valid if and only if (A) is a valid (lambda ...) expression, as the first element of an evaluated list must already be (not evaluate to) a valid function object. In that case, (B) and (C) are evaluated and passed as arguments to the anonymous function/closure (A), whose result is then returned by progn.

(I have the faint impression that expressions of the form ((lambda ...)) are meant to eventually be outlawed in Elisp, but I'm not sure.)

Update from Comments

In asking b), I meant to ask about the semantics and evaluation of the "implicit" progn in let/lambda and an explicit progn in their body, i.e. how does (let () (progn BODY...)) work?

(let () (progn BODY...)) is effectively the same as (progn (progn BODY...)), which is effectively the same as (progn BODY...), i.e. adding an explicit progn where there is already an implicit one is practically redundant/idempotent.

The special form let is actually implemented in terms of progn; see src/eval.c:

DEFUN ("let", Flet, Slet, 1, UNEVALLED, 0,
       doc: /* Bind variables according to VARLIST then eval BODY.
The value of the last form in BODY is returned.
Each element of VARLIST is a symbol (which is bound to nil)
or a list (SYMBOL VALUEFORM) (which binds SYMBOL to the value of VALUEFORM).
All the VALUEFORMs are evalled before any symbols are bound.
usage: (let VARLIST BODY...)  */)
  (Lisp_Object args)
{
  /* Bind local variables according to first argument.  */
  Lisp_Object varlist = XCAR (args);
  ptrdiff_t count = SPECPDL_INDEX ();
  /* ...magic happens here...  */

  /* Pass remaining arguments to 'progn'.  */
  Lisp_Object elt = Fprogn (XCDR (args));

  /* Unbind local variables; return result of 'progn'.  */
  return SAFE_FREE_UNBIND_TO (count, elt);
}

Note that idiomatic Elisp avoids redundant nesting of progn where possible.

6
  • 1
    Thanks for the in-depth answer! In asking b), I meant to ask about the semantics and evaluation of the "implicit" progn in let/lambda and an explicit progn in their body, i.e. how does (let () (progn BODY...)) work?
    – hyiltiz
    Feb 24, 2020 at 18:00
  • Very informative and helpful.
    – RichieHH
    Feb 24, 2020 at 18:04
  • 1
    @hyiltiz Thanks, I've updated the answer accordingly.
    – Basil
    Feb 24, 2020 at 19:03
  • A good answer worths its weight in gold. Such good quality answer. Thanks!
    – hyiltiz
    Feb 24, 2020 at 19:38
  • Is (funcall (lambda (arg1 arg2...) BODY...) val1 val2...) equivalent to (let ((arg1 val1) (arg2 val2)...) BODY...)?
    – Tobias
    Feb 24, 2020 at 20:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.