In Scheme, how do you use lambda to create a recursive function?

16,453

Solution 1

(lambda (x) (x x)) is a function that calls an argument, x, on itself.

The whole block of code you posted results in a function of one argument. You could call it like this:

(((lambda (x) (x x))
  (lambda (fact-gen)
    (lambda (n)
      (if (zero? n)
          1
          (* n ((fact-gen fact-gen) (sub1 n)))))))
 5)

That calls it with 5, and returns 120.

The easiest way to think about this at a high level is that the first function, (lambda (x) (x x)), is giving x a reference to itself so now x can refer to itself, and hence recurse.

Solution 2

The expression (lambda (x) (x x)) creates a function that, when evaluated with one argument (which must be a function), applies that function with itself as an argument.

Your given expression evaluates to a function that takes one numeric argument and returns the factorial of that argument. To try it:

(let ((factorial ((lambda (x) (x x))
                  (lambda (fact-gen)
                    (lambda (n)
                      (if (zero? n)
                          1
                          (* n ((fact-gen fact-gen) (sub1 n)))))))))
  (display (factorial 5)))

There are several layers in your example, it's worthwhile to work through step by step and carefully examine what each does.

Solution 3

Basically what you have is a form similar to the Y combinator. If you refactored out the factorial specific code so that any recursive function could be implemented, then the remaining code would be the Y combinator.

I have gone through these steps myself for better understanding.
https://gist.github.com/z5h/238891

If you don't like what I've written, just do some googleing for Y Combinator (the function).

Solution 4

(lambda (x) (x x)) takes a function object, then invokes that object using one argument, the function object itself.

This is then called with another function, which takes that function object under the parameter name fact-gen. It returns a lambda that takes the actual argument, n. This is how the ((fact-gen fact-gen) (sub1 n)) works.

You should read the sample chapter (Chapter 9) from The Little Schemer if you can follow it. It discusses how to build functions of this type, and ultimately extracting this pattern out into the Y combinator (which can be used to provide recursion in general).

Solution 5

You define it like this:

(let ((fact #f)) 
  (set! fact 
        (lambda (n) (if (< n 2) 1 
                               (* n (fact (- n 1)))))) 
  (fact 5))

which is how letrec really works. See LiSP by Christian Queinnec.


In the example you're asking about, the self-application combinator is called "U combinator",

(let ((U  (lambda (x) (x x)))
      (h  (lambda (g)
            (lambda (n)
              (if (zero? n)
                  1
                  (* n ((g g) (sub1 n))))))))
  ((U h) 5))

The subtlety here is that, because of let's scoping rules, the lambda expressions can not refer to the names being defined.

When ((U h) 5) is called, it is reduced to ((h h) 5) application, inside the environment frame created by the let form.

Now the application of h to h creates new environment frame in which g points to h in the environment above it:

(let ((U  (lambda (x) (x x)))
      (h  (lambda (g)
            (lambda (n)
              (if (zero? n)
                  1
                  (* n ((g g) (sub1 n))))))))
  ( (let ((g h))
      (lambda (n)
              (if (zero? n)
                  1
                  (* n ((g g) (sub1 n)))))) 
    5))

The (lambda (n) ...) expression here is returned from inside that environment frame in which g points to h above it - as a closure object. I.e. a function of one argument, n, which also remembers the bindings for g, h, and U.

So when this closure is called, n gets assigned 5, and the if form is entered:

(let ((U  (lambda (x) (x x)))
      (h  (lambda (g)
            (lambda (n)
              (if (zero? n)
                  1
                  (* n ((g g) (sub1 n))))))))
    (let ((g h))
      (let ((n 5))
              (if (zero? n)
                  1
                  (* n ((g g) (sub1 n)))))))

The (g g) application gets reduced into (h h) application because g points to h defined in the environment frame above the environment in which the closure object was created. Which is to say, up there, in the top let form. But we've already seen the reduction of (h h) call, which created the closure i.e. the function of one argument n, serving as our factorial function, which on the next iteration will be called with 4, then 3 etc.

Whether it will be a new closure object or same closure object will be reused, depends on a compiler. This can have an impact on performance, but not on semantics of the recursion.

Share:
16,453

Related videos on Youtube

NcAdams
Author by

NcAdams

Physics major, amateur programmer, badger mole

Updated on June 03, 2022

Comments

  • NcAdams
    NcAdams almost 2 years

    I'm in a Scheme class and I was curious about writing a recursive function without using define. The main problem, of course, is that you cannot call a function within itself if it doesn't have a name.

    I did find this example: It's a factorial generator using only lambda.

    ((lambda (x) (x x))
     (lambda (fact-gen)
       (lambda (n)
         (if (zero? n)
             1
             (* n ((fact-gen fact-gen) (sub1 n)))))))
    

    But I can't even make sense of the first call, (lambda (x) (x x)): What exactly does that do? And where do you input the value you want to get the factorial of?

    This is not for the class, this is just out of curiosity.

  • Hibou57
    Hibou57 almost 12 years
    Yes, but as let is by it‑self, an alias of a lambda expression, this would still be meaningful to be able to give a lambda expression, a local name to be used inside of it‑self. I believe that's what the OP wanted, but I don't know a way to do it in Scheme.
  • user102008
    user102008 almost 12 years
    @Hibou57: although let is easy to translate to a lambda expression application, letrec is a little harder to do.
  • Will Ness
    Will Ness over 11 years
    and in fact, the last variant is exactly the code in question, uncurried. IOW, the OP code is a curried version of the last variant here, turning one binary function into two nested one-argument lambdas, writing ((f f) x) instead of (f f x).
  • Will Ness
    Will Ness over 4 years
    @Hibou57 (letrec ((name (lambda-expression))) (body)) = (let ((name (Y (lambda (name) (lambda-expression))))) (body)). or something. -- another way is with set!: (let ((name #f)) (set! name (lambda-expression)) (body)).