2016-11-22 9 views
9

Pytanie jest inspirowane przez this one.Sposób wykonywania procesu po przekazaniu go do instancji `instance_exec`

Proc::new ma możliwość być wywołany bez bloku wewnątrz sposób:

Proc::new może być wywołany bez bloku tylko w sposobie z przyłączonym bloku, przy czym bloki te przeprowadza się w Proc obiekt.

Gdy instancja proc/lambda jest przekazywana jako blok kodu, nowa instancja Proc powstaje:

Proc.singleton_class.prepend(Module.new do 
    def new(*args, &cb) 
    puts "PROC#{[block_given?, cb, *args].inspect}" 
    super 
    end 
end) 

Proc.prepend(Module.new do 
    def initialize(*args, &cb) 
    puts "INIT #{[block_given?, cb, *args].inspect}" 
    super 
    end 
    def call(*args, &cb) 
    puts "CALL #{[block_given?, cb, *args].inspect}" 
    super 
    end 
end) 

λ = ->(*args) { } 
[1].each &λ 
#⇒ [1] 

Jak można zobaczyć, ani wezwanie do Proc::new stało, ani Proc#initialize i/lub Proc#call zostały wywołane.

Pytanie brzmi: jak Ruby tworzy i wykonuje owijkę bloku pod maską?


NB Nie przetestować powyższy kod w pry/irb konsoli: oni mają znane usterki z czystego wykonania tego, w zasadzie dlatego, że procs krosowe.

+0

wersja Ruby? – fl00r

+0

@ fl00r MRI 2.1-2.3, rzeczywiście uważam, że wszystkie wersje działają tutaj tak samo. – mudasobwa

+0

Nie mogę otrzymać twojego wyjścia 'ruby 2.2.2p95 (2015-04-13 rewizja 50295) [x86_64-darwin14]' Mam '[1] .eak &λ; => [1]' – fl00r

Odpowiedz

2

Było trochę dyskusji na temat tego zachowania w narzędziu do śledzenia problemów z Rubim, patrz Feature #10499: Eliminate implicit magic in Proc.new and Kernel#proc.

To jest artefakt implementacji YARV: YARV popycha blok na globalnym stosie VM, a Proc::new po prostu tworzy Proc z najwyższego bloku na stosie. Tak więc, jeśli zdarzy ci się zadzwonić pod numer Proc.new z metody, która została wywołana za pomocą bloku, z przyjemnością zgarnie to, co blok znajduje się na wierzchu stosu, bez sprawdzania, skąd pochodzi. Jakoś gdzieś, we mgle czasu, ten (nazwijmy to) "przypadkowy artefakt" (nazwałbym go raczej błędem) stał się udokumentowaną funkcją. Funkcja, którą programiści JRuby (i prawdopodobnie Rubinius, Opal, MagLev, itp.) Woleliby się pozbyć.

Ponieważ większość innych implementacji działa zupełnie inaczej, to zachowanie, które pojawia się "za darmo" na YARV, powoduje, że oba bloki i Proc::new są bardziej kosztowne w innych implementacjach i zabraniają możliwości optymalizacji (co nie boli na YARV, ponieważ YARV robi zoptymalizować).

+0

"YARV przesuwa wskaźnik do bloku na stosie VM" - to wygląda na połowę odpowiedzi na moje pytanie. Czy oznacza to, że 1) jakakolwiek inna implementacja ruby ​​wywołuje 'Proc :: new' dla' [1] .each & λ' i 2) programista nie ma możliwości ingerowania '[1] .each & λ' w YARV, ponieważ istnieje w ogóle nie utworzono instancji "Proc"?AFAIU, bez względu na to, czy stos zawiera blok jako "pierwszy na zewnątrz", YARV z radością wykonałby ominięcie jakiegokolwiek innego kodu ruby ​​/ owijki/cokolwiek, prawda? – mudasobwa

+0

Nie jestem pewien. Moja odpowiedź opierała się na rozmytej pamięci jednoliniowego posta autorstwa Charlesa Nuttera, którą znalazłem i powiązałem z moją odpowiedzią. Nie wyjaśnia jednak, jak np. JRuby to robi. –

+0

Rozumiem, dziękuję. Byłbym wdzięczny, gdyby twoja odpowiedź została zaktualizowana o "jakiekolwiek rozwiązanie może być zastosowane w celu interweniowania bloku, który przechodzi do metody, nie wydaje się być implementacją VM". – mudasobwa

Powiązane problemy