Ruby: convert proc to lambda?
Asked Answered
E

5

16

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!
Euphonium answered 1/6, 2010 at 0:13 Comment(0)
A
22

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
Algernon answered 1/6, 2010 at 0:54 Comment(4)
Thanks! Very helpful :) The fact that define_method ultimately produced a lambda was what prompted my confusion.Duel
Fun question time: how do you do this in jruby?Priestridden
Answer to my fun question: #13239838Priestridden
If you're interested in getting this to be a stable interface. Can you please comment on this issue with why you wanted to convert a proc to a lambda in the first place? bugs.ruby-lang.org/issues/9777#change-46353Priestridden
C
7

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.

Crept answered 8/5, 2014 at 2:18 Comment(0)
S
5

Here is possible solution:

class Proc
  def to_lambda
    return self if lambda?

    # Save local reference to self so we can use it in module_exec/lambda scopes
    source_proc = self

    # Convert proc to unbound method
    unbound_method = Module.new.module_exec do
      instance_method( define_method( :_proc_call, &source_proc ))
    end

    # Return lambda which binds our unbound method to correct receiver and calls it with given args/block
    lambda do |*args, &block|
      # If binding doesn't changed (eg. lambda_obj.call) then bind method to original proc binding,
      # otherwise bind to current binding (eg. instance_exec(&lambda_obj)).
      unbound_method.bind( self == source_proc ? source_proc.receiver : self ).call( *args, &block )
    end
  end

  def receiver
    binding.eval( "self" )
  end
end

p1 = Proc.new { puts "self = #{self.inspect}" }
l1 = p1.to_lambda

p1.call #=> self = main
l1.call #=> self = main

p1.call( 42 ) #=> self = main
l1.call( 42 ) #=> ArgumentError: wrong number of arguments (1 for 0)

42.instance_exec( &p1 ) #=> self = 42
42.instance_exec( &l1 ) #=> self = 42

p2 = Proc.new { return "foo" }
l2 = p2.to_lambda

p2.call #=> LocalJumpError: unexpected return
l2.call #=> "foo"

Should work on Ruby 2.1+

Stewart answered 25/7, 2014 at 23:35 Comment(0)
P
3

Cross ruby compatiable library for converting procs to lambdas: https://github.com/schneems/proc_to_lambda

The Gem: http://rubygems.org/gems/proc_to_lambda

Priestridden answered 12/7, 2013 at 18:42 Comment(0)
P
0

The above code doesn't play nicely with instance_exec but I think there is simple fix for that. Here I have an example which illustrates the issue and solution:

# /tmp/test.rb
def to_lambda1(&block)
  obj = Object.new
  obj.define_singleton_method(:_,&block)
  obj.method(:_).to_proc
end

def to_lambda2(&block)
  Object.new.define_singleton_method(:_,&block).to_proc
end


l1 = to_lambda1 do
  print "to_lambda1: #{self.class.name}\n"
end
print "l1.lambda?: #{l1.lambda?}\n"

l2 = to_lambda2 do
  print "to_lambda2: #{self.class.name}\n"
end
print "l2.lambda?: #{l2.lambda?}\n"

class A; end

A.new.instance_exec &l1
A.new.instance_exec &l2

to_lambda1 is basically the implementation proposed by Mark, to_lambda2 is a "fixed" code.

The output from above script is:

l1.lambda?: true
l2.lambda?: true
to_lambda1: Object
to_lambda2: A

In fact I'd expect instance_exec to output A, not Object (instance_exec should change binding). I don't know why this work differently, but I suppose that define_singleton_method returns a method that is not yet bound to Object and Object#method returns an already bound method.

Pow answered 28/1, 2014 at 15:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.