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

后端 未结 8 979
走了就别回头了
走了就别回头了 2020-12-03 01:17

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 itse

相关标签:
8条回答
  • 2020-12-03 01:34

    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.

    A little off-topic here, but seeing the above statements I just wanted to let you know that "without using define" does not mean "doesn't have a name". It is possible to give something a name and use it recursively in Scheme without define.

    (letrec
      ((fact
         (lambda (n)
           (if (zero? n)
             1
             (* n (fact (sub1 n)))))))
      (fact 5))
    

    It would be more clear if your question specifically says "anonymous recursion".

    0 讨论(0)
  • 2020-12-03 01:35

    I found this question because I needed a recursive helper function inside a macro, where one can't use define.

    One wants to understand (lambda (x) (x x)) and the Y-combinator, but named let gets the job done without scaring off tourists:

     ((lambda (n)
       (let sub ((i n) (z 1))
         (if (zero? i)
             z
             (sub (- i 1) (* z i)) )))
     5 )
    

    One can also put off understanding (lambda (x) (x x)) and the Y-combinator, if code like this suffices. Scheme, like Haskell and the Milky Way, harbors a massive black hole at its center. Many a formerly productive programmer gets entranced by the mathematical beauty of these black holes, and is never seen again.

    0 讨论(0)
  • 2020-12-03 01:49

    I like this question. 'The scheme programming language' is a good book. My idea is from Chapter 2 of that book.

    First, we know this:

    (letrec ((fact (lambda (n) (if (= n 1) 1 (* (fact (- n 1)) n))))) (fact 5))
    

    With letrec we can make functions recursively. And we see when we call (fact 5), fact is already bound to a function. If we have another function, we can call it this way (another fact 5), and now another is called binary function (my English is not good, sorry). We can define another as this:

    (let ((another (lambda (f x) .... (f x) ...))) (another fact 5))
    

    Why not we define fact this way?

    (let ((fact (lambda (f n) (if (= n 1) 1 (* n (f f (- n 1))))))) (fact fact 5))
    

    If fact is a binary function, then it can be called with a function f and integer n, in which case function f happens to be fact itself.

    If you got all the above, you could write Y combinator now, making a substitution of let with lambda.

    0 讨论(0)
  • 2020-12-03 01:50

    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.

    0 讨论(0)
  • 2020-12-03 01:54

    (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).

    0 讨论(0)
  • 2020-12-03 01:58

    (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.

    0 讨论(0)
提交回复
热议问题