2010-06-22 13 views
17

Tôi khá mới với Ruby, và cho đến nay, tìm cách sử dụng "binding" objects là một trong những điểm đau lớn nhất đối với tôi. Nếu tôi đọc tài liệu chính xác, chúng gần như hoàn toàn mờ đục. Để truy cập phạm vi bên trong đối tượng liên kết, bạn phải có một chuỗi mã Ruby và eval nó bằng cách sử dụng ràng buộc.Is 'eval' là cách duy nhất để tương tác với các đối tượng liên kết trong Ruby?

Có lẽ tôi chỉ là một người thuần túy từ một trường khác, nhưng tôi bị dị ứng với các cấu trúc 'eval' dựa trên chuỗi, nói chung. Có cách nào để thực hiện bất kỳ điều nào sau đây, an toàn và trong trường hợp chung, được đưa ra một đối tượng ràng buộc:

  1. Liệt kê các định danh trong phạm vi liên kết thể hiện hoặc truy lục hàm băm.
  2. Đặt giá trị của biến cục bộ trong liên kết bằng với biến số cục bộ trong ngữ cảnh bên ngoài. Lý tưởng nhất, điều này sẽ làm việc nói chung, ngay cả khi giá trị là một tham chiếu đối tượng, xử lý tệp hoặc một số thực thể phức tạp khác.
  3. (phần mở rộng 2 :) Cho một băm, đặt người dân địa phương trong ràng buộc cho mỗi mục nhập.
  4. Tốt hơn, với một băm xây dựng một ràng buộc với chỉ cấu trúc ngôn ngữ cơ bản và tên trong băm trong phạm vi.

Về cơ bản, tôi muốn biết cái nào trong số đó là có thể và cách thực hiện cái nào. Tôi tưởng tượng rằng các giải pháp cho mỗi giải pháp sẽ liên quan chặt chẽ, đó là lý do tại sao tôi đưa tất cả điều này vào một câu hỏi duy nhất.

Ngoài ra, có cách nào để đánh giá mã đã được phân tích cú pháp trong ngữ cảnh ràng buộc, tương tự cú pháp eval BLOCK của Perl không?

+1

câu hỏi này cần thêm @ Jörg W Mittag: P –

Trả lời

7

On tìm kiếm nhiều hơn, tôi tìm thấy câu trả lời cho ít nhất một phần của câu hỏi của tôi:

Dựa trên: http://wikis.onestepback.org/index.cgi/Tech/Ruby/RubyBindings.rdoc/style/print

Phần còn lại là từ thử nghiệm sau khi con trỏ hữu ích Jim Shubert của.

  1. này có thể được thực hiện bằng cách eval -ing local_variables, instance_variablesglobal_variables bên trong ràng buộc.
  2. Bạn có thể làm điều gì đó như được mô tả bên dưới, được cung cấp var_name, new_val, my_binding (cú pháp có thể không hoàn hảo hoặc không thể sửa đổi, cảm thấy tự do để đề xuất trong nhận xét. làm thế nào để làm điều đó cũng sẽ được thực hiện.)
  3. Bạn có thể đơn giản lấy (2) và lặp lại băm để làm điều này.
  4. Xem khối mã thứ hai bên dưới. Ý tưởng là bắt đầu với TOPLEVEL_BINDING, mà tôi tin bình thường chỉ bao gồm các biến toàn cầu.

Điều này liên quan đến việc sử dụng chuỗi eval. Tuy nhiên, không có giá trị biến nào được mở rộng thành các chuỗi liên quan, do đó, nó sẽ khá an toàn nếu được sử dụng như được mô tả và sẽ hoạt động để 'chuyển vào' các giá trị biến phức tạp.

Cũng lưu ý rằng luôn có thể thực hiện eval var_name, my_binding để nhận giá trị của biến.Lưu ý rằng trong tất cả các cách sử dụng này, điều quan trọng là biến số tên của bạn an toàn để eval, vì vậy, lý tưởng nhất là không phải đến từ bất kỳ kiểu nhập nào của người dùng.

Thiết lập một biến bên trong một ràng buộc cho var_name, new_val, my_binding:

# the assignment to nil in the eval coerces the variable into existence at the outer scope 
setter = eval "#{var_name} = nil; lambda { |v| #{var_name} = v }", my_binding 
setter.call(new_val) 

Xây dựng một "bespoke" ràng buộc:

my_binding = eval "lambda { binding }", TOPLEVEL_BINDING # build a nearly-empty binding 
# set_in_binding is based on the above snippet 
vars_to_include.each { |var_name, new_val| set_in_binding(var_name, new_val, my_binding) } 
+1

