views:

3464

answers:

10

A y-combinator is a comp-sci concept from the "functional" side of things. Most programmers don't know much at all about them, if they've even heard about them.

What is a y-combinator? How do they work? What are they good for? Are they useful in procedural languages?

+23  A: 

If you're ready for a long read, Mike Vanier has a great explaination. Long story short, it allows you to implement recursion in a language that doesn't necessarily support it natively.

Nicholas Mancuso
A: 

A fixed point combinator (or fixed-point operator) is a higher-order function that computes a fixed point of other functions. This operation is relevant in programming language theory because it allows the implementation of recursion in the form of a rewrite rule, without explicit support from the language's runtime engine. (src Wikipedia)

Thomas Wagner
+3  A: 

Briefly, it allows you to use anonymous recursion without assuming your host language supports it.

Here's probably the best example of it I have seen. For myself personally, I haven't found it to be super useful in real world applications, but it is one of those things that once you wrap your head around, makes you a little smarter.

swilliams
+4  A: 

y-combinator in JavaScript:

var Y = function(f) {
  return (function(g) {
    return g(g);
  })(function(h) {
    return function() {
      return f(h(h)).apply(null, arguments);
    };
  });
};

var factorial = Y(function(recurse) {
  return function(x) {
    return x == 0 ? 1 : x * recurse(x-1);
  };
});

factorial(5)  // -> 120

Edit: I learn a lot from looking at code, but this one is a bit tough to swallow without some background - sorry about that. With some general knowledge presented by other answers, you can begin to pick apart what is happening. The Y function is the "y-combinator". Now take a look at the var factorial line where Y is used. Notice you pass a function to it that has a parameter (in this example, recurse) that is also used later on in the inner function. The parameter name basically becomes the name of the inner function allowing it to perform a recursive call (since it uses recurse() in it's definition.) The y-combinator performs the magic of associating the otherwise anonymous inner function with the parameter name of the function passed to Y. For the full explanation of how Y does the magic, checked out the linked article (not by me btw.)

Zach
looks confyoozing! intent of code not clear
DarenW
Javascript doesn't need a Y-combinator to do anonymous recursion because you can access the current function with arguments.callee (see http://en.wikipedia.org/wiki/Fixed_point_combinator#Anonymous_recursion_by_other_means)
xitrium
+41  A: 

A Y-combinator is a "functional" (a function that operates on other functions) that enables recursion, when you can't refer to the function from within itself. In computer-science theory, it generalizes recursion, abstracting its implementation, and thereby separating it from the actual work of the function in question. The benefit of not needing a compile-time name for the recursive function is sort of a bonus. =)

This is applicable in languages that support lambda functions. The expression-based nature of lambdas usually means that they cannot refer to themselves by name. And working around this by way of declaring the variable, refering to it, then assigning the lambda to it, to complete the self-reference loop, is brittle. The lambda variable can be copied, and the original variable re-assigned, which breaks the self-reference.

Y-combinators are cumbersome to implement, and often to use, in static-typed languages (which procedural languages often are), because usually typing restrictions require the number of arguments for the function in question to be known at compile time. This means that a y-combinator must be written for any argument count that one needs to use.

Below is an example of how the usage and working of a Y-Combinator, in C#.

Using a Y-combinator involves an "unusual" way of constructing a recursive function. First you must write your function as a piece of code that calls a pre-existing function, rather than itself:

// Factorial, if func does the same thing as this bit of code...
x == 0 ? 1: x * func(x - 1);

Then you turn that into a function that takes a function to call, and returns a function that does so. This is called a functional, because it takes one function, and performs an operation with it that results in another function.

// A function that creates a factorial, but only if you pass in
// a function that does what the inner function is doing.
Func<Func<Double, Double>, Func<Double, Double>> fact =
  (recurs) =>
    (x) =>
      x == 0 ? 1 : x * recurs(x - 1);

Now you have a function that takes a function, and returns another function that sort of looks like a factorial, but instead of calling itself, it calls the argument passed into the outer function. How do you make this the factorial? Pass the inner function to itself. The Y-Combinator does that, by being a function with a permanent name, which can introduce the recursion.

// One-argument Y-Combinator.
public static Func<T, TResult> Y<T, TResult>(Func<Func<T, TResult>, Func<T, TResult>> F)
{
  return
    t =>  // A function that...
      F(  // Calls the factorial creator, passing in...
        Y(F)  // The result of this same Y-combinator function call...
              // (Here is where the recursion is introduced.)
        )
      (t); // And passes the argument into the work function.
}

Rather than the factorial calling itself, what happens is that the factorial calls the factorial generator (returned by the recursive call to Y-Combinator). And depending on the current value of t the function returned from the generator will either call the generator again, with t - 1, or just return 1, terminating the recursion.

It's complicated and cryptic, but it all shakes out at run-time, and the key to its working is "deferred execution", and the breaking up of the recursion to span two functions. The inner F is passed as an argument, to be called in the next iteration, only if necessary.

Chris Ammerman
This is an excellent explanation! Thanks.
asgerhallas
+6  A: 

It's a Seeding Angel investor company for Startups!!!!!

A great one should I say!

Scott
I appreciate the humor :)
Charlie Flowers
Your Welcome. :)
Scott
A: 

For cross referencing here is an implementation of a Y Combinator in Lua.

Robert Gould
A: 

I have written a sort of "idiots guide" to the Y-Combinator in both Clojure and Scheme in order to help myself come to grips with it. They are influenced by material in "The Little Schemer"

http://bolusmjak.posterous.com/tag/ycombinator

Both tutorials are code interspersed with comments and should be cut & pastable into your favourite editor.

z5h
+1  A: 

Bit of a tip, if you are learning about functional languages like I do, better leave combinators until you get comfortable with it, otherwise it's a road to madness...

Igor Zevaka
+1  A: 

Y combininator in Javascript:

function Y(f) { return function (x) { f(arguments.callee)(x); }}

All the gymnastics with multiple recursive calls are only needed because pure lambda calculus doesn't have a way for a function to refer to itself.

This just begs the question.
spacemanaki