3

We can define a recursive function, factorial as an example, by YCombinator as follows

;;; elisp
;;; This code works. Thanks to
;;; https://www.diegoberrocal.com/blog/2015/10/12/y-combinator-in-emacs-lisp/ 

(setq lexical-binding t)  ;;; lexical == static ??

(defun YCombinator (f)
  (funcall #'(lambda (x) (funcall f
                            #'(lambda (y) (funcall (funcall x x) y))))

           #'(lambda (x) (funcall f
                            #'(lambda (y) (funcall (funcall x x) y))))
  )
)

(setq meta-factorial
      #'(lambda (f) #'(lambda (n) (if (eq n 0) 1 (* n (funcall f (1- n)))))))

(funcall (YCombinator meta-factorial) 4) ;; ===> 24

I have learned what a Y combinator is, and knew how it is defined in a mathematical way.

Y: f -> ( (x -> f(x x)) (x -> f(x x)) )

But I found it hard to implement. In particular, my definition of YCombinator, which seems more closer to the mathematical definition, fails to define factorial.


;; this code fails!

(defun YCombinator (f)
  (funcall #'(lambda (x) (funcall f
                            #'(funcall x x)))

           #'(lambda (x) (funcall f
                            #'(funcall x x)))
  )
)

Questions

  1. Why is this the case? Did I miss something?
  2. Why do we need to set lexical-binding to t?
  3. Is there a lambda-expression to (e)lisp translator?
Student
  • 404
  • 3
  • 9

1 Answers1

1

You say you understand this definition:

Y: f -> ( (x -> f(x x)) (x -> f(x x)) )

You can eta-expand the x xs in that to get this:

Y: f -> ( (x -> f(y -> x x y)) (x -> f(y -> x x y)) )

And you should see that this is the same as the working one. Thus, in a pure math lambda-calculus world, your definition and the working one are the same. This leads to the conclusion that yours didn't work because Lisp doesn't live in a pure math lambda-calculus world. This is indeed the case, and the specific difference is that Lisp is strict, which causes it to evaluate the x xs too early, and thus infinitely recurse without ever getting anywhere. Wrapping them in a mathematically unnecessary lambda works around the strictness. As a final note, had you tried to implement this in a lazy language, you wouldn't need that workaround. For example, here's a transliteration of your code into Lazy Racket, which works fine without it:

#lang lazy
(define (YCombinator f) ((lambda (x) (f (x x))) (lambda (x) (f (x x)))))
(define meta-factorial (lambda (f) (lambda (n) (if (= n 0) 1 (* n (f (- n 1)))))))
((YCombinator meta-factorial) 4)

As for why your code uses lexical binding, the simple answer is that that's how lambda calculus works, and trying to get it to work with dynamic binding would significantly complicate everything for no benefit.

  • 1. In the working code I posted, there's also `..(funcall x x)..`. Why wasn't this evaluated too early? 2. Doing that in Haskell directly gives error since it's typed. It's trickier but can be worked around, see my [question](https://www.reddit.com/search?q=haskell%20lambda). – Student May 12 '20 at 16:49
  • 1
    1. Because it's wrapped inside of the `lambda (y)`, and the body of a lambda isn't evaluated until it's called. 2. Yeah, maybe Haskell wasn't the best concrete example because of its type system. I can't think of any languages offhand that have both laziness and a weak enough type system to write that, though. – Joseph Sible-Reinstate Monica May 12 '20 at 17:10
  • Great answer! Today I learn you can make (e)lisp by using lambda expressions. And for the second point.. yeah, I wonder why there's no language that offers us the freedom to choose whether want it to be typed/untyped, lazy/unlazy... – Student May 12 '20 at 17:39
  • 1
    @JosephSible-ReinstateMonica: you can write the obvious Y in lazy Racket. – tfb May 12 '20 at 18:59
  • @tfb Thanks. Answer edited to use it as an example. – Joseph Sible-Reinstate Monica May 12 '20 at 19:48