Nghiên cứu tuyệt vời! –

+3

Chấp nhận điều này, nhưng nếu có ai đến cùng, người biết cách tránh sử dụng chuỗi eval cho công cụ này, vui lòng đóng góp kiến ​​thức của bạn. –

3

Walter, bạn sẽ có thể tương tác trực tiếp với các ràng buộc . Tôi đã không làm việc nhiều với các ràng buộc trước đây, nhưng tôi đã chạy một vài điều trong irb:

[email protected]:~> irb 
irb(main):001:0> eval "self", TOPLEVEL_BINDING 
=> main 
irb(main):002:0> eval "instance_variables", TOPLEVEL_BINDING 
=> [] 
irb(main):003:0> eval "methods", TOPLEVEL_BINDING 
=> ["irb_kill", "inspect", "chws", "install_alias_method", .... 

Tôi cũng có Metaprogramming Ruby không nói nhiều về ràng buộc. Tuy nhiên, nếu bạn chọn này lên, ở phần cuối của trang 144 nó nói

Trong một nghĩa nào đó, bạn có thể thấy Binding đối tượng như một hình thức "tinh khiết" đóng cửa hơn khối, vì các đối tượng này chứa một phạm vi nhưng không chứa mã số .

Và, trên trang ngược lại, nó cho thấy mày mò với mã IRB của (loại bỏ hai args cuối cùng để gọi eval) để xem cách nó sử dụng các ràng buộc:

// ctwc/IRB/workspace.rb
eval (tuyên bố, @binding) #, tệp, dòng)

Và ... Tôi sẽ đề nghị chuyển lambda, nhưng tôi thấy bạn vừa trả lời, vì vậy tôi sẽ để đề xuất để nghiên cứu thêm.

+0

Cảm ơn, cấu trúc 'instance_variables' là mới đối với tôi, điều đó mang lại cho tôi một số điểm nhảy vọt hơn. Hướng dẫn kiểm tra irb cũng hữu ích. Tôi đã upvoted này nhưng không chấp nhận nó, vì nó không trực tiếp trả lời các câu hỏi như được hỏi. –

+0

Walter, cảm ơn. Tôi hy vọng tôi ít nhất đã cho bạn một số ý tưởng để làm việc và tìm câu trả lời. Tôi chắc chắn rằng những gì bạn đã hỏi là có thể, xem xét rằng irb hoạt động trực tiếp với các ràng buộc (irb là một lớp ruby ​​trong chính nó). –

1

Bạn có thể giải thích chính xác bạn đang cố gắng làm gì không? Vui lòng cung cấp một số mã cho biết bạn muốn nó hoạt động như thế nào. Có thể có một cách tốt hơn và an toàn hơn để hoàn thành những gì bạn muốn.

Tôi sẽ chụp ảnh để đoán trường hợp sử dụng thông thường của bạn. Cho một Hash: {: a => 11,: b => 22}

Bạn muốn có một môi trường thực thi tối thiểu, tương đối biệt lập nơi bạn có thể truy cập các giá trị của băm dưới dạng biến cục bộ. (Tôi không chắc chắn lý do tại sao bạn cần chúng là người dân địa phương, ngoại trừ có thể nếu bạn đang viết một DSL, hoặc nếu bạn đã viết mã mà bạn không muốn thích ứng để truy cập vào Hash.)

Đây là nỗ lực của tôi. Để đơn giản, tôi giả sử bạn chỉ sử dụng các ký hiệu là các khóa Hash.

 
class MyBinding 
    def initialize(varhash); @vars=varhash; end 
    def method_missing(methname, *args) 
    meth_s = methname.to_s 
    if meth_s =~ /=\z/ 
     @vars[meth_s.sub(/=\z/, '').to_sym] = args.first 
    else 
     @vars[methname] 
    end 
    end 
    def eval(&block) 
    instance_eval &block 
    end 
end 

Sample Cách sử dụng:

 
hash = {:a => 11, :b => 22} 
mb = MyBinding.new hash 
puts mb.eval { a + b } 
# setting values is not as natural: 
mb.eval { self.a = 33 } 
puts mb.eval { a + b } 

Một số hãy cẩn thận:

1) Tôi không nâng cao một NameError nếu biến không tồn tại, nhưng một sự thay thế đơn giản sửa chữa rằng:

 
def initialize(varhash) 
    @vars = Hash.new {|h,k| raise NameError, "undefined local variable or method `#{k}'" } 
    @vars.update(varhash) 
end 

