Де і як вказана змінна _ (підкреслення)?


81

Більшість з них знає про _спеціальному значенні «S в IRB в якості власника для останнього значення, що повертається, але це НЕ то , що я питаю тут.

Натомість я запитую про те, _коли використовується як ім’я змінної в простому-старому-Ruby-коді. Тут він, мабуть, має особливу поведінку, схожу на “змінну, яка не хвилює” (à la Prolog ). Ось кілька корисних прикладів, що ілюструють його унікальну поведінку:

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

Усі вони запускались у Ruby безпосередньо (з putsдоданими s), а не IRB, щоб уникнути конфлікту з його додатковою функціональністю.

Це все результат мого власного експерименту, оскільки я ніде не можу знайти жодної документації щодо цієї поведінки (правда, це не найпростіша річ для пошуку). Зрештою, мені цікаво, як все це працює внутрішньо, щоб я міг краще зрозуміти, в чому особливість _. Тому я прошу посилання на документацію та, бажано, вихідний код Ruby (і, можливо, RubySpec ), який розкриває, як _поводиться Ruby.

Примітка: більша частина цього виникла в результаті дискусії з @Niklas B.

Відповіді:


54

У джерелі є спеціальна обробка для придушення помилки "дублювання імені аргументу". Повідомлення про помилку з'являється лише shadowing_lvar_genвсередині parse.y, версія 1.9.3 виглядає так :

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

і idUScoreце визначено вid.c такий спосіб:

REGISTER_SYMID(idUScore, "_");

Ви побачите подібну спеціальну обробку в 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]));
    }
}

Ви помітите, що попередження вимкнено у другому рядку forциклу.

Єдина особлива обробка цього, _яку я міг знайти у джерелі 1.9.3, є вище: помилка дублікатів імен пригнічується, а попередження про невикористану змінну пригнічується. Крім цих двох речей, _це просто проста стара змінна, як і будь-яка інша. Я не знаю жодної документації про (незначну) особливість _.

У Ruby 2.0 idUScore == v[i]тест in warn_unused_varзамінюється на виклик 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_idпридушує попередження для змінних, які починаються з _:

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

а не просто _сама. Тож 2.0 трохи послаблює ситуацію.


1
Цікаво, чи різниця в поведінці lambda { |_, _| _ }.call(4, 2)між 1,8 і 1,9 - це просто ненавмисний побічний ефект? Як і в "звичайних" обставинах, коли ім'я змінної не може бути продубльовано, порядок, у якому вони призначені, є несуттєвим.
Ендрю Маршалл

3
@AndrewMarshall: Так, я думаю, що проблема "4 проти 2" - це лише артефакт того, як 1.8 і 1.9 обробляють стек. Єдиний раз, коли це було б помітно, це |_,_,...|тому, що помилка дубліката була придушена.
мю занадто коротке

2
@AndrewMarshall: Цікаво, чи всі читають журнали змін один одного за нашими спинами.
му занадто коротке ,

2
Приємна знахідка. Я припускав, що це буде щось таке просте, просто придушуючи помилку подвійного імені параметра. Рубі справді величезна каша: D
Ніклас Б.

2
@mu: Звичайно, я ще не бачив справді чистої реалізації інтерпретованої мови (Lua наближається).
Ніклас Б.

24

_є дійсним ідентифікатором. Ідентифікатори не можуть містити лише підкреслення, вони також можуть бути підкресленням.

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

Ви також можете використовувати його як назви методів:

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

Звичайно, це не зовсім читабельна назва, а також не передає читачеві ніякої інформації про те, до чого відноситься змінна або що робить метод.

IRB, зокрема, встановлює _значення останнього виразу:

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

Як у вихідному коді , він просто встановлює _останнє значення:

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

Деякі сховища досліджували. Ось що я знайшов:

В останніх рядках файлу id.cє виклик:

REGISTER_SYMID(idUScore, "_");

grepджерело для idUScoreдало мені два, здавалося б, релевантні результати:

shadowing_lvar_genздається механізмом, за допомогою якого формальний параметр блоку замінює однойменну змінну, яка існує в іншій області. Здається, саме ця функція піднімає "дубльоване ім'я аргументу" SyntaxErrorта попередження "затінення зовнішньої локальної змінної".

Після grepвивчення джерела для shadowing_lvar_gen, я виявив у журналі змін до Ruby 1.9.3 наступне :

Вівторок 11 грудня 01:21:21 2007 Юкіхіро Мацумото

  • parse.y (shadowing_lvar_gen): помилка дубліката для "_" відсутня.

Що, ймовірно, буде походженням цього рядка :

if (idUScore == name) return name;

З цього я висновую, що в такій ситуації, як proc { |_, _| :x }.call :a, :bодна _змінна просто затінює іншу.


Ось про який коміт йдеться . В основному він представив ці два рядки:

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

З того часу, коли idUScore, мабуть, навіть не існувало.


6
Це не пояснює , на всіх , чому lambda { |_, _| 42 }роботи поки lambda { |x, x| 42 }немає.
Ендрю Маршалл,

@AndrewMarshall, здається, ти маєш рацію. |_, _|працює, але |__, __|ні. _мабуть, має якесь особливе значення, я побачу, чи зможу я відкопати якусь інформацію з джерела Ruby.
Матеус Морейра

1
До речі, ваше оновлення, хоча і є інформативним, не є актуальним, оскільки воно стосується лише IRb, про що я спеціально заявив у своєму питанні, про яке я не питав.
Ендрю Маршалл,

1
+1 для розкопування запису ChangeLog. Кожен повинен бути хакером C :)
mu занадто короткий

1
+1 для вихідного коду IRB. Про IRB.CurrentContext.last_value дуже цікаво знати, навіть якщо це не стосується поставленого питання. Я опинився тут із пошуку в Google про підкреслення IRB.
Джош
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.