Biến _ (gạch dưới) được chỉ định ở đâu và như thế nào?


81

Hầu hết đều biết _ý nghĩa đặc biệt của IRB với tư cách là người nắm giữ giá trị hoàn vốn cuối cùng, nhưng đó không phải là điều tôi đang hỏi ở đây.

Thay vào đó, tôi đang hỏi về _thời điểm được sử dụng làm tên biến trong mã Ruby-cũ thuần túy. Ở đây, nó dường như có một hành vi đặc biệt, giống như một “biến không quan tâm” (à la Prolog ). Dưới đây là một số ví dụ hữu ích minh họa hành vi độc đáo của nó:

lambda { |x, x| 42 }            # SyntaxError: duplicated argument name
lambda { |_, _| 42 }.call(4, 2) # => 42
lambda { |_, _| 42 }.call(_, _) # NameError: undefined local variable or method `_'
lambda { |_| _ + 1 }.call(42)   # => 43
lambda { |_, _| _ }.call(4, 2)  # 1.8.7: => 2
                                # 1.9.3: => 4
_ = 42
_ * 100         # => 4200
_, _ = 4, 2; _  # => 2

Tất cả những thứ này đều được chạy trực tiếp trong Ruby (có putsthêm vào) —không phải IRB — để tránh xung đột với chức năng bổ sung của nó.

Tuy nhiên, tất cả đều là kết quả thử nghiệm của riêng tôi, vì tôi không thể tìm thấy bất kỳ tài liệu nào về hành vi này ở bất kỳ đâu (phải thừa nhận rằng nó không phải là thứ dễ tìm kiếm nhất). Cuối cùng, tôi tò mò về cách tất cả những điều này hoạt động bên trong để tôi có thể hiểu rõ hơn chính xác điều gì đặc biệt _. Vì vậy, tôi yêu cầu tham chiếu đến tài liệu, và tốt nhất là mã nguồn Ruby (và có lẽ là RubySpec ) tiết lộ cách _hoạt động trong Ruby.

Lưu ý: hầu hết điều này nảy sinh từ cuộc thảo luận này với @Niklas B.

Câu trả lời:


54

Có một số xử lý đặc biệt trong nguồn để loại bỏ lỗi "tên đối số trùng lặp". Thông báo lỗi chỉ xuất hiện shadowing_lvar_genbên trong parse.y, phiên bản 1.9.3 trông như thế này :

