0

I tried using the y-combinator (in both Lua and Clojure) as I thought that would allow me to exceed the size of default stack implementations when using recursion. It seems I was mistaken. Yes, it works, but in both of these systems, the stack blows at exactly the same point as it does using plain old recursion. A lowish ~3600 in Clojure and a highish ~333000 on my Android Lua implementation. It is also a bit slower than regular recursion.

So is there anything to be gained by using the y-combinator, or is it just an intellectual exercise to prove a point? Have I missed something?

===

PS. Sorry, I should have made it clearer that I am aware I can use TCO to exceed the stack. My question does not relate to that. I am interested in this a) from the academic/intellectual point of view b) whether there is anything that can be done about those function that cannot be written tail recursively.

duplode
  • 31,361
  • 7
  • 69
  • 130
Alex Gian
  • 438
  • 4
  • 9
  • Lua supports tail call optimization. If you can rewrite your recursion as a tail call you are no longer limited by the stack size. – Henri Menke Jun 20 '18 at 02:47
  • Thanks Henri. I am aware that Lua supports TCO as I use it regularly. So does Clojure, through 'recur'. My question concerns those functions that /cannot/ be written tail-recursively; also any practical use of the y-combinator. – Alex Gian Jun 20 '18 at 02:51

3 Answers3

3

The Y combinator allows a non-recursive function to be used recursively, but that recursion still consumes stack space through nested function invocations.

For functions that can't be made tail-recursive, you could try refactoring them using continuation passing style, which would consume heap space instead of stack.

Here's a good overview of the topic: https://www.cs.cmu.edu/~15150/previous-semesters/2012-spring/resources/lectures/11.pdf

Taylor Wood
  • 15,104
  • 1
  • 17
  • 35
  • Indeed, I do believe that was what I was looking for, really - transforming stack space use into heap; I'd sort of hoped (in a moment of naivety :) ) that the Y-combinator might somehow do this magically, but at least I've got something to go on now. – Alex Gian Jun 20 '18 at 03:26
  • @AlexGian you might find [this answer](https://stackoverflow.com/a/49565706) helpful too. It's in a different language but it's a good example of using CPS for an algorithm that can't be made tail recursive. – Taylor Wood Jun 20 '18 at 03:49
  • Ah, great - I can't read Haskell very fluently yet, so that "Lecture 11" was giving me a bit of a problem, even though I can see that it's what I want. I'm much happier reading the ML-ey style of that answer... F#, yes? Never used it yet, but it looks cool. Now if I could find it in Scheme or Clojure... or even JS (haha). – Alex Gian Jun 20 '18 at 03:55
  • Great. Found it! In case anyone is interested, here it is: https://stackoverflow.com/questions/5059142/continuation-passing-style-in-scheme – Alex Gian Jun 20 '18 at 04:02
  • The CPS style worked very well with Lua on an Android tablet. Clojure did not have the same luck. Rather than go off-topic here, since the original question re y-combinator has been exhausted, I have moved new question to here: https://stackoverflow.com/questions/50952443/continuation-passing-style-does-not-seem-to-make-a-difference-in-clojure – Alex Gian Jun 20 '18 at 16:04
0

A “tail call” will allow you to exceed any stack size limitations. See Programming in Lua, section 6.3: Proper Tail Calls:

...after the tail call, the program does not need to keep any information about the calling function in the stack. Some language implementations, such as the Lua interpreter, take advantage of this fact and actually do not use any extra stack space when doing a tail call. We say that those implementations support proper tail calls.

brianolive
  • 1,451
  • 2
  • 8
  • 19
  • @AlexGian, can you post sample code of what you are trying to do? – brianolive Jun 20 '18 at 02:59
  • Brian, I am merely experimenting with the Y-combinator and I was wondering whether it would allow me to exceed the stack limits. I am aware I could write the same functions tail recursively and have them work like iterations, but that's not my point. If you still want, I will post how I do this in Lua, there's no point in doing that unless someone is specifically interested. – Alex Gian Jun 20 '18 at 03:06
0

If you haven't seen it yet, there is a good explanation here: What is a Y-combinator?

In summary, it helps to prove that the lambda calculus is Turing complete, but is useless for normal programming tasks.


As you are probably aware, in Clojure you would just use loop/recur to implement a loop that does not consume the stack.

Alan Thompson
  • 25,038
  • 5
  • 36
  • 43
  • "Y in practical programs" and [its discussion on LtU](http://lambda-the-ultimate.org/classic/message5463.html) disagree. :) some call this ["open recursion"](https://stackoverflow.com/search?q=open+recursion+memoization) and have different uses for it, like [memoization](https://stackoverflow.com/questions/21688063/haskell-to-fix-or-not-to-fix/21688436#21688436) and [more](https://stackoverflow.com/questions/8962787/records-with-similar-fields-in-ocaml/8967717#8967717). – Will Ness Sep 09 '18 at 19:02