3

I am currently, going through this great article on Y-combinator by Mike Vanier. Along the explanation the following line is dropped:

It turns out that any let expression can be converted into an equivalent lambda expression using this equation:

(let ((x <expr1>)) <expr2>) ==> ((lambda (x) <expr2>) <expr1>)

The article illustrates this statement by converting:

(define (part-factorial self)
  (let ((f (self self)))
    (lambda (n)
      (if (= n 0)
        1
        (* n (f (- n 1)))))))

to:

(define (part-factorial self)
  ((lambda (f)
    (lambda (n)
      (if (= n 0)
        1
        (* n (f (- n 1))))))
  (self self)))

Now, I understand how and why two code snippets above are identical, though I can't get my head around the fact that general equation for converting let to lambda is:

(let ((x <expr1>)) <expr2>)
==> ((lambda (x) <expr2>) <expr1>)

I'd appreciate the elaborate explanation.

  • [Related](https://stackoverflow.com/questions/16698548/how-to-express-let-as-a-lambda-expression-not-the-regular-let). – user202729 Mar 21 '18 at 10:48
  • re: the article's epigraph, apparently it was John von Neumann who said, "in mathematics you don't understand things. You just get used to them". – Will Ness Mar 21 '18 at 17:19

3 Answers3

5

I've ended up figuring it out myself =).

The point that I was missing is that in:

(let ((x <expr1>)) <expr2>)
==> ((lambda (x) <expr2>) <expr1>)

x is present, somewhere, inside <expr2>, so it's more like:

(let ((x <expr1>)) <expr-containing-x>)
==> ((lambda (x) <expr-containing-x>) <expr1>)

With that being said, if we will substitute x for f in:

(define (part-factorial self)
  (let ((f (self self)))
    (lambda (n)
      (if (= n 0)
        1
        (* n (f (- n 1)))))))

as well as in:

(define (part-factorial self)
  ((lambda (f)
    (lambda (n)
      (if (= n 0)
        1
        (* n (f (- n 1))))))
  (self self)))

and will highlight x, <expr1> and <expr2> with different colors, the conversion formula should become clear then:

x, <expr1> and <expr2> highlighted

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • 1
    `x` doesn't need to be used anywhere - `(let ((x 0)) 99)` is the same as `((lambda (x) 99) 0)`. – molbdnilo Mar 21 '18 at 15:32
  • 1
    for me, it's the other way around: `(x => ...x...) val` *"means"* `let {x = val} in ...x... ` which is self-explanatory *("open up a fresh environment frame; set up a place called `x` in it; put the value of `val` in that place; proceed...").* – Will Ness Mar 22 '18 at 19:26
  • 2
    Examples should use more meaningful names. Would you have been confused by this: `((lambda (param) body ...) arg) <--> (let ((param arg)) body ...)`? – Kaz Apr 16 '18 at 00:40
5

You should imagine having a very little lisp language that has lambda but not let. You want to do:

(let ((nsq (square n)))
  (+ nsq nsq))

You know that nsq is a new variable and that the body of the let could be made a function:

(lambda (nsq) (+ nsq nsq))

Then you need to use that to get the same value:

((lambda (nsq) (+ nsq nsq)) (square n))

Imagine that your simple scheme has macros and thus, you implement as let:

(define-syntax let
  (syntax-rules ()
    [(let ((binding value) ...)
       body ...)
     ((lambda (binding ...)
        body ...)
      value ...)]))

Note, that in many implementations this actually happens exactly this way.

Sylwester
  • 47,942
  • 4
  • 47
  • 79
3

let lets you open a new environment in which variables are available. In programming language terms we say it "opens a new frame".

When you write (let ((x 42)) <body>) you create a frame in which x is available inside <body>, and assign it the value 42.

Well, there is another tool that lets you open new frames. In fact, it's usually the basic brick with which you can build more abstract constructs: it is called lambda.

lambda opens a new frame in which its arguments are available to its body. When you write (lambda (x) <body>) you make x available to the <body> of the function.

The only difference between lambda and let is that let immediately assigns a value to x, while lambda awaits the value as an argument.

Therefore, if you want to wrap a <body> with a directly assigned value using lambda, you just have to pass that value!

((lambda (x) <body>) 42)

Which makes it exactly equivalent to:

(let ((x 42)) <body>)
Zoé Martin
  • 1,887
  • 1
  • 16
  • 28