The Y combinator is defined as $$Y=\lambda f.(\lambda x. f (x x))(\lambda x. f (x x))$$ It has the following useful property: $$Y g = g (Y g)$$ for some expression $g$. It can be easily used to implement a recursive function, e.g. factorial:
Y = lambda f : (lambda x : f(lambda v : x(x)(v)))(lambda x : f(lambda v : x(x)(v)))
F = lambda r : lambda n : 1 if n == 0 else n * r(n - 1)
fact = Y(F) # same as F(Y(F)), therefore the r in the body becomes Y(F) which is fact
print(fact(5))
The above code outputs 120, as expected. (The Y combinator in the code differs from the one at the start of the question. This is because the one at the start would cause a recursion depth exception. They are equivalent in the lambda calculus nonetheless)
An alternative method, which is the topic of my question, is as follows:
F = lambda r : lambda n : 1 if n == 0 else n * r(r)(n - 1)
fact = F(F) # r gets substituted with F, therefore the expression in the body becomes F(F) which is fact
print(fact(5))
This once again outputs 120, but is more readable and straightforward in my opinion.
If the second method works, then what is the point of using the Y combinator? Wikipedia says this about the second method:
…The self-application achieves replication here, passing the function's lambda expression on to the next invocation as an argument value, making it available to be referenced and called there.
This solves it but requires re-writing each recursive call as self-application. We would like to have a generic solution, without a need for any re-writes:…
It then proposes the first method as a better solution. What do they mean by generic solution?