The Y Combinator in Go with generics
eli.thegreenplace.net
eli.thegreenplace.net
fix :: (a -> a) -> a
fix f = f (fix f)
Y combinator (with sharing) -- https://hackage.haskell.org/package/base/docs/Data-Function.html#v:fix
-- https://stackoverflow.com/questions/53715841/sharing-vs-non-sharing-fixed-point-combinator
fix :: (a -> a) -> a
fix f = let x = f x in x
Y combinator (type level) type Fix :: (Type -> Type) -> Type
newtype Fix f = Fix (f (Fix f))The standard definition in untyped lambda calculus is
\f -> (\x -> f (x x)) (\x -> f (x x))
but if we try to give a type to x, let's call it X, we'll see something funny X = i -> o -- we know it's a function type because it's applied
X = X -> o -- it's self-applied, so the input must be X
X = (X -> o) -> o -- expanding the inner reference
X = ((X -> o) -> o) -> o -- oh no
Unfortunately, this won't type in Haskell because `type X = (X -> o) -> o` is invalid and would loop the type checker. We must introduce an explicit indirection. This explicitness forces us to control if and when this type expands and prevents the checker from looping. newtype Loop a = Loop (Loop a -> a)
defer :: (Loop a -> a) -> Loop a
defer f = Loop f
apply :: Loop a -> (Loop a -> a)
apply (Loop f) = f
This type is exactly a solution to `X = (X -> o) -> o` but with the recursion explicitly tagged, as x :: Loop o
apply x :: Loop o -> o
apply x . defer :: (Loop o -> o) -> o
So now we can type the type Y combinator without utilizing Haskell's self-referential bindings. y f :: (a -> a) -> a
y f = apply half half
where
half :: Loop a -- the type of X
half x = defer (f (apply x x)) -- i.e. \x -> f (x x)
Still pretty concise compared to Go!(Probably saw this first at https://r6.ca/blog/20060919T084800Z.html)
https://stackoverflow.com/questions/4273413/y-combinator-in-...
if you're making a recursive function call then you're just making something that kinda looks like Y but isn't.
FWIW i don't think you can write Y in haskell, it would not make much sense.
according to this thread you can't, because it doesn't type check.
https://stackoverflow.com/questions/4273413/y-combinator-in-...
fix :: (a -> a) -> a
fix f = f (fix f)
f :: [Int] -> [Int]
f xs = 1 : map (*2) xs
take 7 (fix f) ==> [1,2,4,8,16,32,64]
Looks right to me.https://medium.com/@lukeh/untyped-lambda-calculus-church-num...
https://gist.github.com/lukehoban/0ec2a3dbbb9a13338d338a3fbb...
auto gcd = [](this auto self, int a, int b) -> int {
return b == 0 ? a : self(b, a % b);
}
std::cout << gcd(20, 30) << std::endl;C++ seems to be trying to maximise confusion these days.
struct foo
{
void bar(int a, int b) {
auto lambda = [&] (auto this self) {
this->compute();
};
}
void compute();
};
if the variable name was "auto this" you wouldn't be able to refer to "compute" through the "this" of the parent class here[0] https://eli.thegreenplace.net/2016/some-notes-on-the-y-combi...
This is a bit more interesting than it looks because ostensibly you're using the name of a function in its definition before that name ever has a meaning -- you're not really saying that you should literally call that symbol, but that the compiler should produce a function according to some set of rules that fills in the self references.
That doesn't work in lambda calculus though, where definitions are concrete and you don't have compiler magic to resolve the undefined symbol in your definition. A solution is to pass some function to itself as an argument, and you have a code pattern that goes along with it (the Y combinator).
This retains some "usefulness" in the sense that lambda calculus is still being explored for various purposes as a foundation for other things, and also in that languages based on that line of thought might benefit from using that structure explicitly. If you have some sort of syntactic sugar for recursion in your language of choice though and don't care about foundations then it's probably not very applicable.
record Cache<T, U>(Function<T, U> function) {
U get(T value) { ... }
}
you can use it that way var cache = new Cache<Integer, Integer>(v -> v + 1);
var result = cache.get(3);
with that settings, how do you specify a lambda which is recursive ? and how can you cache the intermediary steps if you can change the signature of get() ?Y-combinator in like 100 languages: https://rosettacode.org/wiki/Y_combinator #Python
It is a tool to show that untyped lambda calculus with just anonymous functions and applications is already Turing-complete, because you can write the `Y-combinator`, thus you can define recursive functions, and thus you can write `while` loops.
In a way, the Y-combinator is one of the possible ways to reach Turing-completeness by accident. It is an important example to keep in mind if you want to design a type system for a programming language that is not Turing-complete.
But as soon as you want Turing-completeness, the Y-combinator is not a nice primitive for most users. In this case, either your language is flexible enough to write a good library for recursive definitions based on the Y-combinator. Otherwise, it often works better to introduce recursive definitions as a primitive, even if theoretically this primitive is not absolutely needed.
Key point:
the Y combinator implements simple recursion. In the lambda calculus it is not possible to refer to the definition of a function in a function body. Recursion may only be achieved by passing in a function as a parameter. The Y combinator demonstrates this style of programming.
You don't really need it in modern programming languages that natively support named functions and recursion, but it's an interesting part of the underlying theory of lambda calculus. let Y = f => (x => x(x))(x => f(z => x(x)(z)))
then we can call it, again only using closures, and define a function that sums the numbers from 1 to N by "calling itself": let sumToN = Y(sumToN => n => n == 0 ? 0 : n + sumToN(n-1))
sumToN(4)
// -> 10
In practice, no one actually implements recursion this way because it would be slow. So instead it's more of a statement about the expressiveness of languages, and how "we have recursion!" doesn't actually give you any power you didn't have before, if you had functions.Look at eg the Curry's Paradox part on the WP page for a taste.
(It's still pretty opaque without reading the explanation, though.)