Где и как указывается переменная _ (подчеркивание)?

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]тест 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 немного расслабляет.

mu слишком короткий
источник
1
Интересно, не является ли разница в поведении lambda { |_, _| _ }.call(4, 2)между 1.8 и 1.9 просто непреднамеренным побочным эффектом? Как и в «обычных» обстоятельствах, когда имя переменной не может быть продублировано, порядок, в котором они назначаются, не имеет значения.
Эндрю Маршалл
3
@AndrewMarshall: Да, я думаю, что проблема «4 против 2» - это всего лишь артефакт того, как 1.8 и 1.9 обрабатывают стек. Единственный раз, когда это будет заметно, так это |_,_,...|потому, что ошибка-дубликат была подавлена.
mu слишком короткое
2
@AndrewMarshall: Интересно, все ли читают журналы изменений друг друга за нашей спиной.
mu слишком короткое
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 is too short
1
+1 за исходный код IRB. IRB.CurrentContext.last_value очень интересно знать, даже если это не имеет отношения к поставленному вопросу. Я попал сюда из поиска в Google по поводу подчеркивания IRB.
Джош