2

Does anyone know how to implement the Y-combinator in Scheme, specifically with lazy evaluation and additional argument? It's my understanding that Scheme (promise?) (delay) and (force) provide lazy evaluation support.

It's my understanding the Y-combinator with application is as follows, however will not work in Scheme due to applicative order evaluation by default.

((lambda (f)
   ((lambda (x) (f (x x)))
    (lambda (x) (f (x x)))))
 (lambda (r) (r)))

This is a suggested solution (the Z-combinator), however it uses another lambda function with arguments as the solution:

;; Z-combinator
(define Z
  (lambda (f)
    ((lambda (g) (f (g g)))
     (lambda (g) (f (lambda args (apply (g g)
                                        args)))))))
;Value: z

((Z (lambda (r)
      (lambda (x)
        (if (< x 2)
            1
            (* x (r (- x 1))))))) 5)
;Value: 120

;; end Z-combinator

Update based on solutions

The Y-combinator is as follows:

;; Y-combinator
(define Y
  (lambda (f)
      ((lambda (x) (f (delay (x x))))
       (lambda (x) (f (delay (x x)))))))
;Value: y

;; end Y-combinator

;; Non tail recursive
((Y (lambda (r)
      (lambda (x)
        (if (< x 2)
            1
            (* x ((force r) (- x 1)))))))
   5)
;Value: 120

;; Tail - recursive
((Y (lambda (r)
      (lambda (x acc)
        (if (< x 2)
            acc
            ((force r) (- x 1) (* x acc))))))
   5 1)

;Value: 120

Appreciate your guidance!

2 Answers2

2

Yes, with strict evaluation the self application (x x) causes an infinite loop to occur. If you delay this, you can use the y-combinator as long as you also 'force' that delayed object at its use site:

(define (test)
  (((lambda (f)
      ((lambda (x) (f (delay (x x))))
       (lambda (x) (f (delay (x x))))))
    (lambda (r)
      (lambda (x)
        (if (< x 2)
            1
            (* x ((force r) (- x 1)))))))
   5))

The normal way to make a variation of the y-combinator that works in a strict setting is to eta-expand the self application, which is another way to delay it but doesn't require an explicit application of force. Instead the forcing is done by function application.

rain1
  • 959
  • 5
  • 16
  • 1
    Thanks! I posted a tail-recursive equivalent and this paradigm does not work. Appreciate any guidance here. –  May 05 '19 at 20:33
2

The normal order Y combinator, here calculating (ack 3 6) in Lazy Racket:

#lang lazy

(define Y
  (lambda (f)
    ((lambda (g) (g g))
     (lambda (g) (f (g g))))))

((Y (lambda (ackermann)
      (lambda (m n)
        (cond
        ((= m 0) (+ n 1))
        ((= n 0) (ackermann (- m 1) 1))
        (else (ackermann (- m 1) (ackermann m (- n 1))))))))
 3
 6) ; ==> 509

Scheme isn't a lazy language like Lazy Racket so the Y needs to be a little different. It's now called a Z combinator:

#!r6rs
(import (rnrs base))

(define Z
  (lambda (f)
    ((lambda (g)
       (f (g g)))
     (lambda (g)
       (f (lambda args (apply (g g) args)))))))

((Z (lambda (ackermann)
      (lambda (m n)
        (cond
        ((= m 0) (+ n 1))
        ((= n 0) (ackermann (- m 1) 1))
        (else (ackermann (- m 1) (ackermann m (- n 1))))))))
 3
 6) ; ==> 509

Using delay and force doesn't really make it better:

#!r6rs
(import (rnrs base)
        (rnrs r5rs))

(define DY
  (lambda (f)
    ((lambda (g) (g g))
     (lambda (g) (f (delay (g g)))))))


((DY (lambda (d-ackermann)
      (lambda (m n)
        (cond
          ((= m 0) (+ n 1))
          ((= n 0) ((force d-ackermann) (- m 1) 1))
          (else ((force d-ackermann) (- m 1) ((force d-ackermann) m (- n 1))))))))
 3
 6) ; ==> 509

Normally Yand Z allows us to name our recursive procedure, but in this last one we get a promise that we need to resolve and thus we leak implementation details and it gets more difficult to use. Usually when involving promises we want to avoid unnecessary execution, but then the call should return a promise.

Sylwester
  • 44,544
  • 4
  • 42
  • 70
  • Thanks. I tried your last solution with MIT-Scheme and think it's missing a (delay). It outputs: ;The object #[compound-procedure 15], passed as an argument to force, is not a\ promise. –  May 05 '19 at 16:21
  • @Nick I tried it in mit-scheme 9.1.1 and it returned `509` as expected. Since it's R5RS you don't need the three first lines. – Sylwester May 05 '19 at 18:36