2016-11-22 1 views
9

La question est inspirée de this one.Comment proc est exécuté lorsqu'il est passé à `instance_exec`

Proc::new a la possibilité d'être appelé sans un bloc à l'intérieur d'une méthode:

Proc::new peut être appelé sans un seul bloc dans un procédé avec un bloc fixé, dans ce cas, ce bloc est converti en le Proc objet.

Lorsque le proc/lambda instance est passé comme un bloc de code, la nouvelle instance de Proc est en cours de création:

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] 

Comme on peut le voir, ni l'appel à Proc::new est arrivé, ni Proc#initialize et/ou Proc#call ont été appelés.

La question est: comment ruby ​​crée et exécute une enveloppe de bloc sous le capot?


NB Ne testez pas le code ci-dessus dans la console pry/irb: ils connus pour avoir des glitches avec pur exécution de ce, essentiellement parce qu'ils procs patch.

+0

version de Ruby? – fl00r

+0

@ fl00r MRI 2.1-2.3, je crois en fait que toutes les versions agissent de la même manière ici. – mudasobwa

+0

Je ne peux pas recevoir votre sortie 'ruby 2.2.2p95 (2015-04-13 révision 50295) [x86_64-darwin14]' J'ai eu '[1] .each &λ; => [1]' – fl00r

Répondre

2

Il y a eu une discussion de ce comportement sur le Ruby Issue Tracker, voir Feature #10499: Eliminate implicit magic in Proc.new and Kernel#proc. Ceci est un artefact d'implémentation de YARV: YARV pousse un bloc sur la pile VM globale et Proc::new crée simplement un Proc à partir du bloc le plus haut de la pile. Donc, s'il vous arrive d'appeler Proc.new à partir d'une méthode qui a été appelée avec un bloc, il va heureusement attraper n'importe quel bloc au-dessus de la pile, sans jamais vérifier d'où il vient. D'une façon ou d'une autre, quelque part, dans la brume du temps, ce (ou plutôt ce que nous appelons) "artefact accidentel" (j'appellerais plutôt ça un bug) est devenu une fonctionnalité documentée. Une fonctionnalité que les développeurs de JRuby (et vraisemblablement Rubinius, Opal, MagLev, etc.) préfèreraient se débarrasser. Comme la plupart des autres implémentations fonctionnent complètement différemment, ce comportement qui vient "gratuitement" sur YARV, rend les deux blocs et Proc::new plus coûteux sur d'autres implémentations et interdit les optimisations possibles (ce qui ne nuit pas à YARV, car YARV ne le fait pas pas optimiser).

+0

"YARV pousse un pointeur sur le bloc de la pile VM" - cela ressemble à peu près à la moitié d'une réponse à ma question. Cela signifie-t-il que 1) toute autre implémentation de ruby ​​appelle 'Proc :: new' pour' [1] .each & λ' et 2) le développeur n'a aucune possibilité d'interférer '[1] .each & λ' dans YARV, puisqu'il y a pas d'instance 'Proc' créée du tout?AFAIU, que la pile contienne un bloc comme un "premier à sortir", YARV l'exécuterait heureusement en contournant n'importe quel autre code/wrappers/quoi que ce soit, non? – mudasobwa

+0

Pas sûr. Ma réponse était basée sur un souvenir flou d'un article d'une ligne de Charles Nutter, que j'ai maintenant trouvé et auquel je suis associé dans ma réponse. Cependant, il n'explique pas comment, par exemple, JRuby le fait. –

+0

J'ai compris, merci. Je vous serais reconnaissant si vous avez mis à jour votre réponse avec "quelle que soit la solution qui pourrait être appliquée pour intervenir sur le passage du bloc à la méthode, il ne semble pas que ce soit une implémentation cross-VM". – mudasobwa