RECURSION THE HARD WAY
Y
ELEANOR MCHUGH
@feyeleanor
...AN EXTRACT FROM...
A NAMED FUNCTION CAN CALL ITSELF
package main
func main() {
main()
}
11.GO
github://feyeleanor/y_recursion_the_hard_way
this recurses because main() is a named function
BUT CAN AN ANONYMOUS FUNCTION CALL ITSELF
package main
func main() {
func(x) {
...
}
}
github://feyeleanor/y_recursion_the_hard_way
but how do we make an anonymous function recurse?
BUT CAN AN ANONYMOUS FUNCTION CALL ITSELF
package main
func main() {
g := func(x) {
g()
}
}
github://feyeleanor/y_recursion_the_hard_way
works if we run it in the scope where it's created?
otherwise it's not an option
ANONYMOUS FUNCTIONS ARE THE MATHEMATICAL BASIS OF COMPUTATION
github://feyeleanor/y_recursion_the_hard_way
ANONYMOUS FUNCTIONS AND THE LAMBDA CALCULUS
github://feyeleanor/y_recursion_the_hard_way
A FIXED POINT IS WHEN A FUNCTION RETURNS THE VALUE PASSED TO IT
github://feyeleanor/y_recursion_the_hard_way
in untyped lambda calculus functions are anonymous
but every function has a
fi
xed point
which is not the general case in mathematics
so given f(x) = x2 - 3x + 4
f(2) = 2 is a
fi
xed point
meaning that calculating f for a value returns that value unchanged
so given f(x) = x
f(2) = 2 is also a
fi
xed point
INTRODUCING THE Y COMBINATOR
Y g = (λf.(λx.f (x x)) (λx.f (x x))) g
= (λx.g (x x)) (λx.g (x x))
= g ((λx.g (x x)) (λx.g (x x)))
= g (Y g)
the Y combinator uses
fi
xed points
to express recursion
for an anonymous function
github://feyeleanor/y_recursion_the_hard_way
this is good math but hard to read!
THE UNTYPED Y COMBINATOR IN GO
func Y(g func(any) any) func(any) any {
return func(f any) func(any) any {
return f.(func(any) any)(f).(func(any) any)
}(func(f any) any {
return g(func(x any) any {
return f.(func(any) any)(f).(func(any) any)(x)
})
})
}
25.GO
github://feyeleanor/y_recursion_the_hard_way
WTAF?!?!?!?!
it's even worse in go because of types
THE UNTYPED Y COMBINATOR IN GO (PRE-GENERICS)
func Y(g func(interface{}) interface{}) func(interface{}) interface{} {
return func(f interface{}) func(interface{}) interface{} {
return f.(func(interface{}) interface{})(f).(func(interface{}) interface{})
}(func(f interface{}) interface{} {
return g(func(x interface{}) interface{} {
return f.(func(interface{}) interface{})(f).(func(interface{}) interface{})(x)
})
})
}
25.GO
github://feyeleanor/y_recursion_the_hard_way
WTAF?!?!?!?!
!!!and it used to be even worse!!!
COMPUTING FACTORIALS IS A CLASSIC RECURSIVE PROBLEM
github://feyeleanor/y_recursion_the_hard_way
THE UNTYPED Y COMBINATOR
func main() {
...
factorial := Y(func(g any) any {
return func(n any) (r any) {
if n, ok := n.(int); ok {
switch {
case n == 0, n == 1:
return 1
case n > 1:
return n * g.(func(any) any)(n-1).(int)
}
}
panic(n)
}
})
...
}
func Y(g func(any) any) func(any) any {
return func(f any) func(any) any {
return f.(func(any) any)(f).(func(any) any)
}(func(f any) any {
return g(func(x any) any {
return f.(func(any) any)(f).(func(any) any)(x)
})
})
}
25.GO
github://feyeleanor/y_recursion_the_hard_way
INTRODUCING GO TYPES TO THE Y COMBINATOR
type Function func(any) any
func Y(g func(any) any) func(any) any {
return func(f any) func(any) any {
return f.(func(any) any)(f).(func(any) any)
}(func(f any) any {
return g(func(x any) any {
return f.(func(any) any)(f).(func(any) any)(x)
})
})
}
func Y(g Function) Function {
return func(f any) Function {
return f.(Function)(f).(Function)
}(func(f any) any {
return g(func(x any) any {
return f.(Function)(f).(Function)(x)
})
})
}
26.GO
github://feyeleanor/y_recursion_the_hard_way
becomes
INTRODUCING GO TYPES TO THE Y COMBINATOR
type Function func(any) any
func Recursor(f any) Function {
return f.(Function)(f).(Function)
}
func Y(g Function) Function {
return func(f any) Function {
return f.(Function)(f).(Function)
}(func(f any) any {
return g(func(x any) any {
return f.(Function)(f).(Function)(x)
})
})
}
func Y(g Function) Function {
return Recursor(
func(f any) any {
return g(func(x any) any {
return Recursor(f)(x)
})
})
}
26.GO
github://feyeleanor/y_recursion_the_hard_way
becomes
INTRODUCING GO TYPES TO THE Y COMBINATOR
type Function func(any) any
func Recursor(f any) Function {
return f.(Function)(f).(Function)
}
type Transformer func(Function) Function
func Y(g Function) Function {
return Recursor(
func(f any) any {
return g(func(x any) any {
return Recursor(f)(x)
})
})
}
func Y(g Transformer) Function {
return Recursor(
func(f any) any {
return g(func(x any) any {
return Recursor(f)(x)
})
})
}
26.GO
github://feyeleanor/y_recursion_the_hard_way
becomes
INTRODUCING GO TYPES TO THE Y COMBINATOR
type Function func(any) any
func Recursor(f any) Function {
return f.(Function)(f).(Function)
}
type Transformer func(Function) Function
func main() {
...
factorial := Y(func(g Function) Function {
return func(n any) (r any) {
if n, ok := n.(int); ok {
switch {
case n == 0, n == 1:
return 1
case n > 1:
return n * g(n-1).(int)
}
}
panic(n)
}
})
...
}
func Y(g Transformer) Function {
return Recursor(
func(f any) any {
return g(func(x any) any {
return Recursor(f)(x)
})
})
}
27.GO
github://feyeleanor/y_recursion_the_hard_way
SIMPLIFYING THE TYPED Y COMBINATOR
Y g = (λf.(λx.f (x x)) (λx.f (x x))) g
= (λx.g (x x)) (λx.g (x x))
= g ((λx.g (x x)) (λx.g (x x)))
= g (Y g)
github://feyeleanor/y_recursion_the_hard_way
SIMPLIFYING THE TYPED Y COMBINATOR
type Function func(any) any
type Transformer func(Function) Function
type Recursor func(Recursor) Function
func (r Recursor) Apply(t Transformer) Function {
return t(r(r))
}
func Y(t Transformer) Function {
g := func(r Recursor) Function {
return func(x any) any {
return r.Apply(t)(x)
}
}
return g(g)
}
func main() {
...
factorial := Y(func(g Function) Function {
return func(n any) (r any) {
if n, ok := n.(int); ok {
switch {
case n == 0, n == 1:
return 1
case n > 1:
return n * g(n-1).(int)
}
}
panic(n)
}
})
...
}
28.GO
github://feyeleanor/y_recursion_the_hard_way
THE Y COMBINATOR WITH GENERIC TYPES
type Function[T, R any] func(T) R
type Transformer[T, R any] func(Function[T, R]) Function[T, R]
type Recursor[T, R any] func(Recursor[T, R]) Function[T, R]
func (r Recursor[T, R]) Apply(t Transformer[T, R]) Function[T, R] {
return t(r(r))
}
func Y[T, R any](t Transformer[T, R]) Function[T, R] {
g := func(r Recursor[T, R]) Function[T, R] {
return func(x T) R {
return r.Apply(t)(x)
}
}
return g(g)
}
func main() {
...
factorial := Y(func(g Function[int, int]) Function[int, int] {
return func(n int) (r int) {
switch {
case n < 0:
panic(n)
case n > 1:
return n * g(n - 1)
}
return 1
}
})
...
}
29.GO
github://feyeleanor/y_recursion_the_hard_way
THE GENERIC Y COMBINATOR WITH AUTOMATIC RESULT CACHING
func Y[T comparable, R any](t Transformer[T, R]) Function[T, R] {
m := make(map[T]R)
g := func(r Recursor[T, R]) Function[T, R] {
return func(x T) (v R) {
var ok bool
if v, ok = m[x]; ok {
return v
}
v = r.Apply(t)(x)
m[x] = v
fmt.Printf("Y: setting m[%v] = %vn", x, v)
return v
}
}
return g(g)
}
func main() {
...
factorial := Y(func(g Function[int, int]) Function[int, int] {
return func(n int) (r int) {
switch {
case n < 0:
panic(n)
case n > 1:
return n * g(n-1)
}
return 1
}
})
...
}
30.GO
github://feyeleanor/y_recursion_the_hard_way
LEANPUB://GONOTEBOOK [GITHUB | SLIDESHARE]://FEYELEANOR

