2010-06-01 11 views
13

Có thể chuyển đổi một proc có hương vị proc thành một Proc có hương vị lambda không?Ruby: chuyển đổi proc thành lambda?

Bit ngạc nhiên rằng điều này không làm việc, ít nhất là trong 1.9.2:

my_proc = proc {|x| x} 
my_lambda = lambda &p 
my_lambda.lambda? # => false! 

Trả lời

20

một Đây là một chút khó khăn để theo dõi. Nhìn vào tài liệu cho Proc#lambda? for 1.9, có một cuộc thảo luận khá dài về sự khác biệt giữa proc s và lamdba s.

Điều gì xảy ra là số lambda thi hành số lượng đối số chính xác và proc thì không. Và từ tài liệu, khoảng cách duy nhất để chuyển đổi một proc thành một lambda được hiển thị trong ví dụ này:

define_method luôn xác định một phương pháp mà không có thủ đoạn, thậm chí nếu một tổ chức phi lambda Proc đối tượng được đưa ra. Đây là ngoại lệ duy nhất mà các thủ thuật không được bảo tồn.

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

Nếu bạn muốn tránh gây ô nhiễm bất kỳ lớp, bạn chỉ có thể xác định một phương pháp singleton vào một đối tượng ẩn danh để ép buộc một proc đến một 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 
+0

Cảm ơn! Rất hữu ích :) Thực tế là define_method cuối cùng tạo ra một lambda là những gì nhắc nhở sự nhầm lẫn của tôi. –

+3

Thời gian câu hỏi thú vị: làm thế nào để bạn làm điều này trong jruby? – Schneems

+1

Trả lời cho câu hỏi thú vị của tôi: http://stackoverflow.com/questions/13239338/convert-bloc-to-lambda-in-jruby – Schneems

0

Đoạn mã trên không chơi độc đáo với instance_exec nhưng tôi nghĩ rằng có sửa chữa đơn giản cho điều đó. Ở đây tôi có một ví dụ minh họa các vấn đề và giải pháp:

# /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 về cơ bản là thực hiện Mark đề nghị, to_lambda2 là một "cố định" mã.

Kết quả từ kịch bản trên là:

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

Trong thực tế tôi mong đợi instance_exec để đầu ra A, không Object (instance_exec nên thay đổi ràng buộc). Tôi không biết tại sao công việc này khác đi, nhưng tôi cho rằng define_singleton_method trả về một phương thức chưa được gắn với ObjectObject#method trả về một phương thức đã bị ràng buộc.

4

Đó là không có thể chuyển đổi một proc thành lambda mà không gặp sự cố. Câu trả lời của Mark Rushakoff không bảo toàn giá trị của self trong khối, vì self trở thành Object.new. Câu trả lời của Pawel Tomulik không thể làm việc với Ruby 2.1, bởi vì define_singleton_method hiện trả về một Biểu tượng, vì vậy to_lambda2 trả về :_.to_proc.

câu trả lời của tôi là cũng sai:

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 

Nó bảo tồn giá trị của self trong khối:

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 

Nhưng nó không thành công với instance_exec:

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

tôi phải sử dụng block.binding.eval('self') để tìm đúng đối tượng. Tôi đặt phương thức của mình vào một mô-đun ẩn danh, vì vậy nó không bao giờ gây ô nhiễm bất kỳ lớp nào. Sau đó, tôi ràng buộc phương pháp của tôi với đối tượng chính xác. Điều này làm việc mặc dù các đối tượng không bao giờ bao gồm các mô-đun! Phương pháp ràng buộc tạo ra một lambda.

66.instance_exec &q không thành công vì q bí mật là phương thức được liên kết với 42instance_exec không thể khởi động lại phương thức. Người ta có thể sửa lỗi này bằng cách mở rộng q để hiển thị phương thức không gắn kết và xác định lại instance_exec để liên kết phương thức không gắn kết với một đối tượng khác. Mặc dù vậy, module_execclass_exec vẫn không thành công.

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) 

Vấn đề là Hash.class_exec &$q định nghĩa Array#greet và không Hash#greet. (Mặc dù $q là bí mật một phương thức của một mô-đun ẩn danh, nó vẫn định nghĩa các phương thức trong Array, không phải trong mô-đun ẩn danh.) Với proc ban đầu, Hash.class_exec &$p sẽ xác định Hash#greet. Tôi kết luận rằng convert_to_lambda là sai vì nó không hoạt động với class_exec.

3

Đây là khả năng giải pháp:

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" 

nên làm việc trên Ruby 2.1 +