;; Identity combinator
(define I
  (lambda (x) x))

;; Application combinator
(define app
  (lambda (f x)
    (f x)))

;; Sequencing combinator (normal order)
(define seq
  (lambda (x y)
    (if x y y)))

;; Curry as combinator
(define curry
  (lambda (f)
    (lambda (x)
      (lambda (y)
	(f x y)))))

(define plusc (curry +))

(define plus2 ((curry +) 2))


;; Recursive definitions in lambda-calculus

;; Defining factorial in pure lambda calculus (no recursive defines or
;; letrec)

;; This expression (call it f) takes a procedure (factorial) that
;; returns a procedure that computes the factorial of a number.
(define f
  (lambda (g)
    (lambda (n)
      (if (= n 0)
	  1
	  (* n (g (- n 1)))))))

;; What can we apply it to, to get the factorial function?

;; (f f) is not well defined: type of f is (I->I)->(I->I) 

;;you can apply it to itself but the resulting fuction cannot be
;;applied to anything.

;; (f I) is well defined:  it returns n * (n-1) (for n>0) but it
;; does not return the desired factorial function

;; how do we get factorial?
;; we need a procedure X representing factorial, i.e.,
;; (f X) = X

;; In the factorial example, the following lambda-expression does the job:
(define X
  ((lambda (x) 
     ((lambda (g)
	(lambda (n)
	  (if (= n 0)
	      1
	      (* n (g (- n 1))))))
      (lambda (y) ((x x) y))))
   (lambda (x) 
     ((lambda (g)
	(lambda (n)
	  (if (= n 0)
	      1
	      (* n (g (- n 1)))))) 
      (lambda (y) ((x x) y))))))

;; Such X can be defined in terms of the recursive function f as follows
;; X = (Y f)

;; where Y is the recursion combinator 
(define Y
  (lambda (f)
    ((lambda (x) (f (x x)))
     (lambda (x) (f (x x))))))

;; here in applicative order (after eta-expansion) so that it
;; terminates with applicative order reduction
(define Y
  (lambda (f)
    ((lambda (x) (f (lambda (y) ((x x) y))))
     (lambda (x) (f (lambda (y) ((x x) y)))))))

;; the type of Y is ((I->I)->(I->I))->(I->I)

;; thus, here is the "pure" lambda-calculus expression computing factorial:
;; (assuming that 0, 1, -, *, = have beed defined in the lambda calculus)

((lambda (f)
    ((lambda (x) (f (lambda (y) ((x x) y))))
     (lambda (x) (f (lambda (y) ((x x) y))))))
 (lambda (g)
  (lambda (n)
    (if (= n 0)
	1
	(* n (g (- n 1)))))))

;; notice that (Y f) = (f (Y f)) by the construction above.

;; Fortunately, in Scheme, the "define" and "letrec" forms make our
;; life easier for defining recursive procedures.

;; Factorial
(define !
  (lambda (n)
    (if (= n 0)
	1
	(* n (! (- n 1))))))

;; Exponential
(define ^
  (lambda (b n)
    (if (= n 0)
	1
	(* b (^ b (- n 1))))))

;; Powers of 2
(define 2^ ((curry ^) 2))

(define rcurry
  (lambda (f)
    (lambda (x)
      (lambda (y)
	(f y x)))))

;; Square function
(define ^2 ((rcurry ^) 2))

