Environnements #2

Lors d'un épisode précédent, nous avons compris comment fonctionnent les tiroirs de notre établi. Cette fois-ci, nous allons créer des boîtes au-dessus de .GlobalEnv.

Soit deux vecteurs...

rm(list = ls(all = TRUE))
y <- x <- 1

... et une boîte :

box <- new.env()

Sans surprise, on constate que :

> ls()
[1] "box" "x"   "y"

Par ailleurs :

> class(box)
[1] "environment"
> ls(box)
character(0)
> parent.env(box)

C'est-à-dire que notre boîte box est un environnement vide et parent.env nous précise qu'elle est posée juste au-dessus de .GlobalEnv (contrairement aux tiroirs qui, souvenez-vous, sont en-dessous).

Nous allons maintenant ranger deux objets x et y dans notre boîte :

box$x <- 1:3
box$y <- 2

À la surface de notre établi, on a toujours :

> ls()
[1] "box" "x"   "y"

Mais dans la boîte :

> ls(box)
[1] "x" "y"

Avec :

> get("x", box)
[1] 1 2 3

Et (autre méthode) :

> box$y
[1] 2

Si nous sommons le x et le y posés sur .GlobalEnv :

> x+y
[1] 4

Mais si nous évaluons l'expression x + y dans box :

> eval(expression(x+y), envir = box)
[1] 3 4 5

Logique n'est-ce pas ?

Bien. Maintenant, considérez cette fonction :

foo <- function(x, y) {
      res <- x + y^2
      return(res)
}

Si nous l'exécutons dans .GlobalEnv, nous obtenons :

> foo(x, y)
[1] 6

Question : pourquoi res n’apparaît-il pas :

> ls()
[1] "box" "foo" "x"   "y"

Eh bien c'est fort simple : parce que foo a évalué res dans un environnement distinct de .GlobalEnv. On peut le vérifier comme suit :

foo <- function(x, y) {
      res <- x + y^2
      e <- environment()
      return(e)
}

Ce qui nous donne :

> ans <- foo(x, y)
> ans
<environment: 0x105d9c84>
> ls(ans)
[1] "e"   "res" "x"   "y"
> ans$res
[1] 6

Par ailleurs...

> parent.env(ans)
<environment: R_GlobalEnv>

... nous signale que cet environnement est posé au-dessus de .GlobalEnv.

Ça n’est pas du tout trivial. Voici pourquoi :

foo <- function(x) x + a

On peut vérifier que :

> a <- 5
> foo(x)
[1] 7

Tandis qu'avec :

rm(list = "a")
box$a <- 5

Gruik !

> foo(x)
Erreur dans foo(x) : objet 'a' introuvable

R a cherché a dans l'environnement de foo, dans .GlobalEnv, dans tous les tiroirs jusqu'au tiroir vide et rien, rien de rien : pas la moindre trace de a ; d'où le message d'erreur. En revanche, R n'est pas allée voir dans box parce que box est un environnement parallèle à celui de foo.

Shématiquement, en notant <foo> l'environnement de foo, on peut représenter le chemin de recherche de R comme suit :

<foo> --> |
          | <.GlobalEnv> --> <rgl> --> (...) --> <base> --> <vide>
<box>     |
C’est pour cette raison que ce qui est évalué dans foo n’est pas accessible dans .GlobalEnv et que ce qui est dans box n’est pas accessible à foo.

En revanche, avec :

rm(list = ls(all = TRUE))
box <- new.env()
box$a <- 1
foo <- function(x) x + box$a

On obtient :

> foo(1)
[1] 2

Parce que box étant posé que .GlobalEnv, il est bien sur le chemin de recherche et il suffit de dire à R de remonter chercher a dans cet environnement pour que ça fonctionne.

Si vous souhaitez pousser des objets hors de la boîte d'une fonction, vous pouvez faire des choses comme ça :

rm(list = ls(all = TRUE))
check <- function() {
      if(exists(".count", envir = .GlobalEnv)) {
            .count <<- .count + 1
      } else {
            .count <<- 1
      }
      return(.count)
}

Ce qui donne :

> check()
[1] 1
> check()
[1] 2
> check()
[1] 3
> .count
[1] 3

Ou des choses comme ça :

rm(list = ls(all = TRUE))
make.rot <- function(n) {
 foo <- function(x) {
            ix <- (match(strsplit(x, "")[[1]], letters) + n) %% 26
            paste(letters[ix], collapse = "")
        }
 fname <- paste("rot", n, sep = "")
 assign(fname, foo, .GlobalEnv)
 if(exists(fname, .GlobalEnv)) TRUE else FALSE
}

D'où :

> make.rot(13)
[1] TRUE
> ls()
[1] "make.rot" "rot13"
> rot13("obawbhe")
[1] "bonjour"

Aucun commentaire:

Enregistrer un commentaire