2017-05-17 1 views
2

Disons que j'ai une fonction f qui prend un tas d'arguments, avec un argument supplémentaire optionnel.Propre façon d'effectuer une évaluation paresseuse imbriquée dans rlang

f <- function(..., extra) 
{ 
    arglst <- lapply(quos(...), get_expr) 
    if(!missing(extra)) 
    { 
     extra <- get_expr(enquo(extra)) 
     arglst <- c(arglst, extra=extra) 
    } 
    arglst 
    ## ... do something with argument list ... ## 
} 

f(a, extra=foo) 
# [[1]] 
# a 
# 
# $extra 
# foo 

Notez que je ne veux pas évaluer les arguments en tant que tel, mais je ne veux obtenir les expressions qui ont été transmis, à évaluer par tout autre code la ligne.

Le nouveau package rlang (qui alimente la prochaine version de dplyr, qui sera publiée sur CRAN Real Soon Now) fournit de nombreuses fonctionnalités pour l'évaluation paresseuse que j'utilise dans f ci-dessus. Par exemple quos, get_expr et enquo sont toutes les fonctions de rlang.

Dans f, la partie où je traite extra est en fait le code standard: je vais le faire dans d'autres fonctions, pas seulement dans f. Je ne veux pas réécrire à chaque fois, donc je pensais que je mettrais dans sa propre fonction:

doExtra <- function(arglst, extra) 
{ 
    if(!missing(extra)) 
    { 
     extra <- get_expr(enquo(extra)) 
     arglst <- c(arglst, extra=extra) 
    } 
    arglst 
} 

f2 <- function(..., extra) 
{ 
    arglst <- lapply(quos(...), get_expr) 
    arglst <- doExtra(arglst, extra) 
    arglst 
} 

Le problème est que quand je le fais de cette façon, la valeur de extra que doExtra voit est ce qui est passé dans de f2, et non l'original:

f2(a, extra=foo) 
# [[1]] 
# a 
# 
# $extra 
# extra 

Comment puis-je modifier f pour isoler le code passe-partout, sans obtenir le mauvais résultat? Je peux faire quelque chose comme manipuler l'environnement de la trame d'appel doExtra directement, mais ce serait extrêmement moche.

Répondre

2
  • Pour transférer un argument nommé à une autre fonction de enquoting, vous devez alors enquote unquote: !! enquo(arg). Si vous venez de passer enquo(arg), la fonction d'interrogation verra juste cela: enquo(arg). Si vous passez le symbole d'argument, c'est ce qu'il verra également. C'est pourquoi vous avez besoin de mettre en avant l'argument qu'il capture.

    !! enquo(arg) déclenche l'évaluation de enquo(arg), qui renvoie l'expression fournie à l'argument arg. Ensuite, il n'est pas cité dans l'argument capturé par votre fonction.

  • Si vous demandez un argument potentiellement manquant, il est préférable de l'interroger et de vérifier la présence de données manquantes avec quo_is_missing(). Enquêter un argument manquant crée le même objet renvoyé en appelant quo() sans argument. Si vous n'avez pas besoin de quosures, vous pouvez utiliser exprs() et enexpr(). Cependant vous perdez l'environnement et vous faites une évaluation plus fragile.

    Si vous capturez l'environnement d'une autre manière pour l'évaluer avec base::eval() ou similaire, veuillez noter que les quosures peuvent contenir d'autres quosures. Seul eval_tidy() comprendra ces quosures imbriquées.

IIUC votre question, il s'agit de passer un argument qui devrait être attribué à une autre fonction.Une façon de le faire est de capturer dans la première fonction, puis passez par valeur à la deuxième fonction:

library("purrr") 
library("rlang") 

f <- function(..., extra) { 
    exprs <- exprs(...) 

    # Pass the enquoted argument by value 
    exprs <- extra_by_value(exprs, enexpr(extra)) 

    exprs 
} 
extra_by_value <- function(exprs, extra) { 
    if (!is_missing(extra)) { 
    c(exprs, extra = extra) 
    } else { 
    exprs 
    } 
} 

Si la deuxième fonction doit prendre par l'expression plutôt que par la valeur (peut-être parce qu'il est un autre par l'utilisateur face verbe), vous devez indiquer l'expression recherchée:

f <- function(..., extra) { 
    exprs <- exprs(...) 

    # Since the argument is captured by the function, we need 
    # to unquote the relevant expression into the argument: 
    exprs <- extra_by_expression(exprs, !! enexpr(extra)) 

    exprs 
} 
extra_by_expression <- function(exprs, extra) { 
    extra <- enexpr(extra) 
    if (!is_missing(extra)) { 
    c(exprs, extra = extra) 
    } else { 
    exprs 
    } 
} 

Tous ces concepts s'appliquent aux quosures. Voici le code équivalent:

f <- function(..., extra) { 
    quos <- quos(...) 

    # Since the argument is captured by the function, we need 
    # to unquote the relevant expression into the argument: 
    quos <- extra_by_expression(quos, !! enquo(extra)) 

    quos 
} 
extra_by_expression <- function(quos, extra) { 
    extra <- enquo(extra) 
    if (!quo_is_missing(extra)) { 
    c(quos, extra = extra) 
    } else { 
    quos 
    } 
} 

Il est presque toujours préférable d'utiliser quosures que les expressions premières parce qu'ils gardent une trace de leur contexte.

+0

L'utilisation de quosures est problématique car le code en aval fait son propre NSE. Le code extra_by_expression ne fonctionne pas pour moi (il semble que l'analyse de '!!' soit '! (! (...))') mais la valeur extra_by_value fonctionne. Merci. –

+0

Je dirais qu'il n'utilise pas les quosures qui posent problème, il fait NSE de manière non-ordonnée;) – lionel

+0

J'ai essayé quelque chose de similaire: 'outer (nomColonne)' appelant 'inner (nomColonne)' appelant les fonctions dplyr en utilisant 'columnName' . Les deux 'outer' et' inner' utilisent 'enquo' et ensuite' !! '. 'inner' fonctionne seul, mais' outer' ne fonctionne pas. Que voulez-vous dire en utilisant NSE d'une manière non-rangé? –