Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby: convert proc to lambda?

Is it possible to convert a proc-flavored Proc into a lambda-flavored Proc?

Bit surprised that this doesn't work, at least in 1.9.2:

my_proc = proc {|x| x}
my_lambda = lambda &p
my_lambda.lambda? # => false!
like image 439
Alan O'Donnell Avatar asked Jun 01 '10 00:06

Alan O'Donnell


People also ask

What is difference between lambda and Proc in Ruby?

In Ruby, a lambda is an object similar to a proc. Unlike a proc, a lambda requires a specific number of arguments passed to it, and it return s to its calling method rather than returning immediately. proc_demo = Proc. new { return "Only I print!" }

How do you use Proc in Ruby?

A Proc object is an encapsulation of a block of code, which can be stored in a local variable, passed to a method or another Proc, and can be called. Proc is an essential concept in Ruby and a core of its functional programming features.

What is the difference between procs and blocks?

When using parameters prefixed with ampersands, passing a block to a method results in a proc in the method's context. Procs behave like blocks, but they can be stored in a variable. Lambdas are procs that behave like methods, meaning they enforce arity and return as methods instead of in their parent scope.


2 Answers

This one was a bit tricky to track down. Looking at the docs for Proc#lambda? for 1.9, there's a fairly lengthy discussion about the difference between procs and lamdbas.

What it comes down to is that a lambda enforces the correct number of arguments, and a proc doesn't. And from that documentation, about the only way to convert a proc into a lambda is shown in this example:

define_method always defines a method without the tricks, even if a non-lambda Proc object is given. This is the only exception which the tricks are not preserved.

 class C
   define_method(:e, &proc {})
 end
 C.new.e(1,2)       => ArgumentError
 C.new.method(:e).to_proc.lambda?   => true

If you want to avoid polluting any class, you can just define a singleton method on an anonymous object in order to coerce a proc to a lambda:

def convert_to_lambda &block
  obj = Object.new
  obj.define_singleton_method(:_, &block)
  return obj.method(:_).to_proc
end

p = Proc.new {}
puts p.lambda? # false
puts(convert_to_lambda(&p).lambda?) # true

puts(convert_to_lambda(&(lambda {})).lambda?) # true
like image 97
Mark Rushakoff Avatar answered Sep 19 '22 15:09

Mark Rushakoff


It is not possible to convert a proc to a lambda without trouble. The answer by Mark Rushakoff doesn't preserve the value of self in the block, because self becomes Object.new. The answer by Pawel Tomulik can't work with Ruby 2.1, because define_singleton_method now returns a Symbol, so to_lambda2 returns :_.to_proc.

My answer is also wrong:

def convert_to_lambda &block
  obj = block.binding.eval('self')
  Module.new.module_exec do
    define_method(:_, &block)
    instance_method(:_).bind(obj).to_proc
  end
end

It preserves the value of self in the block:

p = 42.instance_exec { proc { self }}
puts p.lambda?      # false
puts p.call         # 42

q = convert_to_lambda &p
puts q.lambda?      # true
puts q.call         # 42

But it fails with instance_exec:

puts 66.instance_exec &p    # 66
puts 66.instance_exec &q    # 42, should be 66

I must use block.binding.eval('self') to find the correct object. I put my method in an anonymous module, so it never pollutes any class. Then I bind my method to the correct object. This works though the object never included the module! The bound method makes a lambda.

66.instance_exec &q fails because q is secretly a method bound to 42, and instance_exec can't rebind the method. One might fix this by extending q to expose the unbound method, and redefining instance_exec to bind the unbound method to a different object. Even so, module_exec and class_exec would still fail.

class Array
  $p = proc { def greet; puts "Hi!"; end }
end
$q = convert_to_lambda &$p
Hash.class_exec &$q
{}.greet # undefined method `greet' for {}:Hash (NoMethodError)

The problem is that Hash.class_exec &$q defines Array#greet and not Hash#greet. (Though $q is secretly a method of an anonymous module, it still defines methods in Array, not in the anonymous module.) With the original proc, Hash.class_exec &$p would define Hash#greet. I conclude that convert_to_lambda is wrong because it doesn't work with class_exec.

like image 26
George Koehler Avatar answered Sep 20 '22 15:09

George Koehler