The Y Combinator, a Lightning Talk from GoLab 2025

  • 1.
    RECURSION THE HARDWAY Y ELEANOR MCHUGH @feyeleanor ...AN EXTRACT FROM...
  • 2.
    A NAMED FUNCTIONCAN CALL ITSELF package main func main() { main() } 11.GO github://feyeleanor/y_recursion_the_hard_way this recurses because main() is a named function
  • 3.
    BUT CAN ANANONYMOUS FUNCTION CALL ITSELF package main func main() { func(x) { ... } } github://feyeleanor/y_recursion_the_hard_way but how do we make an anonymous function recurse?
  • 4.
    BUT CAN ANANONYMOUS FUNCTION CALL ITSELF package main func main() { g := func(x) { g() } } github://feyeleanor/y_recursion_the_hard_way works if we run it in the scope where it's created? otherwise it's not an option
  • 5.
    ANONYMOUS FUNCTIONS ARETHE MATHEMATICAL BASIS OF COMPUTATION github://feyeleanor/y_recursion_the_hard_way
  • 6.
    ANONYMOUS FUNCTIONS ANDTHE LAMBDA CALCULUS github://feyeleanor/y_recursion_the_hard_way
  • 7.
    A FIXED POINTIS WHEN A FUNCTION RETURNS THE VALUE PASSED TO IT github://feyeleanor/y_recursion_the_hard_way in untyped lambda calculus functions are anonymous but every function has a fi xed point which is not the general case in mathematics so given f(x) = x2 - 3x + 4 f(2) = 2 is a fi xed point meaning that calculating f for a value returns that value unchanged so given f(x) = x f(2) = 2 is also a fi xed point
  • 8.
    INTRODUCING THE YCOMBINATOR Y g = (λf.(λx.f (x x)) (λx.f (x x))) g = (λx.g (x x)) (λx.g (x x)) = g ((λx.g (x x)) (λx.g (x x))) = g (Y g) the Y combinator uses fi xed points to express recursion for an anonymous function github://feyeleanor/y_recursion_the_hard_way this is good math but hard to read!
  • 9.
    THE UNTYPED YCOMBINATOR IN GO func Y(g func(any) any) func(any) any { return func(f any) func(any) any { return f.(func(any) any)(f).(func(any) any) }(func(f any) any { return g(func(x any) any { return f.(func(any) any)(f).(func(any) any)(x) }) }) } 25.GO github://feyeleanor/y_recursion_the_hard_way WTAF?!?!?!?! it's even worse in go because of types
  • 10.
    THE UNTYPED YCOMBINATOR IN GO (PRE-GENERICS) func Y(g func(interface{}) interface{}) func(interface{}) interface{} { return func(f interface{}) func(interface{}) interface{} { return f.(func(interface{}) interface{})(f).(func(interface{}) interface{}) }(func(f interface{}) interface{} { return g(func(x interface{}) interface{} { return f.(func(interface{}) interface{})(f).(func(interface{}) interface{})(x) }) }) } 25.GO github://feyeleanor/y_recursion_the_hard_way WTAF?!?!?!?! !!!and it used to be even worse!!!
  • 11.
    COMPUTING FACTORIALS ISA CLASSIC RECURSIVE PROBLEM github://feyeleanor/y_recursion_the_hard_way
  • 12.
    THE UNTYPED YCOMBINATOR func main() { ... factorial := Y(func(g any) any { return func(n any) (r any) { if n, ok := n.(int); ok { switch { case n == 0, n == 1: return 1 case n > 1: return n * g.(func(any) any)(n-1).(int) } } panic(n) } }) ... } func Y(g func(any) any) func(any) any { return func(f any) func(any) any { return f.(func(any) any)(f).(func(any) any) }(func(f any) any { return g(func(x any) any { return f.(func(any) any)(f).(func(any) any)(x) }) }) } 25.GO github://feyeleanor/y_recursion_the_hard_way
  • 13.
    INTRODUCING GO TYPESTO THE Y COMBINATOR type Function func(any) any func Y(g func(any) any) func(any) any { return func(f any) func(any) any { return f.(func(any) any)(f).(func(any) any) }(func(f any) any { return g(func(x any) any { return f.(func(any) any)(f).(func(any) any)(x) }) }) } func Y(g Function) Function { return func(f any) Function { return f.(Function)(f).(Function) }(func(f any) any { return g(func(x any) any { return f.(Function)(f).(Function)(x) }) }) } 26.GO github://feyeleanor/y_recursion_the_hard_way becomes
  • 14.
    INTRODUCING GO TYPESTO THE Y COMBINATOR type Function func(any) any func Recursor(f any) Function { return f.(Function)(f).(Function) } func Y(g Function) Function { return func(f any) Function { return f.(Function)(f).(Function) }(func(f any) any { return g(func(x any) any { return f.(Function)(f).(Function)(x) }) }) } func Y(g Function) Function { return Recursor( func(f any) any { return g(func(x any) any { return Recursor(f)(x) }) }) } 26.GO github://feyeleanor/y_recursion_the_hard_way becomes
  • 15.
    INTRODUCING GO TYPESTO THE Y COMBINATOR type Function func(any) any func Recursor(f any) Function { return f.(Function)(f).(Function) } type Transformer func(Function) Function func Y(g Function) Function { return Recursor( func(f any) any { return g(func(x any) any { return Recursor(f)(x) }) }) } func Y(g Transformer) Function { return Recursor( func(f any) any { return g(func(x any) any { return Recursor(f)(x) }) }) } 26.GO github://feyeleanor/y_recursion_the_hard_way becomes
  • 16.
    INTRODUCING GO TYPESTO THE Y COMBINATOR type Function func(any) any func Recursor(f any) Function { return f.(Function)(f).(Function) } type Transformer func(Function) Function func main() { ... factorial := Y(func(g Function) Function { return func(n any) (r any) { if n, ok := n.(int); ok { switch { case n == 0, n == 1: return 1 case n > 1: return n * g(n-1).(int) } } panic(n) } }) ... } func Y(g Transformer) Function { return Recursor( func(f any) any { return g(func(x any) any { return Recursor(f)(x) }) }) } 27.GO github://feyeleanor/y_recursion_the_hard_way
  • 17.
    SIMPLIFYING THE TYPEDY COMBINATOR Y g = (λf.(λx.f (x x)) (λx.f (x x))) g = (λx.g (x x)) (λx.g (x x)) = g ((λx.g (x x)) (λx.g (x x))) = g (Y g) github://feyeleanor/y_recursion_the_hard_way
  • 18.
    SIMPLIFYING THE TYPEDY COMBINATOR type Function func(any) any type Transformer func(Function) Function type Recursor func(Recursor) Function func (r Recursor) Apply(t Transformer) Function { return t(r(r)) } func Y(t Transformer) Function { g := func(r Recursor) Function { return func(x any) any { return r.Apply(t)(x) } } return g(g) } func main() { ... factorial := Y(func(g Function) Function { return func(n any) (r any) { if n, ok := n.(int); ok { switch { case n == 0, n == 1: return 1 case n > 1: return n * g(n-1).(int) } } panic(n) } }) ... } 28.GO github://feyeleanor/y_recursion_the_hard_way
  • 19.
    THE Y COMBINATORWITH GENERIC TYPES type Function[T, R any] func(T) R type Transformer[T, R any] func(Function[T, R]) Function[T, R] type Recursor[T, R any] func(Recursor[T, R]) Function[T, R] func (r Recursor[T, R]) Apply(t Transformer[T, R]) Function[T, R] { return t(r(r)) } func Y[T, R any](t Transformer[T, R]) Function[T, R] { g := func(r Recursor[T, R]) Function[T, R] { return func(x T) R { return r.Apply(t)(x) } } return g(g) } func main() { ... factorial := Y(func(g Function[int, int]) Function[int, int] { return func(n int) (r int) { switch { case n < 0: panic(n) case n > 1: return n * g(n - 1) } return 1 } }) ... } 29.GO github://feyeleanor/y_recursion_the_hard_way
  • 20.
    THE GENERIC YCOMBINATOR WITH AUTOMATIC RESULT CACHING func Y[T comparable, R any](t Transformer[T, R]) Function[T, R] { m := make(map[T]R) g := func(r Recursor[T, R]) Function[T, R] { return func(x T) (v R) { var ok bool if v, ok = m[x]; ok { return v } v = r.Apply(t)(x) m[x] = v fmt.Printf("Y: setting m[%v] = %vn", x, v) return v } } return g(g) } func main() { ... factorial := Y(func(g Function[int, int]) Function[int, int] { return func(n int) (r int) { switch { case n < 0: panic(n) case n > 1: return n * g(n-1) } return 1 } }) ... } 30.GO github://feyeleanor/y_recursion_the_hard_way
  • 22.
    LEANPUB://GONOTEBOOK [GITHUB |SLIDESHARE]://FEYELEANOR