-
Notifications
You must be signed in to change notification settings - Fork 1
/
Environments.qmd
847 lines (572 loc) · 36.2 KB
/
Environments.qmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
# Entornos {#sec-environments}
\index{environments}
```{r, include = FALSE}
source("common.R")
```
## Introduction
El entorno es la estructura de datos que impulsa el alcance. Este capítulo profundiza en los entornos, describe su estructura en profundidad y los usa para mejorar su comprensión de las cuatro reglas de scoping descritas en la @sec-lexical-scoping. Comprender los entornos no es necesario para el uso diario de R. Pero es importante comprenderlos porque impulsan muchas funciones importantes de R, como el scoping léxico, los espacios de nombres y las clases R6, e interactúan con la evaluación para brindarle herramientas poderosas para crear dominios. lenguajes específicos, como dplyr y ggplot2.
### Prueba {.unnumbered}
Si puede responder correctamente las siguientes preguntas, ya conoce los temas más importantes de este capítulo. Puede encontrar las respuestas al final del capítulo en la @sec-env-answers.
1. Enumere al menos tres formas en que un entorno difiere de una lista.
2. ¿Cuál es el padre del medio ambiente global? ¿Cuál es el único entorno que no tiene un padre?
3. ¿Qué es el entorno envolvente de una función? ¿Por qué es importante?
4. ¿Cómo determina el entorno desde el que se llamó a una función?
5. ¿En qué se diferencian `<-` y `<<-`?
### Estructura {.unnumbered}
- La @sec-env-basics le presenta las propiedades básicas de un entorno y le muestra cómo crear el suyo propio.
- La @sec-env-recursion proporciona una plantilla de funciones para computar con entornos, ilustrando la idea con una función útil.
- La @sec-special-environments describe entornos utilizados para fines especiales: para paquetes, dentro de funciones, para espacios de nombres y para la ejecución de funciones.
- La @sec-call-stack explica el último entorno importante: el entorno de la persona que llama. Esto requiere que aprenda sobre la pila de llamadas, que describe cómo se llamó a una función. Habrás visto la pila de llamadas si alguna vez llamaste a `traceback()` para ayudar en la depuración.
- La @sec-explicit-envs analiza brevemente tres lugares donde los entornos son estructuras de datos útiles para resolver otros problemas.
### Requisitos previos {.unnumbered}
Este capítulo utilizará las funciones [rlang](https://rlang.r-lib.org) para trabajar con entornos, ya que nos permite centrarnos en la esencia de los entornos, en lugar de los detalles secundarios.
```{r setup, message = FALSE}
library(rlang)
```
Las funciones `env_` en rlang están diseñadas para trabajar con la canalización: todas toman un entorno como primer argumento, y muchas también devuelven un entorno. No usaré la canalización en este capítulo con el fin de mantener el código lo más simple posible, pero debería considerarlo para su propio código.
## Conceptos básicos de entornos {#sec-env-basics}
En general, un entorno es similar a una lista con nombre, con cuatro excepciones importantes:
- Cada nombre debe ser único.
- Los nombres de un entorno no están ordenados.
- Un entorno tiene un padre.
- Los entornos no se copian cuando se modifican.
Exploremos estas ideas con código e imágenes.
### Lo esencial
\index{environments!creating} \index{env()} \index{new.env()} \index{assignment}
Para crear un entorno, utilice `rlang::env()`. Funciona como `list()`, tomando un conjunto de pares nombre-valor:
```{r}
e1 <- env(
a = FALSE,
b = "a",
c = 2.3,
d = 1:3,
)
```
::: base
Utilice `new.env()` para crear un nuevo entorno. Ignora los parámetros `hash` y `size`; no son necesarios. No puede crear y definir valores simultáneamente; use `$<-`, como se muestra a continuación.
:::
El trabajo de un entorno es asociar, o **vincular**, un conjunto de nombres a un conjunto de valores. Puede pensar en un entorno como una bolsa de nombres, sin orden implícito (es decir, no tiene sentido preguntar cuál es el primer elemento en un entorno). Por esa razón, dibujaremos el entorno así:
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/environments/bindings.png")
```
Como se discutió en la @sec-env-modify, los entornos tienen una semántica de referencia: a diferencia de la mayoría de los objetos R, cuando los modifica, los modifica en su lugar y no crea una copia. Una implicación importante es que los entornos pueden contenerse a sí mismos.
```{r}
e1$d <- e1
```
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/environments/loop.png")
```
Imprimir un entorno solo muestra su dirección de memoria, lo que no es muy útil:
```{r}
e1
```
En su lugar, usaremos `env_print()` que nos brinda un poco más de información:
```{r}
env_print(e1)
```
Puede usar `env_names()` para obtener un vector de caracteres que proporcione los enlaces actuales
```{r}
env_names(e1)
```
::: base
En R 3.2.0 y versiones posteriores, use `names()` para enumerar los enlaces en un entorno. Si su código necesita funcionar con R 3.1.0 o anterior, use `ls()`, pero tenga en cuenta que deberá configurar `all.names = TRUE` para mostrar todos los enlaces.
:::
### Entornos importantes
\index{environments!current} \index{environments!global}
Hablaremos en detalle sobre entornos especiales en @sec-special-environments, pero por ahora necesitamos mencionar dos. El entorno actual, o `current_env()` es el entorno en el que el código se está ejecutando actualmente. Cuando estás experimentando de forma interactiva, ese suele ser el entorno global, o `global_env()`. El entorno global a veces se llama su "área de trabajo", ya que es donde se lleva a cabo todo el cálculo interactivo (es decir, fuera de una función).
Para comparar entornos, debe usar `identical()` y no `==`. Esto se debe a que `==` es un operador vectorizado y los entornos no son vectores.
```{r, error = TRUE}
identical(global_env(), current_env())
global_env() == current_env()
```
::: base
Accede al entorno global con `globalenv()` y al entorno actual con `environment()`. El entorno global se imprime como `R_GlobalEnv` y `.GlobalEnv`.
:::
### Padres
\index{environments!parent} \index{env\_parent()}
Cada entorno tiene un **padre**, otro entorno. En los diagramas, el padre se muestra como un pequeño círculo azul pálido y una flecha que apunta a otro entorno. El padre es lo que se usa para implementar el scoping léxico: si un nombre no se encuentra en un entorno, entonces R buscará en su padre (y así sucesivamente). Puede configurar el entorno principal proporcionando un argumento sin nombre a `env()`. Si no lo proporciona, el valor predeterminado es el entorno actual. En el siguiente código, `e2a` es el padre de `e2b`.
```{r}
e2a <- env(d = 4, e = 5)
e2b <- env(e2a, a = 1, b = 2, c = 3)
```
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/environments/parents.png")
```
Para ahorrar espacio, normalmente no dibujaré a todos los antepasados; solo recuerda que cada vez que veas un círculo azul pálido, hay un entorno principal en alguna parte.
Puedes encontrar el padre de un entorno con `env_parent()`:
```{r}
env_parent(e2b)
env_parent(e2a)
```
Solo un entorno no tiene un padre: el entorno **vacío**. Dibujo el entorno vacío con un entorno principal vacío y, cuando el espacio lo permita, lo etiquetaré con `R_EmptyEnv`, el nombre que usa R.
```{r}
e2c <- env(empty_env(), d = 4, e = 5)
e2d <- env(e2c, a = 1, b = 2, c = 3)
```
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/environments/parents-empty.png")
```
Los ancestros de cada ambiente eventualmente terminan con el ambiente vacío. Puedes ver todos los ancestros con `env_parents()`:
```{r}
env_parents(e2b)
env_parents(e2d)
```
Por defecto, `env_parents()` se detiene cuando llega al entorno global. Esto es útil porque los ancestros del entorno global incluyen todos los paquetes adjuntos, que puede ver si anula el comportamiento predeterminado como se muestra a continuación. Volveremos a estos entornos en la @sec-search-path.
```{r}
env_parents(e2b, last = empty_env())
```
::: base
Use `parent.env()` para encontrar el padre de un entorno. Ninguna función base devuelve todos los ancestros.
:::
### Asignación superior, `<<-`
\index{<<-}
\index{assignment} \index{super assignment}
Los ancestros de un entorno tienen una relación importante con `<<-`. La asignación regular, `<-`, siempre crea una variable en el entorno actual. La súper asignación, `<<-`, nunca crea una variable en el entorno actual, sino que modifica una variable existente que se encuentra en un entorno principal.
```{r}
x <- 0
f <- function() {
x <<- 1
}
f()
x
```
Si `<<-` no encuentra una variable existente, creará una en el entorno global. Esto generalmente no es deseable, porque las variables globales introducen dependencias no obvias entre funciones. `<<-` se usa más a menudo junto con una fábrica de funciones, como se describe en la @sec-stateful-funs.
### Conseguir y configurar
\index{environments!bindings} \index{env\_bind\_*}
Puede obtener y establecer elementos de un entorno con `$` y `[[` de la misma manera que una lista:
```{r}
e3 <- env(x = 1, y = 2)
e3$x
e3$z <- 3
e3[["z"]]
```
Pero no puedes usar `[[` con índices numéricos, y no puedes usar `[`:
```{r, error = TRUE}
e3[[1]]
e3[c("x", "y")]
```
`$` y `[[` devolverán `NULL` si el enlace no existe. Usa `env_get()` si quieres un error:
```{r, error = TRUE}
e3$xyz
env_get(e3, "xyz")
```
Si desea usar un valor predeterminado si el enlace no existe, puede usar el argumento `default`.
```{r}
env_get(e3, "xyz", default = NA)
```
Hay otras dos formas de agregar enlaces a un entorno:
- `env_poke()`[^environments-1] toma un nombre (como cadena) y un valor:
```{r}
env_poke(e3, "a", 100)
e3$a
```
- `env_bind()` le permite vincular múltiples valores:
```{r}
env_bind(e3, a = 10, b = 20)
env_names(e3)
```
[^environments-1]: Quizás se pregunte por qué rlang tiene `env_poke()` en lugar de `env_set()`. Esto es por coherencia: las funciones `_set()` devuelven una copia modificada; Las funciones `_poke()` se modifican en su lugar.
Puede determinar si un entorno tiene un enlace con `env_has()`:
```{r}
env_has(e3, "a")
```
A diferencia de las listas, establecer un elemento en `NULL` no lo elimina, porque a veces desea un nombre que se refiera a `NULL`. En su lugar, usa `env_unbind()`:
```{r}
e3$a <- NULL
env_has(e3, "a")
env_unbind(e3, "a")
env_has(e3, "a")
```
Desvincular un nombre no elimina el objeto. Ese es el trabajo del recolector de basura, que elimina automáticamente los objetos sin nombres vinculados a ellos. Este proceso se describe con más detalle en la @sec-gc.
::: base
\index{rm()}\index{assignment!assign()@\texttt{assign()}}\index{get()}\index{exists()} Consulte `get()`, `assign()`, `exists()` y `rm()`. Estos están diseñados de forma interactiva para su uso con el entorno actual, por lo que trabajar con otros entornos es un poco complicado. También tenga cuidado con el argumento `inherits`: por defecto es `TRUE`, lo que significa que los equivalentes base inspeccionarán el entorno suministrado y todos sus ancestros.
:::
### Enlaces avanzados {#sec-advanced-bindings}
\index{bindings!delayed} \index{promises} \index{bindings!active} \index{active bindings} \index{env\_bind\_*}
Hay dos variantes más exóticas de `env_bind()`:
- `env_bind_lazy()` crea **enlaces retrasados**, que se evalúan la primera vez que se accede a ellos. Detrás de escena, los enlaces retrasados crean promesas, por lo que se comportan de la misma manera que los argumentos de función.
```{r, cache = TRUE}
env_bind_lazy(current_env(), b = {Sys.sleep(1); 1})
system.time(print(b))
system.time(print(b))
```
El uso principal de los enlaces retrasados es `autoload()`, que permite que los paquetes de R proporcionen conjuntos de datos que se comportan como si estuvieran cargados en la memoria, aunque solo se cargan desde el disco cuando es necesario.
- `env_bind_active()` crea **enlaces activos** que se vuelven a calcular cada vez que se accede a ellos:
```{r}
env_bind_active(current_env(), z1 = function(val) runif(1))
z1
z1
```
Los enlaces activos se utilizan para implementar los campos activos de R6, sobre los que aprenderá en la @sec-active-fields.
::: base
Consulte `?delayedAssign()` y `?makeActiveBinding()`.
:::
### Ejercicios
1. Enumera tres formas en las que un entorno difiere de una lista.
2. Cree un entorno como el que se ilustra en esta imagen.
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/environments/recursive-1.png")
```
3. Cree un par de ambientes como se ilustra en esta imagen.
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/environments/recursive-2.png")
```
4. Explique por qué `e[[1]]` y `e[c("a", "b")]` no tienen sentido cuando `e` es un entorno.
5. Cree una versión de `env_poke()` que solo vinculará nombres nuevos, nunca volverá a vincular nombres antiguos. Algunos lenguajes de programación solo hacen esto y se conocen como \[lenguajes de asignación única\] (http://en.wikipedia.org/wiki/Assignment\_(computer_science)#Single_assignment).
6. ¿Qué hace esta función? ¿En qué se diferencia de `<<-` y por qué podría preferirlo?
```{r, error = TRUE}
rebind <- function(name, value, env = caller_env()) {
if (identical(env, empty_env())) {
stop("Can't find `", name, "`", call. = FALSE)
} else if (env_has(env, name)) {
env_poke(env, name, value)
} else {
rebind(name, value, env_parent(env))
}
}
rebind("a", 10)
a <- 5
rebind("a", 10)
a
```
## Recursing sobre entornos {#sec-env-recursion}
\index{recursion!over environments}
Si desea operar en todos los ancestros de un entorno, a menudo es conveniente escribir una función recursiva. Esta sección le muestra cómo, aplicando su nuevo conocimiento de entornos para escribir una función que, dado un nombre, encuentra el entorno `where()` está definido ese nombre, utilizando las reglas de alcance habituales de R.
La definición de `where()` es sencilla. Tiene dos argumentos: el nombre a buscar (como una cadena) y el entorno en el que iniciar la búsqueda. (Aprenderemos por qué `caller_env()` es un buen valor predeterminado en la @sec-call-stack.)
```{r}
where <- function(name, env = caller_env()) {
if (identical(env, empty_env())) {
# caso base
stop("Can't find ", name, call. = FALSE)
} else if (env_has(env, name)) {
# caso de exitoso
env
} else {
# caso recursivo
where(name, env_parent(env))
}
}
```
Hay tres casos:
- El caso base: hemos llegado al entorno vacío y no hemos encontrado el enlace. No podemos ir más lejos, por lo que lanzamos un error.
- El caso exitoso: el nombre existe en este entorno, por lo que devolvemos el entorno.
- El caso recursivo: el nombre no se encontró en este entorno, así que pruebe con el padre.
Estos tres casos se ilustran con estos tres ejemplos:
```{r, error = TRUE}
where("yyy")
x <- 5
where("x")
where("mean")
```
Podría ayudar ver una imagen. Imagine que tiene dos entornos, como en el siguiente código y diagrama:
```{r}
e4a <- env(empty_env(), a = 1, b = 2)
e4b <- env(e4a, x = 10, a = 11)
```
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/environments/where-ex.png")
```
- `where("a", e4b)` encontrará `a` en `e4b`.
- `where("b", e4b)` no encuentra `b` en `e4b`, así que busca en su padre, `e4a`, y lo encuentra ahí.
- `where("c", e4b)` busca en `e4b`, entonces `e4a`, luego llega al entorno vacío y arroja un error.
Es natural trabajar con entornos recursivamente, por lo que `where()` proporciona una plantilla útil. Eliminar los detalles de `where()` muestra la estructura más claramente:
```{r}
f <- function(..., env = caller_env()) {
if (identical(env, empty_env())) {
# caso base
} else if (success) {
# caso exitoso
} else {
# caso recursivo
f(..., env = env_parent(env))
}
}
```
::: sidebarinplace
### Iteración versus recursividad {.unnumbered}
Es posible usar un bucle en lugar de recursividad. Creo que es más difícil de entender que la versión recursiva, pero la incluyo porque puede resultarle más fácil ver lo que sucede si no ha escrito muchas funciones recursivas.
```{r}
f2 <- function(..., env = caller_env()) {
while (!identical(env, empty_env())) {
if (success) {
# caso exitoso
return()
}
# inspeccionar padre
env <- env_parent(env)
}
# caso base
}
```
:::
### Ejercicios
1. Modifique `where()` para devolver *todos* los entornos que contienen un enlace para `name`. Piensa detenidamente qué tipo de objeto necesitará devolver la función.
2. Escribe una función llamada `fget()` que encuentre solo objetos de función. Debe tener dos argumentos, `name` y `env`, y debe obedecer las reglas regulares de alcance de las funciones: si hay un objeto con un nombre coincidente que no es una función, busque en el padre. Para un desafío adicional, agregue también un argumento `inherits` que controle si la función recurre a los padres o solo busca en un entorno.
## Entornos especiales {#sec-special-environments}
La mayoría de los entornos no los crea usted (por ejemplo, con `env()`), sino que los crea R. En esta sección, aprenderá sobre los entornos más importantes, comenzando con los entornos de paquete. Luego, aprenderá sobre el entorno de la función vinculado a la función cuando se crea y el entorno de ejecución (generalmente) efímero que se crea cada vez que se llama a la función. Finalmente, verá cómo los entornos de funciones y paquetes interactúan para admitir espacios de nombres, lo que garantiza que un paquete siempre se comporte de la misma manera, independientemente de qué otros paquetes haya cargado el usuario.
### Entornos de paquetes y la ruta de búsqueda {#sec-search-path}
\index{search()}
\index{search path} \index{Autoloads} \index{environments!base}
Cada paquete adjunto por `library()` o `require()` se convierte en uno de los padres del entorno global. El padre inmediato del entorno global es el último paquete que adjuntó [^environments-2], el padre de ese paquete es el penúltimo paquete que adjuntó, ...
[^environments-2]: Tenga en cuenta la diferencia entre adjunto y cargado. Un paquete se carga automáticamente si accede a una de sus funciones usando `::`; solo se **adjunta** a la ruta de búsqueda mediante `library()` o `require()`.
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/environments/search-path.png")
```
Si sigue a todos los padres hacia atrás, verá el orden en que se adjuntó cada paquete. Esto se conoce como **ruta de búsqueda** porque todos los objetos en estos entornos se pueden encontrar desde el espacio de trabajo interactivo de nivel superior. Puede ver los nombres de estos entornos con `base::search()`, o los propios entornos con `rlang::search_envs()`:
```{r}
search()
search_envs()
```
Los dos últimos entornos en la ruta de búsqueda son siempre los mismos:
- El entorno `Autoloads` utiliza enlaces retrasados para ahorrar memoria al cargar solo objetos del paquete (como grandes conjuntos de datos) cuando es necesario.
- El entorno base, `package: base` o, a veces, simplemente `base`, es el entorno del paquete base. Es especial porque debe poder iniciar la carga de todos los demás paquetes. Puedes acceder a él directamente con `base_env()`.
Tenga en cuenta que cuando adjunta otro paquete con `library()`, el entorno principal del entorno global cambia:
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/environments/search-path-2.png")
```
### El entorno funcional {#sec-function-environments}
\index{environments!function} \index{fn\_env()}
Una función vincula el entorno actual cuando se crea. Esto se denomina **entorno de función** y se utiliza para el scoping léxico. En todos los lenguajes informáticos, las funciones que capturan (o encierran) sus entornos se denominan **cierres**, razón por la cual este término a menudo se usa indistintamente con *función* en la documentación de R.
Puede obtener el entorno de la función con `fn_env()`:
```{r}
y <- 1
f <- function(x) x + y
fn_env(f)
```
::: base
Utilice `environment(f)` para acceder al entorno de la función `f`.
:::
En los diagramas, dibujaré una función como un rectángulo con un extremo redondeado que une un entorno.
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/environments/binding.png")
```
En este caso, `f()` vincula el entorno que vincula el nombre `f` a la función. Pero ese no es siempre el caso: en el siguiente ejemplo, `g` está enlazado en un nuevo entorno `e`, pero `g()` enlaza el entorno global. La distinción entre atar y ser atado por es sutil pero importante; la diferencia es cómo encontramos `g` versus cómo `g` encuentra sus variables.
```{r}
e <- env()
e$g <- function() 1
```
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/environments/binding-2.png")
```
### Espacios de nombres
\index{namespaces}
En el diagrama anterior, vio que el entorno principal de un paquete varía según los otros paquetes que se hayan cargado. Esto parece preocupante: ¿no significa eso que el paquete encontrará diferentes funciones si los paquetes se cargan en un orden diferente? El objetivo de los **espacios de nombres** es asegurarse de que esto no suceda y de que todos los paquetes funcionen de la misma manera, independientemente de los paquetes que adjunte el usuario.
Por ejemplo, tome `sd()`:
```{r}
sd
```
`sd()` se define en términos de `var()`, por lo que podría preocuparse de que el resultado de `sd()` se vea afectado por cualquier función llamada `var()` ya sea en el entorno global o en uno de los otros paquetes adjuntos . R evita este problema aprovechando el entorno de función versus enlace descrito anteriormente. Cada función en un paquete está asociada con un par de entornos: el entorno del paquete, del que aprendió anteriormente, y el entorno del **espacio de nombres**.
- El entorno del paquete es la interfaz externa del paquete. Así es como usted, el usuario de R, encuentra una función en un paquete adjunto o con `::`. Su padre está determinado por la ruta de búsqueda, es decir, el orden en que se han adjuntado los paquetes.
- El entorno del espacio de nombres es la interfaz interna del paquete. El entorno del paquete controla cómo encontramos la función; el espacio de nombres controla cómo la función encuentra sus variables.
Cada enlace en el entorno del paquete también se encuentra en el entorno del espacio de nombres; esto asegura que cada función pueda usar cualquier otra función en el paquete. Pero algunos enlaces solo ocurren en el entorno del espacio de nombres. Estos se conocen como objetos internos o no exportados, que permiten ocultar al usuario detalles de implementación internos.
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/environments/namespace-bind.png")
```
Cada entorno de espacio de nombres tiene el mismo conjunto de ancestros:
- Cada espacio de nombres tiene un entorno de **importaciones** que contiene enlaces a todas las funciones utilizadas por el paquete. El entorno de importación está controlado por el desarrollador del paquete con el archivo `NAMESPACE`.
- La importación explícita de cada función base sería tediosa, por lo que el padre del entorno de importación es el **espacio de nombres** base. El espacio de nombres base contiene los mismos enlaces que el entorno base, pero tiene un padre diferente.
- El padre del espacio de nombres base es el entorno global. Esto significa que si un enlace no está definido en el entorno de importación, el paquete lo buscará de la forma habitual. Esto suele ser una mala idea (porque hace que el código dependa de otros paquetes cargados), por lo que `R CMD check` advierte automáticamente sobre dicho código. Es necesario principalmente por razones históricas, particularmente debido a cómo funciona el envío del método S3.
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/environments/namespace-env.png")
```
Juntando todos estos diagramas obtenemos:
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/environments/namespace.png")
```
Entonces, cuando `sd()` busca el valor de `var`, siempre lo encuentra en una secuencia de entornos determinada por el desarrollador del paquete, pero no por el usuario del paquete. Esto garantiza que el código del paquete siempre funcione de la misma manera, independientemente de los paquetes que haya adjuntado el usuario.
No existe un vínculo directo entre el paquete y los entornos de espacio de nombres; el enlace está definido por los entornos de función.
### Entornos de ejecución {#sec-execution-environments}
\index{environments!execution} \index{functions!environment}
El último tema importante que debemos cubrir es el entorno de **ejecución**. ¿Qué devolverá la siguiente función la primera vez que se ejecute? ¿Qué pasa con el segundo?
```{r}
g <- function(x) {
if (!env_has(current_env(), "a")) {
message("Defining a")
a <- 1
} else {
a <- a + 1
}
a
}
```
Piénsalo un momento antes de seguir leyendo.
```{r}
g(10)
g(10)
```
Esta función devuelve el mismo valor cada vez debido al principio de nuevo comienzo, descrito en la @sec-fresh-start. Cada vez que se llama a una función, se crea un nuevo entorno para albergar la ejecución. Esto se denomina entorno de ejecución y su padre es el entorno de funciones. Ilustremos ese proceso con una función más simple. La figura @fig-execution-env ilustra las convenciones gráficas: dibujo entornos de ejecución con un padre indirecto; el entorno principal se encuentra a través del entorno de función.
```{r}
h <- function(x) {
# 1.
a <- 2 # 2.
x + a
}
y <- h(1) # 3.
```
```{r}
#| label: fig-execution-env
#| echo: FALSE
#| out.width: NULL
#| fig.cap : "The execution environment of a simple function call. Note that the parent of the execution environment is the function environment."
knitr::include_graphics("diagrams/environments/execution.png")
```
Un entorno de ejecución suele ser efímero; una vez que la función se haya completado, el entorno se recolectará como basura. Hay varias maneras de hacer que se quede por más tiempo. El primero es devolverlo explícitamente:
```{r}
h2 <- function(x) {
a <- x * 2
current_env()
}
e <- h2(x = 10)
env_print(e)
fn_env(h2)
```
Otra forma de capturarlo es devolver un objeto con un enlace a ese entorno, como una función. El siguiente ejemplo ilustra esa idea con una fábrica de funciones, `plus()`. Usamos esa fábrica para crear una función llamada `plus_one()`.
Están sucediendo muchas cosas en el diagrama porque el entorno envolvente de `plus_one()` es el entorno de ejecución de `plus()`.
```{r}
plus <- function(x) {
function(y) x + y
}
plus_one <- plus(1)
plus_one
```
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/environments/closure.png")
```
¿Qué sucede cuando llamamos `plus_one()`? Su entorno de ejecución tendrá el entorno de ejecución capturado de `plus()` como padre:
```{r}
plus_one(2)
```
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/environments/closure-call.png")
```
Aprenderá más sobre las fábricas de funciones en la @sec-factory-fundamentals.
### Ejercicios
1. ¿En qué se diferencia `search_envs()` de `env_parents(global_env())`?
2. Dibuje un diagrama que muestre los entornos circundantes de esta función:
```{r, eval = FALSE}
f1 <- function(x1) {
f2 <- function(x2) {
f3 <- function(x3) {
x1 + x2 + x3
}
f3(3)
}
f2(2)
}
f1(1)
```
3. Escriba una versión mejorada de `str()` que proporcione más información sobre las funciones. Muestre dónde se encontró la función y en qué entorno se definió.
## Pilas de llamadas {#sec-call-stack}
\index{environments!calling} \index{parent.frame()} \index{call stacks}
Hay un último entorno que debemos explicar, el entorno **caller**, al que se accede con `rlang::caller_env()`. Esto proporciona el entorno desde el que se llamó a la función y, por lo tanto, varía en función de cómo se llame a la función, no de cómo se creó. Como vimos anteriormente, este es un valor predeterminado útil cada vez que escribe una función que toma un entorno como argumento.
::: base
`parent.frame()` es equivalente a `caller_env()`; solo tenga en cuenta que devuelve un entorno, no un marco.
:::
Para comprender completamente el entorno de la persona que llama, debemos analizar dos conceptos relacionados: la **pila de llamadas**, que se compone de **marcos**. La ejecución de una función crea dos tipos de contexto. Ya aprendió sobre uno: el entorno de ejecución es un elemento secundario del entorno de función, que está determinado por el lugar donde se creó la función. Hay otro tipo de contexto creado por donde se llamó a la función: esto se llama la pila de llamadas.
<!-- HW: mencionar que esto es en realidad un árbol! -->
### Pilas de llamadas simples {#sec-simple-stack}
```{=tex}
\index{cst()}
\index{traceback()}
```
Ilustremos esto con una secuencia simple de llamadas: `f()` llama a `g()` llama a `h()`.
```{r}
f <- function(x) {
g(x = 2)
}
g <- function(x) {
h(x = 3)
}
h <- function(x) {
stop()
}
```
La forma más común de ver una pila de llamadas en R es mirando el `traceback()` después de que haya ocurrido un error:
```{r, eval = FALSE}
f(x = 1)
#> Error:
traceback()
#> 4: stop()
#> 3: h(x = 3)
#> 2: g(x = 2)
#> 1: f(x = 1)
```
En lugar de `stop()` + `traceback()` para entender la pila de llamadas, vamos a usar `lobstr::cst()` para imprimir el árbol de pilas de llamadas (**c**all **s**tack *t*ree, en inglés):
```{r, eval = FALSE}
h <- function(x) {
lobstr::cst()
}
f(x = 1)
#> █
#> └─f(x = 1)
#> └─g(x = 2)
#> └─h(x = 3)
#> └─lobstr::cst()
```
Esto nos muestra que `cst()` fue llamado desde `h()`, que fue llamado desde `g()`, que fue llamado desde `f()`. Tenga en cuenta que el orden es el opuesto de `traceback()`. A medida que las pilas de llamadas se vuelven más complicadas, creo que es más fácil entender la secuencia de llamadas si comienza desde el principio, en lugar del final (es decir, `f()` llama a `g()`; en lugar de `g()` fue llamado por `f()`).
### Evaluación perezosa {#sec-lazy-call-stack}
\index{lazy evaluation}
La pila de llamadas anterior es simple: mientras obtiene una pista de que hay una estructura similar a un árbol involucrada, todo sucede en una sola rama. Esto es típico de una pila de llamadas cuando todos los argumentos se evalúan con entusiasmo.
Vamos a crear un ejemplo más complicado que implique una evaluación perezosa. Crearemos una secuencia de funciones, `a()`, `b()`, `c()`, que pasan un argumento `x`.
```{r, eval = FALSE}
a <- function(x) b(x)
b <- function(x) c(x)
c <- function(x) x
a(f())
#> █
#> ├─a(f())
#> │ └─b(x)
#> │ └─c(x)
#> └─f()
#> └─g(x = 2)
#> └─h(x = 3)
#> └─lobstr::cst()
```
`x` se evalúa perezosamente, por lo que este árbol tiene dos ramas. En la primera rama `a()` llama a `b()`, luego `b()` llama a `c()`. La segunda rama comienza cuando `c()` evalúa su argumento `x`. Este argumento se evalúa en una nueva rama porque el entorno en el que se evalúa es el entorno global, no el entorno de `c()`.
### Marcos
\index{frame} \index{parent.frame()}
Cada elemento de la pila de llamadas es un **marco**[^environments-3], también conocido como contexto de evaluación. El marco es una estructura de datos interna extremadamente importante, y el código R solo puede acceder a una pequeña parte de la estructura de datos porque manipularlo romperá R. Un marco tiene tres componentes clave:
[^environments-3]: NB: `?environment` usa marco en un sentido diferente: "Los entornos consisten en un *marco*, o una colección de objetos con nombre, y un puntero a un entorno envolvente". Evitamos este sentido de marco, que proviene de S, porque es muy específico y no se usa mucho en la base R. Por ejemplo, el marco en `parent.frame()` es un contexto de ejecución, no una colección de objetos con nombre.
- Una expresión (etiquetada con `expr`) que da la llamada a la función. Esto es lo que imprime `traceback()`.
- Un entorno (etiquetado con `env`), que suele ser el entorno de ejecución de una función. Hay dos excepciones principales: el entorno del marco global es el entorno global, y llamar a `eval()` también genera marcos, donde el entorno puede ser cualquier cosa.
- Un padre, la llamada anterior en la pila de llamadas (se muestra con una flecha gris).
La figura @fig-calling ilustra la pila para la llamada a `f(x = 1)` que se muestra en la @sec-simple-stack.
```{r}
#| label: fig-calling
#| echo: FALSE
#| out.width: NULL
#| fig.cap: "The graphical depiction of a simple call stack"
knitr::include_graphics("diagrams/environments/calling.png")
```
(Para centrarme en los entornos de llamada, he omitido los enlaces en el entorno global de `f`, `g` y `h` a los objetos de función respectivos.)
El marco también contiene controladores de salida creados con `on.exit()`, reinicios y controladores para el sistema de condiciones, y a qué contexto `return()` cuando se completa una función. Estos son detalles internos importantes a los que no se puede acceder con el código R.
### Alcance dinámico
\index{scoping!dynamic}
La búsqueda de variables en la pila de llamadas en lugar de en el entorno adjunto se denomina **ámbito dinámico**. Pocos lenguajes implementan el alcance dinámico (Emacs Lisp es una \[excepción notable\] (http://www.gnu.org/software/emacs/emacs-paper.html#SEC15).) Esto se debe a que el alcance dinámico hace que sea mucho más difícil razonar sobre cómo opera una función: no solo necesita saber cómo se definió, también necesita saber el contexto en el que se llamó. El alcance dinámico es principalmente útil para desarrollar funciones que ayudan al análisis interactivo de datos y es uno de los temas tratados en el @sec-evaluation.
### Ejercicios
1. Escriba una función que enumere todas las variables definidas en el entorno en el que se llamó. Debería devolver los mismos resultados que `ls()`.
## Como estructuras de datos {#sec-explicit-envs}
\index{hashmaps} \index{dictionaries|see {hashmaps}}
Además de potenciar el alcance, los entornos también son estructuras de datos útiles por derecho propio porque tienen semántica de referencia. Hay tres problemas comunes que pueden ayudar a resolver:
- **Evitar copias de datos de gran tamaño**. Dado que los entornos tienen semántica de referencia, nunca creará una copia accidentalmente. Pero es complicado trabajar con entornos desnudos, por lo que en su lugar recomiendo usar objetos R6, que se construyen sobre los entornos. Obtenga más información en el @sec-r6.
- **Administrar el estado dentro de un paquete**. Los entornos explícitos son útiles en los paquetes porque le permiten mantener el estado en las llamadas a funciones. Normalmente, los objetos de un paquete están bloqueados, por lo que no puede modificarlos directamente. En su lugar, puedes hacer algo como esto:
```{r}
my_env <- new.env(parent = emptyenv())
my_env$a <- 1
get_a <- function() {
my_env$a
}
set_a <- function(value) {
old <- my_env$a
my_env$a <- value
invisible(old)
}
```
Devolver el valor anterior de las funciones de establecimiento es un buen patrón porque hace que sea más fácil restablecer el valor anterior junto con `on.exit()` (@sec-on-exit).
- **Como hashmap**. Un hashmap es una estructura de datos que toma tiempo constante, O(1), para encontrar un objeto basado en su nombre. Los entornos proporcionan este comportamiento de forma predeterminada, por lo que se pueden usar para simular un mapa hash. Vea el paquete hash [@hash] para un desarrollo completo de esta idea.
## Respuestas de la prueba {#sec-env-answers}
1. Hay cuatro formas: cada objeto en un entorno debe tener un nombre; el orden no importa; los ambientes tienen padres; los entornos tienen semántica de referencia.
2. El padre del entorno global es el último paquete que cargó. El único entorno que no tiene un padre es el entorno vacío.
3. El entorno envolvente de una función es el entorno donde se creó. Determina dónde una función busca variables.
4. Use `caller_env()` o `parent.frame()`.
5. `<-` siempre crea un enlace en el entorno actual; `<<-` vuelve a enlazar un nombre existente en un padre del entorno actual.