Here is the delta function: δ = (lambda (f) (f f)).  Delta
  takes a function and tail calls that function on itself.  What
  happens if we apply the delta function to itself?  Since the delta
  function is the argument, it is tail called and applied to itself.
  Which leads again to itself being tail called and applied to
  itself.  We have a situation of infinite regression:  the output
  of (δ δ) ends up being a restatement of the
  output of (δ δ).  Now in this case, regression
  is infinite and there is no base case, but imagine that somehow
  there were a base case, or that somehow we identified a value that
  an infinite regression equated to.  Then each stage of the infinite
  regression just replicates the previous stage exactly.  It is like
  having a perfectly silvered mirror:  it just replicates the image
  presented to it exactly.  By calling delta on delta, we've arranged our perfectly silvered
  mirror to reflect an image of itself.  This leads to the “infinite
  hall of mirrors” effect.
So let's tweak the delta function so that instead of perfectly
  replicating the infinite regression, it applies a
  function g around the replication: (lambda (f) (g (f
  f))).  If we apply this modified delta function to itself,
  each expansion of the infinite regression ends up wrapping an
  application of the g around it: (g (f f)) = (g (g
  (f f))) = (g (g (g (f f)))) = (g (g (g (g … )))).  So
  our modified delta function gives us a nested infinite regression of
  applications of g.  This is like our perfectly silvered
  mirror, but now the reflected image isn't mirrored exactly:  we've
  put a frame on the mirror.  When we arrange for the mirror to
  reflect itself, each nested reflection also has an image of the
  frame around the reflection, so we get a set of infinitely nested
  frames.
An infinite regression of (g (g (g (g … )))) is
  confusing.  What does it mean?  We can untangle this by unwrapping
  an application.  (g (g (g (g … )))) is just a
  call to g.  The argument to that call is weird, but
  we're just calling (g <something>).  The
  result of the infinite regression (g (g (g (g …
  )))) is simply the result of the outermost call
  to g.  We can use this to build a recursive function.
  
;; If factorial = (g (g (g (g … )))), then
;; factorial = (g factorial), where
(defun g (factorial)
  (lambda (x)
    (if (zerop x)
        1
        (* x (funcall factorial (- x 1))))))The value returned
by an inner invocation of g is the value that will be
funcalled in the altenative branch of the conditional.
Y is defined thus:
Y = λg.(λf.g(f f))(λf.g(f f))
A straightforward implementation attempt would be;; Non working y operator
(defun y (g)
  (let ((d (lambda (f) (funcall g (funcall f f)))))
    (funcall d d)))but since lisp is a
call-by-value language, it will attempt to (funcall f f)
before funcalling g, and this will cause runaway
recursion.  We can avoid the runaway recursion by delaying
the (funcall f f) with a strategically placed
thunk;; Call-by-value y operator
;; returns (g (lambda () (g (lambda () (g (lambda () … ))))))
(defun y (g)
  (let ((d (lambda (f) (funcall g (lambda () (funcall f f))))))
    (funcall d d)))Since the recursion is
now wrapped in a thunk, we have to funcall the thunk to force the
recursive call.  Here is an example where we see that:* (funcall (Y (lambda (thunk)
                (lambda (x)
                  (if (zerop x)
                      1
                      (* x (funcall (funcall thunk) (- x 1)))))))
           6)
720the
(funcall thunk) invokes the thunk in order to get the
actual recursive function, which we when then funcall
on (- x 1).
By wrapping the self-application with a thunk, we've made the call site where we use the thunk more complicated. We can clean that up by wrapping the call to the thunk in something nicer:
* (funcall
    (y (lambda (thunk)
         (flet ((factorial (&rest args)
                  (apply (funcall thunk) args)))
           (lambda (x)
             (if (zerop x)
                 1
                 (* x (factorial (- x 1))))))))
      6)
720And we can even go so far as to hoist that wrapper back up
into the definiton of y(defun y1 (g)
  (let ((d (lambda (f) (funcall g (lambda (&rest args) (apply (funcall f f) args))))))
    (funcall d d)))
* (funcall
    (y1 (lambda (factorial)
          (lambda (x)           
            (if (zerop x)
                1
                (* x (funcall factorial x))))))
    6)
720y1 is an alternative formulation of the Y
operator where we've η-expanded the recursive call to avoid the
runaway recursion.
The η-expanded version of the applicative order Y operator has the advantage that it is convenient for defining recursive functions. The thunkified version is less convenient because you have to force the thunk before using it, but it allows you to use the Y operator to define recursive data structures as well as functions:
(Y
  (lambda (delayed-ones)
    (cons-stream 1 (delayed-ones))))
{1 …}
The argument to the thunkified Y operator is itself a procedure of one argument, the thunk. Y returns the result of calling its argument. Y should return a procedure, so the argument to Y should return a procedure. But it doesn't have to immediately return a procedure, it just has to eventually return a procedure, so we could, for example, print something before returning the procedure:
* (funcall (Y (lambda (thunk)
                (format t "~%Returning a procedure")
                (lambda (x)
                  (if (zerop x)
                      1
                      (* x (funcall (funcall thunk) (- x 1)))))))
         6)
