2012-06-20 2 views
3

Dans mon développement R j'ai besoin d'envelopper les primitives de fonction dans proto objets de sorte qu'un certain nombre d'arguments peuvent être automatiquement transmis aux fonctions lorsque la méthode $perform() de l'objet est appelée. L'appel de fonction se passe en interne via do.call(). Tout va bien, sauf lorsque la fonction tente d'accéder à des variables à partir de la fermeture dans laquelle elle est définie. Dans ce cas, la fonction ne peut pas résoudre les noms.Environnement chaînage dans R

Voici l'exemple le plus petit que j'ai trouvé qui reproduit le comportement:

library(proto) 

make_command <- function(operation) { 
    proto(
    func = operation, 
    perform = function(., ...) { 
     func <- with(., func) # unbinds proto method 
     do.call(func, list(), envir=environment(operation)) 
    } 
    ) 
} 

test_case <- function() { 
    result <- 100 
    make_command(function() result)$perform() 
} 

# Will generate error: 
# Error in function() : object 'result' not found 
test_case() 

J'ai un test testthat reproductible qui génère aussi beaucoup de sortie de diagnostic. La sortie de diagnostic m'a bloqué. En cherchant la chaîne d'environnement parent, mon code de diagnostic, qui vit dans la fonction, trouve et imprime la même variable que la fonction ne parvient pas à trouver. See this gist..

Comment l'environnement de do.call peut être configuré correctement?

Répondre

3

Ce fut la réponse finale après une discussion en ligne avec l'affiche:

make_command <- function(operation) { 
proto(perform = function(.) operation()) 
} 
+0

Merci pour vos nombreux suivis sur cette question. Pourriez-vous m'aider à comprendre pourquoi cela fonctionne? Est-ce que ça va comme ceci: ** (1) ** le '.' dans l'appel de' proto() 'fait référence au cadre d'évaluation/environnement de l'appel à' make_command() ', qui devient ainsi le parent (ie '.super') du nouvel objet proto; de sorte que ** (2) ** quand 'operation' n'est pas trouvé dans l'objet proto, il est recherché et trouvé dans son' .super'; et alors ** (3) ** 'operation' lui-même a comme environnement le cadre/environnement d'évaluation de l'appel à' test_case() '(où' result' vit aussi)? –

+0

Un objet proto est un environnement. L'objet proto a ici un composant, 'perform', qui est une fonction/méthode. L'environnement de 'perform' est mis à l'objet proto quand il est inséré alors quand' perform' s'exécute, il cherche d'abord 'operation' dans l'objet proto et quand il n'est pas trouvé il regarde dans le parent de l'objet proto. Le parent de l'objet proto n'a pas été spécifié, donc il est par défaut dans l'environnement dans lequel l'objet 'proto' a été défini - qui est l'environnement dans le cadre d'exécution de' make_command' et là, il trouve 'operation' comme argument. Voir la nouvelle FAQ sur la page d'accueil proto. –

+0

Merci pour les questions/commentaires supplémentaires et merci pour la nouvelle FAQ. – Sim

1

Je pense que la question est ici plus claire et plus facile à explorer si vous:

  • Remplacer la fonction anonyme au sein make_command() avec un nom.

  • Faites cette fonction ouvrir un browser() (au lieu d'essayer d'obtenir result). De cette façon, vous pouvez regarder autour de vous pour voir où vous êtes et ce qui se passe.

Essayez ceci, qui devrait préciser la cause de votre problème:

test_case <- function() { 
    result <- 100 
    myFun <- function() browser() 
    make_command(myFun)$perform() 
} 
test_case() 
## Then from within the browser: 
# 
parent.env(environment()) 
# <environment: 0x0d8de854> 
# attr(,"class") 
# [1] "proto"  "environment" 
get("result", parent.env(environment())) 
# Error in get("result", parent.env(environment())) : 
# object 'result' not found 
# 
parent.frame() 
# <environment: 0x0d8ddfc0> 
get("result", parent.frame()) ## (This works, so points towards a solution.) 
# [1] 100 

est ici le problème. Bien que vous pensiez évaluer myFun(), dont l'environnement est le cadre d'évaluation de test_case(), votre appel à do.call(func, ...) évalue vraiment func(), dont l'environnement est l'environnement proto dans lequel il a été défini. Après avoir recherché et ne pas trouver result dans son propre cadre, l'appel à func() suit les règles de portée lexicale, et regarde ensuite dans l'environnement proto. Ni lui, ni son environnement parent contient un objet nommé result, résultant dans le message d'erreur que vous avez reçu.

Si cela n'a pas de sens immédiatement, vous pouvez continuer à fouiller dans le navigateur. Voici quelques appels supplémentaires pourraient vous être utiles:

environment(get("myFun", parent.frame())) 
ls(environment(get("myFun", parent.frame()))) 
environment(get("func", parent.env(environment()))) 
ls(environment(get("func", parent.env(environment())))) 
+0

Merci, ce sont des conseils de débogage utiles. Quelle lecture recommanderiez-vous pour vraiment comprendre les chaînes de portée dans R? – Sim

+0

Un petit point de clarification sur votre réponse: l'environnement de func est l'objet proto mais pas parce que c'est là qu'il est défini. Proto encapsule des fonctions dans une classe pour les transformer en méthodes instanciées. avec (., func) les décompresse et _should_ aurait fonctionné, sauf que l'implémentation de proto réinitialise réellement l'environnement de la fonction pour que son code puisse accéder aux internes de l'objet proto.C'est cette réinitialisation qui se passe dans les coulisses qui est la cause du comportement (et de la confusion). – Sim

+0

@Sim - Merci pour la clarification. La section 13.3 du «logiciel pour l'analyse des données» de John Chambers donne une explication claire de la délimitation de la portée dans R. La seule chose qui prête à confusion, c'est que l'on appelle systématiquement environnements environnants «environnements parents». Je pense que c'est malheureux car cela embrouille la distinction importante entre la pile d'appels (une chaîne de cadres parents) et la chaîne d'environnements englobants le long de laquelle l'évaluateur cherche à résoudre les valeurs des symboles non présents dans l'environnement local. –