Gdzie i jak określono zmienną _ (podkreślenie)?

81

Większość zdaje sobie sprawę ze _specjalnego znaczenia IRB jako posiadacza ostatniej zwracanej wartości, ale nie o to tutaj pytam.

Zamiast tego pytam, _kiedy jest używany jako nazwa zmiennej w zwykłym starym-Ruby-kodzie. Tutaj wydaje się, że zachowuje się w specjalny sposób, podobnie jak zmienna „nie przejmuj się” (à la Prolog ). Oto kilka przydatnych przykładów ilustrujących jego wyjątkowe zachowanie:

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

Były one uruchamiane bezpośrednio w Rubim (z putsdodanymi s) - nie w IRB - aby uniknąć konfliktu z jego dodatkową funkcjonalnością.

To wszystko jest jednak wynikiem moich własnych eksperymentów, ponieważ nie mogę nigdzie znaleźć żadnej dokumentacji na temat tego zachowania (co prawda nie jest to najłatwiejsza rzecz do wyszukania). Ostatecznie jestem ciekawy, jak to wszystko działa wewnętrznie, więc mogę lepiej zrozumieć, co jest specjalnego _. Dlatego proszę o odniesienia do dokumentacji i, najlepiej, kodu źródłowego Rubiego (i być może RubySpec ), które pokazują, jak _zachowuje się w Rubim.

Uwaga: większość z tego wynikła z dyskusji z @Niklas B.

Andrew Marshall
źródło

Odpowiedzi:

54

W źródle istnieje specjalny sposób postępowania w celu uniknięcia błędu „zduplikowana nazwa argumentu”. Komunikat o błędzie pojawia się tylko w shadowing_lvar_genśrodku parse.y, wersja 1.9.3 wygląda tak :

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

i idUScorejest zdefiniowanyid.c następująco:

REGISTER_SYMID(idUScore, "_");

Zobaczysz podobne specjalne traktowanie w 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]));
    }
}

Zauważysz, że ostrzeżenie jest pomijane w drugiej linii forpętli.

Jedyna specjalna obsługa _, jaką mogłem znaleźć w źródle 1.9.3, jest powyżej: pomijany jest błąd zduplikowanej nazwy, a ostrzeżenie o nieużywanej zmiennej jest pomijane. Poza tymi dwiema rzeczami _jest to zwykła stara zmienna, jak każda inna. Nie znam żadnej dokumentacji dotyczącej (drobnej) specjalności_ .

W Ruby 2.0 idUScore == v[i]test w warn_unused_varjest zastępowany wywołaniem 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]));

i is_private_local_idpomija ostrzeżenia dla zmiennych zaczynających się od _:

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

a nie tylko _siebie. Więc 2.0 trochę rozluźnia.

mu jest za krótkie
źródło
1
Zastanawiam się, czy różnica w zachowaniu lambda { |_, _| _ }.call(4, 2)między 1,8 a 1,9 to tylko niezamierzony efekt uboczny? Podobnie jak w „normalnych” okolicznościach, w których nazwy zmiennej nie można powielić, kolejność, w jakiej są przypisywane, jest nieistotna.
Andrew Marshall
3
@AndrewMarshall: Tak, myślę, że problem „4 na 2” jest tylko artefaktem tego, jak 1.8 i 1.9 radzą sobie ze stosem. Jedynym przypadkiem, w którym byłoby to zauważalne, jest |_,_,...|wyeliminowanie błędu duplikatu.
mu jest za krótkie.
2
@AndrewMarshall: Zastanawiam się, czy wszyscy czytają za naszymi plecami dzienniki zmian.
mu jest za krótkie
2
Niezłe znalezisko. Założyłem, że będzie to coś takiego, po prostu pomijanie błędu podwójnej nazwy parametru. Ruby to naprawdę wielki bałagan: D
Niklas B.
2
@mu: Oczywiście, nie widziałem jeszcze naprawdę czystej implementacji języka interpretowanego (Lua jest blisko).
Niklas B.
24

_jest prawidłowym identyfikatorem. Identyfikatory nie mogą zawierać tylko podkreślenia, mogą również być podkreśleniem.

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

Możesz go również użyć jako nazw metod:

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

Oczywiście nie jest to dokładnie czytelna nazwa, ani nie przekazuje czytelnikowi żadnych informacji o tym, do czego odwołuje się zmienna ani do czego służy metoda.

IRBw szczególności ustawia _wartość ostatniego wyrażenia:

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

Tak jak w kodzie źródłowym , po prostu ustawia _ostatnią wartość:

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

Przeszukałem trochę repozytorium. Oto co znalazłem:

W ostatnich wierszach pliku id.cjest wywołanie:

REGISTER_SYMID(idUScore, "_");

grepZnalezienie źródła dla idUScoredało mi dwa pozornie istotne wyniki:

shadowing_lvar_genwydaje się być mechanizmem, za pomocą którego formalny parametr bloku zastępuje zmienną o tej samej nazwie, która istnieje w innym zakresie. Jest to funkcja, która wydaje się zgłaszać „zduplikowaną nazwę argumentu”SyntaxError ostrzeżenie o i „cieniowaniu zewnętrznej zmiennej lokalnej”.

Po znalezieniu grepźródła dla shadowing_lvar_gen, w dzienniku zmian dla Rubiego 1.9.3 znalazłem następujące informacje :

Wtorek 11 grudnia 01:21:21 2007 Yukihiro Matsumoto

  • parse.y (shadowing_lvar_gen): brak podwójnego błędu dla „_”.

Co może być źródłem tej linii :

if (idUScore == name) return name;

Z tego wnioskuję, że w takiej sytuacji proc { |_, _| :x }.call :a, :bjedna _zmienna po prostu przesłania drugą.


Oto omawiany commit . Zasadniczo wprowadził te dwie linie:

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

Od chwili, gdy idUScorenawet nie istnieje, widocznie.

Matheus Moreira
źródło
6
To nie wyjaśnia, na wszystkich dlaczego lambda { |_, _| 42 }robót natomiast lambda { |x, x| 42 }nie.
Andrew Marshall,
@AndrewMarshall, wygląda na to, że masz rację. |_, _|działa, ale |__, __|nie. _wydaje się mieć jakieś specjalne znaczenie, zobaczę, czy uda mi się wydobyć jakiekolwiek informacje ze źródła Rubiego.
Matheus Moreira
1
Nawiasem mówiąc, twoja aktualizacja, chociaż zawiera informacje, nie jest istotna, ponieważ dotyczy tylko IRb, o czym wyraźnie wskazałem w moim pytaniu, o które nie pytałem.
Andrew Marshall,
1
+1 za odkopanie wpisu ChangeLog. Każdy powinien być C hackerem :)
mu jest za krótkie
1
+1 za kod źródłowy IRB. IRB.CurrentContext.last_value jest bardzo interesująca, nawet jeśli nie ma to związku z zadanym pytaniem. Skończyło się tutaj z wyszukiwarki Google na temat podkreślenia IRB.
Josh