Returning a procedure
Returning a procedure
Returning a procedure
Returning a procedure
Returning a procedure
Returning a procedure
720There is one caveat.  You must be able to return the
  procedure without attempting to make the recursive call.
Let's transform the returned function before returning it by applying an arbitrary function h to it:
(Y (lambda (thunk)
     (h (lambda (x)
          (if (zerop x)
              1
              … )))))Ok, so now when
we (funcall thunk) we don't get what we want, we've got
  an invocation of h around it.  If we have an inverse to h,
  h-1, available, we can undo it:(y (lambda (thunk)
      (h (lambda (x)
           (if (zerop x)
               1
               (* (funcall (h-1 (funcall thunk)) (- x 1))))))))
As a concrete example, we return a list and at the call site we
  extract the first element of that list before calling it:* (funcall (car (y (lambda (thunk)
                     (list (lambda (x)
                             (if (zerop x)
                                 1
                                 (* x (funcall (car (funcall thunk)) (- x 1))))))))
           6)
720So we can return a list of mutually recursive
  functions:(y (lambda (thunk)
    (list
      ;; even?
      (lambda (n)
        (or (zerop n)
            (funcall (cadr (funcall thunk)) (- n 1))))
      ;; odd?
      (lambda (n)
        (and (not (zerop n))
             (funcall (car (funcall thunk)) (- n 1))))
      )))If we use the η-expanded version of the Y operator,
then we can adapt it to expect a list of mutually recursive functions
on the recursive call:(defun y* (&rest g-list)
  (let ((d (lambda (f)
             (map 'list (lambda (g)
                          (lambda (&rest args)
                            (apply (apply g (funcall f f)) args)))
                  g-list))))
     (funcall d d)))which we could use like this:* (let ((eo (y* (lambda (even? odd?)
                  (declare (ignore even?))
                  (lambda (n)
                    (or (zerop n)
                        (funcall odd? (- n 1)))))
                (lambda (even? odd?)
                  (declare (ignore odd?))
                  (lambda (n)
                    (and (not (zerop n))
                         (funcall even? (- n 1))))))))
     (let ((even? (car eo))
           (odd?  (cadr eo)))
       (do ((i 0 (+ i 1)))
           ((>= i 5))
         (format t "~%~d, ~s ~s"
                 i
                 (funcall even? i)
                 (funcall odd? i)))))))
                 
0, T NIL
1, NIL T
2, T NIL
3, NIL T
4, T NILInstead of returning a list of mutually recursive functions, we could return them as multiple values.
We just have to be expecting multiple values at the call site:(defun y* (&rest gs)
  (let ((d (lambda (f)
             (apply #'values
                    (map 'list
                         (lambda (g)
                           (lambda (&rest args)
                             (apply (multiple-value-call g (funcall f f)) args)))
                         gs)))))
    (funcall d d)))
MIT Scheme used to have a construct called a named lambda. A named lambda has an extra first argument that is automatically filled in with the function itself. So during evaluation of the body of a named lambda, the name is bound to the named lambda, enabling the function to call itself recursively:
(defmacro named-lambda ((name &rest args) &body body)
  `(y1 (lambda (,name)
         (lambda ,args
           ,@body))))
* (funcall (named-lambda (factorial x)
            (if (zerop x)
                1
                (* x (funcall factorial (- x 1)))))
         6)
720This leads us to named let expressions.  In a named let,
  the implicit lambda that performs the let
  bindings is a named lambda.  Using that name to invoke the lambda on a different set of arguments is
  like recursively re-doing the let.* (named-let fact ((x 6)) (if (zerop x) 1 (* x (funcall fact (- x 1))))) 720
In Scheme, you use letrec to define recursive or mutually recursive procedures.  Internal definitions expand into
  an appropriate letrec.  letrec achieves the necessary circularity not through the Y operator,
  but through side effects.  It is hard to tell the difference, but there is a difference.  Using the Y operator would allow
  you to have recursion, but avoid the implicit side effects in a letrec.
Oleg Kiselyov has more to say about the Y operator at http://okmij.org/ftp/Computation/fixed-point-combinators.html
In the "y1" definition, should "(funcall factorial x)" actually be "(funcall factorial (- x 1))"?
ReplyDelete