; The U combinator
(define (U f) (f f))
; A non-recursive function
(define (fib-nr f)
(lambda (n)
(if (< n 2) 1 (+ ((f f) (- n 1)) ((f f) (- n 2))))))
; Finding the 35-th Fibonacci number, by passing 35 to
; the fixpoint of fib-nr
((U fib-nr) 35) ;==> 14930352
which took 31.35 sec run time (using Scheme48 on a 2GHz Pentium 4).
I didn't have a powerful computer back in 2000, so the old paper
computed only 25-th Fibonacci number.
Now, let's define a more generic fixpoint combinator:
(define (UM myapply f)
(define (g v)
(lambda args (myapply (f v) args)))
(f g))
which uses an "abstract" function myapply. We can use the freedom of
choosing myapply, for example, to _transparently_ interpose a
memoization.
; Cache function's arguments and results
(define (make-memoizer)
(let ((application-cache '()))
(lambda (function args)
(cond
((assoc args application-cache) => cdr)
(else
(let ((result (apply function args)))
(set! application-cache
(cons (cons args result)
application-cache))
result))))))
Now,
((UM (make-memoizer) fib-nr) 35) gives the same result under 0.01
seconds. The function fib_nr did not have to be changed at all.
In OOP parlance, we "inherited" from a function and "overrode"
the self-application. Incidentally, there is a deep connection
between object-based programming and Y: Indeed, given a state
transformer function
transform:: State -> Message -> State
an object (of a recursive type Object = Object -> Message -> Object)
is a fixpoint
((Y (\self state msg -> self (transform state msg)) init-state)