Can I reference a lambda from within itself using Ruby?
Asked Answered
G

4

29

I want to be able to call an anonymous lambda from within itself using Ruby. Consider the following recursive block (returns a factorial). I know I can assign it to a variable, and that variable is within the scope of the lambda:

fac = lambda { |n| n == 1 ? 1 : n * fac.call(n - 1) }
fac.call(5)

But, I want to be able to do the following (for no practical reason as of yet, I'm just interested in exploring the language some more):

(lambda { |n| n == 1 ? 1 : n * self.call(n - 1) }).call(5)

I know that won't work, because self is the main object. Am I doing it wrong? Am I trying to do something that's not possible -- and if not, is this because of some theoretical restriction or is it simply not implemented in Ruby?

Grimsley answered 1/3, 2012 at 12:16 Comment(1)
Are you familiar with Y combinator? It might be not the best practical solution, but from theoretical point of view it's very interesting. If you don't, have a look at this article. Be careful, it might blow your brain out.Drear
E
13

In the following example, the lambda is still anonymous, but it has a reference. (Does that pass for anonymous?)

(l = lambda { l.call }).call

(Thanks to Niklas B. for pointing out the error in my original answer; I had only tested it in IRB and it worked there).

This of course ends in a SystemStackError: stack level too deep error, but it demonstrates the purpose.

Eijkman answered 1/3, 2012 at 13:54 Comment(0)
T
6

It seems that anonymous function really doesn't have any reference. You can check it by callee

lambda{ __callee__ }.call #=> nil

And without reference you can't call this function. I can propose to you only a little more clean variant:

(fac = lambda{ |n| n==1 ? 1 : n*fac.call(n-1) }).call(5)
Taproot answered 1/3, 2012 at 13:48 Comment(2)
It would really be a lot cleaner to just create a named function for that.Tuff
Yeah, my original version did wrap the lambda in parentheses and I ran a .call() on there. This isn't really a question of code brevity, though, it's more about just how deep down the functional rabbit hole Ruby can go. KL-7 has a comment above linking to an article describing the Y combinator which is a very interesting read.Grimsley
L
5
fact = -> (x){ x < 2 ? 1 : x*fact.(x-1)}

minimal function

Layby answered 22/4, 2019 at 17:24 Comment(2)
Almost. fact = -> (x){ x < 2 ? 1 : x*fact[x-1]} has one less non-whitespace character.Phocaea
@Phocaea Most of the time parentheses are preferred over square brackets. Simply because square brackets mostly denote access to data (array, hash, struct, etc). Using parentheses clearly shows that you're calling a method/proc/lambda. Square bracket usage isn't wrong, but might mislead the unaware code reader.Trot
E
3

In addition to KL-7's comment, here's a Y combinator solution:

lambda { |f|
  lambda { |x| x.call(x) }.call(
  lambda { |x| f.call( lambda { |v| x.call(x).call(v) } ) } )
}.call(
  lambda { |f|
    lambda { |n| n == 0 ? 1 : n * f.call(n - 1) }
  }
).call(5) #=> 120

You would normally split these:

y = lambda { |f|
  lambda { |x| x.call(x) }.call(
  lambda { |x| f.call( lambda { |v| x.call(x).call(v) } ) } )
}

fac = y.call(
  lambda { |f| lambda { |n| n == 0 ? 1 : n * f.call(n - 1) } }
)

fac.call(5) #=> 120

Note that although fac is being assigned, it is not used within the lambda.

I'd use Ruby's -> syntax and .() instead of .call():

y = ->(f) {
  ->(x) { x.(x) }.(
  ->(x) { f.(->(v) { x.(x).(v) }) } )
}

fac = y.(->(f) {
  ->(n) { n == 0 ? 1 : n * f.(n - 1) }
})

fac.(5) #=> 120

The y invocation can be simplified a bit by using curry:

y = ->(f) {
  ->(x) { x.(x) }.(
  ->(x) { f.curry.(->(v) { x.(x).(v) }) } )
}

fac = y.(
  ->(f, n) { n == 0 ? 1 : n * f.(n - 1) }
)

fac.(5) #=> 120
Enantiomorph answered 14/12, 2015 at 16:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.