2013-08-31 52 views
7

Với hai miếng mã sau đây:

def hello(z) 
    "hello".gsub(/(o)/, &z) 
end 
z = proc {|m| p $1} 
hello(z) 
# prints: nil 

def hello 
    z = proc {|m| p $1} 
    "hello".gsub(/(o)/, &z) 
end 
hello 
# prints: "o" 

Tại sao các kết quả đầu ra của hai miếng các mã khác nhau? Có cách nào để vượt qua một khối để gsub từ bên ngoài của định nghĩa phương pháp để các biến $1, $2 sẽ được đánh giá theo cùng một cách như nếu khối đã được đưa ra bên trong định nghĩa phương pháp?

Trả lời

2

Tại sao đầu ra lại khác?

Một proc trong ruby ​​có phạm vi từ vựng. Điều này có nghĩa rằng khi nó tìm thấy một biến không được xác định, nó được giải quyết trong bối cảnh proc là được xác định, không phải được gọi là. Điều này giải thích hành vi của mã của bạn.

Bạn có thể thấy khối được xác định trước regexp và điều này có thể gây nhầm lẫn. Vấn đề liên quan đến một biến ruby ​​ma thuật, và nó hoạt động khá khác so với các biến khác. Citing @JörgWMittag

Nó khá đơn giản, thực sự: lý do tại sao $ SAFE không hoạt động như bạn mong đợi từ một biến toàn cầu là vì nó không phải là một biến toàn cục. Đó là một điều kỳ diệu kỳ lân.

Có một vài trong số những phép thuật kỳ lân trong Ruby, và chúng không được ghi chép đầy đủ (không phải là tài liệu, trên thực tế), vì các nhà phát triển các triển khai Ruby đã phát hiện ra một cách khó khăn. Những thingamajiggies tất cả các hành vi khác nhau và (dường như) không nhất quán, và khá nhiều hai điều duy nhất họ có điểm chung là họ trông giống như các biến toàn cầu nhưng không cư xử như họ.

Một số có phạm vi địa phương. Một số có phạm vi thread-local. Một số thay đổi kỳ diệu mà không có ai từng giao cho họ. Một số có ý nghĩa kỳ diệu cho người phiên dịch và thay đổi cách ngôn ngữ hoạt động. Một số có ngữ nghĩa kì lạ khác gắn liền với chúng.

Nếu bạn đang thực sự lên để tìm chính xác cách thức $1$2 biến làm việc, tôi giả sử chỉ "tài liệu", bạn sẽ tìm thấy là rubyspec, đó là một spec cho ruby ​​thực hiện một cách khó khăn bởi các folks Rubinus. Có một hack tốt đẹp, nhưng được chuẩn bị cho cơn đau.


Có cách nào để vượt qua một khối để Gsub từ bối cảnh khác với $ 1, $ 2 biến thiết lập đúng cách?

Bạn có thể đạt được những gì bạn muốn với sửa đổi sau này (nhưng tôi đặt cược bạn đã biết rằng)

require 'pp' 
def hello(z) 
    #z = proc {|m| pp $1} 
    "hello".gsub(/(o)/, &z) 
end 
z = proc {|m| pp m} 
hello(z) 

Tôi không nhận thức được một cách để thay đổi phạm vi của một proc on the fly . Nhưng bạn có thực sự muốn làm điều này?

+0

Ngay cả trong mã này ''hello'.gsub (/ (e) /) {đặt $ 1}' Proc.new được gọi trước gsub: đây là cách mọi ngôn ngữ lập trình hoạt động - khối là cùng một đối số với những người khác. Nó cần phải được xây dựng trước khi vượt qua nó ở đâu đó. Vì vậy, tôi không nghĩ rằng câu trả lời của bạn giải thích bất cứ điều gì –

+0

@ BogdanGusiev 'Proc.new' được gọi trước' gsub', nhưng khối không phải là. Nó chỉ được phân tích cú pháp. Một khối không phải là một đối số. – sawa

+1

Có, khối không được gọi trước 'gsub' trong cả hai biến thể mã. Vì vậy, sự khác biệt là số lượng các cuộc gọi để vượt qua khối để gsub: đó có thể là 1 hoặc 2. Nhưng nó vẫn không bao giờ bằng 0. '$ 1' biến không bao giờ có mặt trong bối cảnh nơi' z' được định nghĩa. Biến số –

1

Hai phiên bản khác nhau vì biến số $1 là chuỗi địa phương và phương pháp cục bộ.Trong ví dụ đầu tiên, $1 chỉ tồn tại trong khối bên ngoài phương thức hello. Trong ví dụ thứ hai, $1 tồn tại bên trong phương thức hello.

Không có cách nào để chuyển $ 1 trong khối thành gsub từ bên ngoài định nghĩa phương thức.

Lưu ý rằng gsub chuyển chuỗi đối sánh vào khối, vì vậy z = proc { |m| pp m } sẽ chỉ hoạt động miễn là cụm từ thông dụng của bạn chỉ chứa toàn bộ kết quả trùng khớp. Ngay khi biểu thức chính quy của bạn chứa bất kỳ thông tin nào khác ngoài tham chiếu bạn muốn, bạn sẽ không may mắn.

Ví dụ: "hello".gsub(/l(o)/) { |m| m } =>hello, vì toàn bộ chuỗi đối sánh được chuyển đến khối.

Trong khi đó, "hello".gsub(/l(o)/) { |m| $1 } =>helo, vì l khớp với khối bị xóa, tất cả những gì chúng tôi quan tâm là bị bắt giữ o.

Giải pháp của tôi là để match biểu thức chính quy, sau đó vượt qua các đối tượng MatchData vào khối:

require 'pp' 

def hello(z) 
    string = "hello" 
    regex = /(o)/ 

    m = string.match(regex) 
    string.gsub(regex, z.call(m)) 
end 

z = proc { |m| pp m[1] } 
pp hello(z) 
1

Những điều như $1, $2 hành vi như biến cục bộ, mặc dù lãnh đạo doanh nghiệp $. Bạn có thể thử các mã dưới đây để chứng minh điều này:

def foo 
    /(hell)o/ =~ 'hello' 
    $1 
end 

def bar 
    $1 
end 

foo #=> "hell" 
bar #=> nil 

Vấn đề của bạn là do proc z được định nghĩa bên ngoài phương pháp hello, vì vậy z truy cập vào $1 trong bối cảnh main, nhưng gsub đặt $1 trong bối cảnh của phương thức hello.