; The Y Combinator
; ----------------

; The Y combinator is defined as follows:

(define Y
    (lambda (f)
        (
            (lambda (x)
                    (f (lambda (y) ((x x) y)))
            )
            (lambda (x)
                    (f (lambda (y) ((x x) y)))
            )
        )
    )
)

; It has the property that (Y f) is equivalent to (f (Y f)) (and so to
; infinity. One can use the Y combinator to implement recursion in
; lambda calculus.

; Let's see an example in Scheme on how to use the Y combinator:

(define (top x) (car x))
(define (var x) (cadr x))        ; (cadr x) is equivalent to (car (cdr x))
(define (result x) (caddr x))

; This function calculates the sum of integers from (var x) to (top x).
; Note that we can only pass one argument to the Y's function so a
; list is used to pass multiple arguments.
(define mysum
    (Y (lambda (f)
        (lambda (x)
            (if (<= (var x) (top x))
                ; If part
                    ; The Y combinator enables us to use f to call
                    ; itself recursively.
                (f (list
                    (top x)
                    (+ (var x) 1)
                    (+ (result x) (var x))
                ))
                ; Else part
                x
            )
        )
    ))
)


(define solution (mysum (list 10 5 0)))
(display "The solution is: ")
(display (result solution))
(newline)

; Naturally, we can convert this entire thing to lambda calculus:

(define (top x) (lc_car x))
(define (var x) (lc_car (lc_cdr x)))
(define (result x) (lc_car (lc_cdr (lc_cdr x))))

(define make-mysum-list
(lambda (top)
    (lambda (var)
        (lambda (result)
            ((lc_cons top) ((lc_cons var) ((lc_cons result) zero)))
        )
    )
))

; Notice that we wrap the inner if's in lambdas and pass zero as no_use.
; Otherwise, both conditional clauses will always be evaluated and it will
; run an infinite number of times.
(define mysum
    (Y (lambda (f)
        (lambda (x)
            (((((less-or-equal (var x)) (top x)) ; If var x < top x
                ; If part
                (lambda (no_use)
                    (f
                        (((make-mysum-list
                            (top x))
                            (succ (var x)))
                            ((add (result x)) (var x))
                        )
                    )
                ))
                ; Else part
                (lambda (no_use)
                    x
                ))
                    zero ;  Pass zero as argument to no_use
            )
        )
    ))
)

(define solution (mysum
    (((make-mysum-list
        (int->church 10))
        (int->church 5))
        (int->church 0))
        ))

(display "The solution is: ")
(display (church->int (result solution)))
(newline)