static ID
shadowing_lvar_gen(struct parser_params *parser, ID name)
{
    if (idUScore == name) return name;
    /* ... */

idUScoređược định nghĩaid.c như thế này:

REGISTER_SYMID(idUScore, "_");

Bạn sẽ thấy cách xử lý đặc biệt tương tự trong warn_unused_var:

static void
warn_unused_var(struct parser_params *parser, struct local_vars *local)
{
    /* ... */
    for (i = 0; i < cnt; ++i) {
        if (!v[i] || (u[i] & LVAR_USED)) continue;
        if (idUScore == v[i]) continue;
        rb_compile_warn(ruby_sourcefile, (int)u[i], "assigned but unused variable - %s", rb_id2name(v[i]));
    }
}

Bạn sẽ nhận thấy rằng cảnh báo bị chặn trên dòng thứ hai của forvòng lặp.

Cách xử lý đặc biệt duy nhất _mà tôi có thể tìm thấy trong nguồn 1.9.3 ở trên: lỗi tên trùng lặp bị loại bỏ và cảnh báo biến không sử dụng bị loại bỏ. Ngoài hai thứ đó, _chỉ là một biến cũ đơn giản như bất kỳ biến nào khác. Tôi không biết bất kỳ tài liệu nào về tính đặc biệt (vị thành niên) của _.

Trong Ruby 2.0, idUScore == v[i]kiểm tra trong warn_unused_varđược thay thế bằng một cuộc gọi tới is_private_local_id:

if (is_private_local_id(v[i])) continue;
rb_warn4S(ruby_sourcefile, (int)u[i], "assigned but unused variable - %s", rb_id2name(v[i]));

is_private_local_idngăn chặn cảnh báo cho các biến bắt đầu bằng _:

if (name == idUScore) return 1;
/* ... */
return RSTRING_PTR(s)[0] == '_';

hơn là chỉ _chính nó. Vì vậy, 2.0 nới lỏng mọi thứ một chút.


1
Tôi tự hỏi liệu sự khác biệt trong hành vi lambda { |_, _| _ }.call(4, 2)giữa 1,8 và 1,9 sau đó chỉ là một tác dụng phụ không chủ ý? Như trong các trường hợp "bình thường" khi tên biến không thể trùng lặp, thứ tự mà chúng được gán là không quan trọng.
Andrew Marshall

3
@AndrewMarshall: Đúng vậy, tôi nghĩ vấn đề "4 vs 2" chỉ là sự tạo tác của cách 1,8 và 1,9 xử lý ngăn xếp. Lần duy nhất nó có thể đáng chú ý là |_,_,...|vì lỗi trùng lặp đã bị loại bỏ.
mu quá ngắn

2
@AndrewMarshall: Tôi tự hỏi liệu mọi người có đang đọc ChangeLog của nhau sau lưng chúng tôi không.
mu quá ngắn

2
Rất vui. Tôi cho rằng nó sẽ là một cái gì đó đơn giản như vậy, chỉ cần loại bỏ lỗi tên tham số kép. Ruby thực sự là một mớ hỗn độn lớn: D
Niklas B.

2
@mu: Tất nhiên, tôi vẫn chưa thấy một ngôn ngữ thông dịch thực sự rõ ràng (Lua đã đến gần).
Niklas B.

24

_là một định danh hợp lệ. Số nhận dạng không thể chỉ chứa dấu gạch dưới mà còn có thể là dấu gạch dưới.

_ = o = Object.new
_.object_id == o.object_id
# => true

Bạn cũng có thể sử dụng nó làm tên phương thức:

def o._; :_ end
o._
# => :_

Tất nhiên, nó không chính xác là một cái tên có thể đọc được, cũng như không chuyển bất kỳ thông tin nào cho người đọc về những gì biến đề cập đến hoặc những gì phương thức thực hiện.

IRB, đặc biệt, đặt _thành giá trị của biểu thức cuối cùng:

$ irb
> 'asd'
# => "asd"
> _
# => "asd"

Vì nó nằm trong mã nguồn , nó chỉ cần đặt _thành giá trị cuối cùng:

@workspace.evaluate self, "_ = IRB.CurrentContext.last_value"

Đã khám phá một số kho lưu trữ. Đây là những gì tôi tìm thấy:

Trên những dòng cuối cùng của tệp id.c, có lời gọi:

REGISTER_SYMID(idUScore, "_");

greplấy nguồn cho idUScoretôi hai kết quả có vẻ phù hợp:

shadowing_lvar_gendường như là cơ chế thông qua đó tham số chính thức của một khối thay thế một biến cùng tên tồn tại trong một phạm vi khác. Đây là hàm dường như nâng cao "tên đối số trùng lặp" SyntaxErrorvà cảnh báo "biến cục bộ bên ngoài bóng".

Sau khi grepnhập mã nguồn cho shadowing_lvar_gen, tôi tìm thấy thông tin sau trên bảng thay đổi cho Ruby 1.9.3 :

Thứ ba 11 tháng 12 01:21:21 2007 Yukihiro Matsumoto

  • parse.y (shadowing_lvar_gen): không có lỗi trùng lặp cho "_".

Có thể là nguồn gốc của dòng này :

if (idUScore == name) return name;

Từ điều này, tôi suy ra rằng trong một tình huống chẳng hạn proc { |_, _| :x }.call :a, :b, một _biến chỉ đơn giản là che khuất biến kia.


Đây là cam kết được đề cập . Về cơ bản, nó giới thiệu hai dòng sau:

if (!uscore) uscore = rb_intern("_");
if (uscore == name) return;

Từ một thời điểm mà idUScorethậm chí không tồn tại, dường như.


6
Điều này không giải thích tại sao lambda { |_, _| 42 }hoạt động trong khi lambda { |x, x| 42 }không.
Andrew Marshall

@AndrewMarshall, có vẻ như bạn đã đúng. |_, _|hoạt động, nhưng |__, __|không. _dường như có một số ý nghĩa đặc biệt, tôi sẽ xem liệu tôi có thể đào bất kỳ thông tin nào từ nguồn Ruby không.
Matheus Moreira

1
Nhân tiện, bản cập nhật của bạn, mặc dù có nhiều thông tin, không liên quan vì nó chỉ áp dụng cho IRb, mà tôi đã nêu cụ thể trong câu hỏi của mình mà tôi không hỏi về nó.
Andrew Marshall

1
+1 để tìm mục nhập ChangeLog. Mỗi người nên có một hacker C :)
mu quá ngắn

1
+1 cho mã nguồn IRB. IRB.CurrentContext.last_value rất thú vị để biết, ngay cả khi nó không liên quan đến câu hỏi như đã đặt ra. Tôi đã kết thúc ở đây từ một tìm kiếm trên google về IRB gạch dưới.
Josh
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.