2) Các quy tắc phạm vi thông thường là như vậy nếu một địa phương tồn tại có tên giống như một phương pháp, địa phương có p recedence, trừ khi bạn thực hiện một lời gọi phương thức một cách rõ ràng, như a().Lớp trên có hành vi ngược lại; phương thức này được ưu tiên. Để có hành vi "bình thường", bạn cần phải ẩn tất cả (hoặc hầu hết) các phương thức chuẩn, như #object_id. ActiveSupport cung cấp một lớp BlankSlate cho mục đích này; nó chỉ giữ 3 phương pháp:

 __send__, instance_eval, __id__

. Để sử dụng nó, chỉ cần làm cho MyBinding kế thừa từ BlankSlate. Bên cạnh 3 phương pháp này, bạn cũng sẽ không thể có người dân địa phương có tên "eval" hoặc "method_missing".

3) Đặt địa phương không tự nhiên, vì nó cần "tự" để nhận cuộc gọi phương thức. Không chắc chắn nếu có một giải pháp cho việc này.

4) Khối eval có thể gây rối với hàm băm @vars.

5) Nếu bạn có một var địa phương thực trong phạm vi mà bạn gọi là mb.eval, có cùng tên với một trong các khóa băm, địa phương thực sẽ được ưu tiên ... đây có lẽ là nhược điểm lớn nhất bởi vì Các lỗi tinh vi có thể bị xâm nhập.

Sau khi nhận ra những nhược điểm đối với kỹ thuật của tôi, tôi khuyên bạn nên sử dụng Hash trực tiếp để giữ một tập các biến, trừ khi tôi thấy lý do khác. Nhưng nếu bạn vẫn muốn sử dụng eval gốc, bạn có thể cải thiện độ an toàn bằng cách sử dụng Regexps để tránh chèn mã và cài đặt "cục bộ" $ SAFE để cao hơn cho cuộc gọi eval bằng cách sử dụng Proc, như vậy:

 
proc { $SAFE = 1; eval "do_some_stuff" }.call # returns the value of eval call 
0

Dưới đây là một số mã cho giải pháp dựa trên Hash.

 
class ScopedHash 
    def initialize(varhash) 
    # You can use an OpenStruct instead of a Hash, but the you lose the NameError feature. 
    # OpenStructs also don't have the ability to list their members unless you call a protected method 
    @vars = Hash.new {|h,k| raise NameError, "undefined local variable or method `#{k}'" } 
    @vars.update(varhash) 
    end 
    def eval 
    yield @vars 
    end 
end 

if __FILE__ == $0 
    # sample usage 
    hash = {:a => 11, :b => 22} 
    sh = ScopedHash.new hash 
    puts sh.eval {|v| v[:a] + v[:b] } 
    sh.eval {|v| v[:a] = 33 } 
    puts sh.eval {|v| v[:a] + v[:b] } 
    sh.eval{|v| v[:c] } # raises NameError 
end 

Vì vậy, thay vì sử dụng người dân địa phương, bạn chỉ cần truy cập vào băm có lợi. Tôi nghĩ rằng có rất ít lý do khiến người ta buộc phải thao túng đối tượng Binding; thường có những cách thức sạch hơn để hoàn thành nhiệm vụ.

+0

Bất kể sự cố gắng thao túng Bindings, đó là chủ đề của câu hỏi này. Đề xuất này không trả lời câu hỏi. -1 –

+0

Đã được cấp, nó không nằm trong chủ đề. Tôi đang cố gắng hữu ích và vẫn muốn - bạn có thể cung cấp trường hợp sử dụng không? Hoặc có lẽ mục tiêu của bạn là chỉ thử nghiệm cụ thể với Binding như một ngôn ngữ xây dựng và không quan tâm đến bất cứ điều gì mà không sử dụng Bindings? Nếu vậy, chỉ cần nói như vậy và tôi sẽ cố gắng nghĩ về một giải pháp tốt hơn với Bindings. – Kelvin

+0

Câu hỏi đặt ra là câu hỏi sau. Tôi nghĩ rằng tôi đã đưa ra nó trong khi thử nghiệm với các mẫu, nhưng lý do thực sự của tôi cho yêu cầu nó là để tìm hiểu như thế nào ngôn ngữ xử lý các đối tượng ràng buộc Binding. Vâng, đó là nó - Tôi muốn tạo ra một không gian tên được điều khiển cho lời gọi mẫu chỉ với các giá trị từ một bộ băm cụ thể, mà không cần phải thay đổi cách khuôn mẫu được viết để nó đi qua một băm. –

Các vấn đề liên quan