-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathExpressions.qmd
943 lines (639 loc) · 37.7 KB
/
Expressions.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
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
# Expresiones {#sec-expressions}
```{r, include = FALSE}
source("common.R")
```
## Introducción
\index{expressions}
Para calcular el lenguaje, primero necesitamos entender su estructura. Eso requiere un vocabulario nuevo, algunas herramientas nuevas y algunas formas nuevas de pensar sobre el código R. El primero de ellos es la distinción entre una operación y su resultado. Toma el siguiente código, que multiplica una variable `x` por 10 y guarda el resultado en una nueva variable llamada `y`. No funciona porque no hemos definido una variable llamada `x`:
```{r, error = TRUE}
y <- x * 10
```
Sería bueno si pudiéramos capturar la intención del código sin ejecutarlo. En otras palabras, ¿cómo podemos separar nuestra descripción de la acción de la acción misma?
Una forma es usar `rlang::expr()`:
```{r}
z <- rlang::expr(y <- x * 10)
z
```
`expr()` devuelve una expresión, un objeto que captura la estructura del código sin evaluarlo (es decir, ejecutarlo). Si tiene una expresión, puede evaluarla con `base::eval()`:
```{r}
x <- 4
eval(z)
y
```
El enfoque de este capítulo son las estructuras de datos que subyacen a las expresiones. Dominar este conocimiento le permitirá inspeccionar y modificar el código capturado y generar código con código. Volveremos a `expr()` en el @sec-quasiquotation, ya `eval()` en el @sec-evaluation.
### Estructura {.unnumbered}
- La @sec-ast introduce la idea del árbol de sintaxis abstracta (AST) y revela la estructura de árbol que subyace en todo el código R.
- La @sec-expression-details se sumerge en los detalles de las estructuras de datos que sustentan el AST: constantes, símbolos y llamadas, que se conocen colectivamente como expresiones.
- La @sec-grammar cubre el análisis, el acto de convertir la secuencia lineal de caracteres en código en AST, y usa esa idea para explorar algunos detalles de la gramática de R.
- La @sec-ast-funs le muestra cómo puede usar funciones recursivas para calcular en el lenguaje, escribiendo funciones que calculan con expresiones.
- La @sec-expression-special vuelve a tres estructuras de datos más especializadas: listas de pares, argumentos perdidos y vectores de expresión.
### Requisitos previos {.unnumbered}
Asegúrese de haber leído la descripción general de la metaprogramación en el @sec-meta-big-picture para obtener una descripción general amplia de la motivación y el vocabulario básico. También necesitará el paquete [rlang](https://rlang.r-lib.org) para capturar y calcular expresiones, y el paquete [lobstr](https://lobstr.r-lib.org) para visualizarlos.
```{r setup}
library(rlang)
library(lobstr)
```
## Árboles de sintaxis abstracta {#sec-ast}
\index{ASTs} \index{abstract syntax tree!see {ASTs}}
Las expresiones también se denominan **árboles de sintaxis abstracta** (AST) porque la estructura del código es jerárquica y se puede representar naturalmente como un árbol. Comprender esta estructura de árbol es crucial para inspeccionar y modificar expresiones (es decir, metaprogramación).
### Dibujo
\index{ASTs!ast()@\texttt{ast()}}
Comenzaremos presentando algunas convenciones para dibujar AST, comenzando con una simple llamada que muestra sus componentes principales: `f(x, "y", 1)`. Dibujaré árboles de dos maneras[^expressions-1]:
[^expressions-1]: Para un código más complejo, también puede usar el visor de árboles de RStudio, que no obedece a las mismas convenciones gráficas, pero le permite explorar de forma interactiva grandes AST. Pruébelo con `View(expr(f(x, "y", 1)))`.
- A "mano" (es decir, con OmniGraffle):
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/expressions/simple.png")
```
- Con `lobstr::ast()`:
```{r}
lobstr::ast(f(x, "y", 1))
```
Ambos enfoques comparten convenciones tanto como sea posible:
- Las hojas del árbol son símbolos, como `f` y `x`, o constantes, como `1` o `"y"`. Los símbolos se dibujan en púrpura y tienen esquinas redondeadas. Las constantes tienen bordes negros y esquinas cuadradas. Las cadenas y los símbolos se confunden fácilmente, por lo que las cadenas siempre se escriben entre comillas.
- Las ramas del árbol son objetos de llamada, que representan llamadas de función y se dibujan como rectángulos naranjas. El primer hijo (`f`) es la función que se llama; el segundo hijo y los subsiguientes (`x`, `"y"` y `1`) son los argumentos de esa función.
Los colores se mostrarán cuando *usted* llame a `ast()`, pero no aparecen en el libro por razones técnicas complicadas.
El ejemplo anterior solo contenía una llamada de función, lo que lo convierte en un árbol muy poco profundo. La mayoría de las expresiones contendrán considerablemente más llamadas, creando árboles con múltiples niveles. Por ejemplo, considere el AST para `f(g(1, 2), h(3, 4, i()))`:
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/expressions/complicated.png")
```
```{r}
lobstr::ast(f(g(1, 2), h(3, 4, i())))
```
Puede leer los diagramas dibujados a mano de izquierda a derecha (ignorando la posición vertical) y los diagramas dibujados por langosta de arriba a abajo (ignorando la posición horizontal). La profundidad dentro del árbol está determinada por el anidamiento de las llamadas a funciones. Esto también determina el orden de evaluación, ya que la evaluación generalmente procede de lo más profundo a lo más superficial, pero esto no está garantizado debido a la evaluación diferida (@sec-lazy-evaluation). También tenga en cuenta la aparición de `i()`, una llamada de función sin argumentos; es una rama con una sola hoja (símbolo).
### Componentes sin código
\index{ASTs!non-code}
Es posible que se haya preguntado qué hace que estos árboles de sintaxis *abstractos*. Son abstractos porque solo capturan detalles estructurales importantes del código, no espacios en blanco ni comentarios:
```{r}
ast(
f(x, y) # important!
)
```
Solo hay un lugar donde los espacios en blanco afectan el AST:
```{r}
lobstr::ast(y <- x)
lobstr::ast(y < -x)
```
### Llamadas infijas
\index{ASTs!infix calls}
Cada llamada en R se puede escribir en forma de árbol porque cualquier llamada se puede escribir en forma de prefijo (@sec-prefix-transform). Tome `y <- x * 10` de nuevo: ¿cuáles son las funciones que se están llamando? No es tan fácil de detectar como `f(x, 1)` porque esta expresión contiene dos llamadas infijas: `<-` y `*`. Eso significa que estas dos líneas de código son equivalentes:
```{r, eval = FALSE}
y <- x * 10
`<-`(y, `*`(x, 10))
```
Y ambas tienen este AST[^expressions-2]:
[^expressions-2]: Los nombres de las funciones sin prefijo no son sintácticos, por lo que los rodeo con ``` `` ```, como en la @sec-non-syntactic.
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/expressions/prefix.png")
```
```{r}
lobstr::ast(y <- x * 10)
```
Realmente no hay diferencia entre los AST, y si genera una expresión con llamadas de prefijo, R aún la imprimirá en forma de infijo:
```{r}
expr(`<-`(y, `*`(x, 10)))
```
El orden en que se aplican los operadores infijos se rige por un conjunto de reglas llamadas precedencia de operadores, y usaremos `lobstr::ast()` para explorarlas en la @sec-operator-precedence.
### Ejercicios
1. Reconstruya el código representado por los árboles a continuación:
```{r, echo = FALSE}
ast(f(g(h())))
ast(1 + 2 + 3)
ast((x + y) * z)
```
2. Dibuja los siguientes árboles a mano y luego verifica tus respuestas con `lobstr::ast()`.
```{r, eval = FALSE}
f(g(h(i(1, 2, 3))))
f(1, g(2, h(3, i())))
f(g(1, 2), h(3, i(4, 5)))
```
3. ¿Qué está pasando con los AST a continuación? (Sugerencia: lea atentamente `?"^"`.)
```{r}
lobstr::ast(`x` + `y`)
lobstr::ast(x ** y)
lobstr::ast(1 -> x)
```
4. ¿Qué está pasando con los AST a continuación? (Sugerencia: lea atentamente la @sec-fun-components.)
```{r}
lobstr::ast(function(x = 1, y = 2) {})
```
5. ¿Cómo se ve el árbol de llamadas de una instrucción `if` con múltiples condiciones `else if`? ¿Por qué?
## Expresiones {#sec-expression-details}
\index{expressions} \index{expr()}
En conjunto, las estructuras de datos presentes en el AST se denominan expresiones. Una **expresión** es cualquier miembro del conjunto de tipos base creados mediante el código de análisis: escalares constantes, símbolos, objetos de llamada y listas de pares. Estas son las estructuras de datos utilizadas para representar el código capturado de `expr()`, y `is_expression(expr(...))` siempre es verdadero. Las constantes, los símbolos y los objetos de llamada son los más importantes y se analizan a continuación. Las listas de pares y los símbolos vacíos son más especializados y volveremos a ellos en las secciones @sec-pairlists y @sec-empty-symbol.
NB: En la documentación base de R, "expresión" se usa para significar dos cosas. Además de la definición anterior, expresión también se usa para referirse al tipo de objeto devuelto por `expression()` y `parse()`, que son básicamente listas de expresiones como se define anteriormente. En este libro llamaré a estos **vectores de expresión**, y regresaré a ellos en la @sec-expression-vectors.
### Constantes
\index{constants} \index{scalars}
Las constantes escalares son el componente más simple del AST. Más precisamente, una **constante** es `NULL` o un vector atómico de longitud 1 (o escalar, @sec-scalars) como `TRUE`, `1L`, `2.5` o `"x"`. Puedes probar una constante con `rlang::is_syntactic_literal()`.
Las constantes son autocomillas en el sentido de que la expresión utilizada para representar una constante es la misma constante:
```{r}
identical(expr(TRUE), TRUE)
identical(expr(1), 1)
identical(expr(2L), 2L)
identical(expr("x"), "x")
```
### Simbolos
\index{symbols} \index{names|see {symbols}} \index{sym()}
Un **símbolo** representa el nombre de un objeto como `x`, `mtcars` o `mean`. En la base R, los términos símbolo y nombre se usan indistintamente (es decir, `is.name()` es idéntico a `is.symbol()`), pero en este libro usé símbolo de manera consistente porque "nombre" tiene muchos otros significados.
Puede crear un símbolo de dos maneras: capturando el código que hace referencia a un objeto con `expr()`, o convirtiendo una cadena en un símbolo con `rlang::sym()`:
```{r}
expr(x)
sym("x")
```
\index{as\_string()}
Puede volver a convertir un símbolo en una cadena con `as.character()` o `rlang::as_string()`. `as_string()` tiene la ventaja de indicar claramente que obtendrá un vector de caracteres de longitud 1.
```{r}
as_string(expr(x))
```
Puede reconocer un símbolo porque está impreso sin comillas, `str()` le dice que es un símbolo, y `is.symbol()` es `TRUE`:
```{r}
str(expr(x))
is.symbol(expr(x))
```
El tipo de símbolo no está vectorizado, es decir, un símbolo siempre tiene una longitud de 1. Si desea varios símbolos, deberá ponerlos en una lista usando (p. ej.) `rlang::syms()`.
### Llamadas
\index{call objects} \index{language objects!see {call objects}}
Un **objeto de llamada** representa una llamada de función capturada. Los objetos de llamada son un tipo especial de lista donde el primer componente especifica la función a llamar (generalmente un símbolo), y los elementos restantes son los argumentos para esa llamada. Los objetos de llamada crean ramas en el AST, porque las llamadas se pueden anidar dentro de otras llamadas.
Puede identificar un objeto de llamada cuando se imprime porque parece una llamada de función. Confusamente `typeof()` y `str()` imprimen "lenguaje" para los objetos de llamada, pero `is.call()` devuelve `TRUE`:
```{r}
lobstr::ast(read.table("important.csv", row.names = FALSE))
x <- expr(read.table("important.csv", row.names = FALSE))
typeof(x)
is.call(x)
```
#### Subconjunto
\index{call objects!subsetting}
Las llamadas generalmente se comportan como listas, es decir, puede usar herramientas estándar de creación de subconjuntos. El primer elemento del objeto de llamada es la función a llamar, que suele ser un símbolo:
```{r}
x[[1]]
is.symbol(x[[1]])
```
El resto de los elementos son los argumentos:
```{r}
as.list(x[-1])
```
Puede extraer argumentos individuales con `[[` o, si se nombra, `$`:
```{r}
x[[2]]
x$row.names
```
Puede determinar la cantidad de argumentos en un objeto de llamada restando 1 de su longitud:
```{r}
length(x) - 1
```
Extraer argumentos específicos de las llamadas es un desafío debido a las reglas flexibles de R para la coincidencia de argumentos: potencialmente podría estar en cualquier ubicación, con el nombre completo, con un nombre abreviado o sin nombre. Para solucionar este problema, puede usar `rlang::call_standardise()` que estandariza todos los argumentos para usar el nombre completo: \index{standardise\_call()}
```{r}
rlang::call_standardise(x)
```
(NB: Si la función usa `...` no es posible estandarizar todos los argumentos).
Las llamadas se pueden modificar de la misma forma que las listas:
```{r}
x$header <- TRUE
x
```
#### Posición de la función
\index{call objects!function component}
El primer elemento del objeto de llamada es la **posición de la función**. Contiene la función que se llamará cuando se evalúe el objeto, y generalmente es un símbolo [^expressions-3]:
[^expressions-3]: Curiosamente, también puede ser un número, como en la expresión `3()`. Pero esta llamada siempre fallará en la evaluación porque un número no es una función.
```{r}
lobstr::ast(foo())
```
Mientras que R le permite rodear el nombre de la función con comillas, el analizador lo convierte en un símbolo:
```{r}
lobstr::ast("foo"())
```
Sin embargo, a veces la función no existe en el entorno actual y es necesario realizar algunos cálculos para recuperarla: por ejemplo, si la función está en otro paquete, es un método de un objeto R6 o es creada por una fábrica de funciones. En este caso, la posición de la función será ocupada por otra llamada:
```{r}
lobstr::ast(pkg::foo(1))
lobstr::ast(obj$foo(1))
lobstr::ast(foo(1)(2))
```
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/expressions/call-call.png")
```
#### Construyendo {#sec-call2}
\index{call objects!constructing} \index{call2()}
Puede construir un objeto de llamada a partir de sus componentes utilizando `rlang::call2()`. El primer argumento es el nombre de la función a llamar (ya sea como una cadena, un símbolo u otra llamada). Los argumentos restantes se pasarán a la llamada:
```{r}
call2("mean", x = expr(x), na.rm = TRUE)
call2(expr(base::mean), x = expr(x), na.rm = TRUE)
```
Las llamadas de infijo creadas de esta manera aún se imprimen como de costumbre.
```{r}
call2("<-", expr(x), 10)
```
Usar `call2()` para crear expresiones complejas es un poco torpe. Aprenderás otra técnica en el @sec-quasiquotation.
### Resumen
La siguiente tabla resume la apariencia de los diferentes subtipos de expresión en `str()` y `typeof()`:
| | `str()` | `typeof()` |
|-------------------|---------------------|--------------------------------|
| constante escalar | `logi`/`int`/`num`/`chr` | `logical`/`integer`/`double`/`character` |
| Símbolo | `symbol` | `symbol` |
| Objeto de llamada | `language` | `language` |
| Lista de pares | Lista de pares punteados | `pairlist` |
| Vector de expresión | `expression()` | `expression` |
Tanto base R como rlang proporcionan funciones para probar cada tipo de entrada, aunque los tipos cubiertos son ligeramente diferentes. Puede distinguirlas fácilmente porque todas las funciones básicas comienzan con `is.` y las funciones rlang comienzan con `is_`.
\newpage
<!-- New page so that there's no beak inside the table -->
| | base | rlang |
|-------------------|-------------------|--------------------------|
| Scalar constant | --- | `is_syntactic_literal()` |
| Symbol | `is.symbol()` | `is_symbol()` |
| Call object | `is.call()` | `is_call()` |
| Pairlist | `is.pairlist()` | `is_pairlist()` |
| Expression vector | `is.expression()` | --- |
### Ejercicios
1. ¿Cuáles dos de los seis tipos de vectores atómicos no pueden aparecer en una expresión? ¿Por qué? De manera similar, ¿por qué no puedes crear una expresión que contenga un vector atómico de longitud mayor que uno?
2. ¿Qué sucede cuando crea un subconjunto de un objeto de llamada para eliminar el primer elemento? p.ej. `expr(read.csv("foo.csv", header = TRUE))[-1]`. ¿Por qué?
3. Describa las diferencias entre los siguientes objetos de llamada.
```{r, results = FALSE}
x <- 1:10
call2(median, x, na.rm = TRUE)
call2(expr(median), x, na.rm = TRUE)
call2(median, expr(x), na.rm = TRUE)
call2(expr(median), expr(x), na.rm = TRUE)
```
4. `rlang::call_standardise()` no funciona tan bien para las siguientes llamadas. ¿Por qué? ¿Qué hace especial a `mean()`?
```{r}
call_standardise(quote(mean(1:10, na.rm = TRUE)))
call_standardise(quote(mean(n = T, 1:10)))
call_standardise(quote(mean(x = 1:10, , TRUE)))
```
5. ¿Por qué este código no tiene sentido?
```{r, eval = FALSE}
x <- expr(foo(x = 1))
names(x) <- c("x", "y")
```
6. Construya la expresión `if(x > 1) "a" else "b"` utilizando varias llamadas a `call2()`. ¿Cómo refleja la estructura del código la estructura del AST?
## Análisis y gramática {#sec-grammar}
\index{grammar}
Hemos hablado mucho sobre las expresiones y el AST, pero no sobre cómo se crean las expresiones a partir del código que escribe (como `"x + y"`). El proceso mediante el cual un lenguaje informático toma una cadena y construye una expresión se denomina **análisis sintáctico** y se rige por un conjunto de reglas conocido como **gramática**. En esta sección, usaremos `lobstr::ast()` para explorar algunos de los detalles de la gramática de R, y luego mostraremos cómo puede transformar de un lado a otro entre expresiones y cadenas.
### Predecencia de operadores {#sec-operator-precedence}
\index{operator precedence}
Las funciones infijas introducen dos fuentes de ambigüedad. La primera fuente de ambigüedad surge de las funciones infijas: ¿qué produce `1 + 2 * 3`? ¿Obtienes 9 (es decir, `(1 + 2) * 3`), o 7 (es decir, `1 + (2 * 3)`)? En otras palabras, ¿cuál de los dos posibles árboles de análisis de abajo usa R?
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/expressions/ambig-order.png")
```
Los lenguajes de programación usan convenciones llamadas **precedencia de operadores** para resolver esta ambigüedad. Podemos usar `ast()` para ver qué hace R:
```{r}
lobstr::ast(1 + 2 * 3)
```
Predicting the precedence of arithmetic operations is usually easy because it's drilled into you in school and is consistent across the vast majority of programming languages.
Predecir la precedencia de otros operadores es más difícil. Hay un caso particularmente sorprendente en R: `!` tiene una precedencia mucho menor (es decir, se une con menos fuerza) de lo que cabría esperar. Esto le permite escribir operaciones útiles como:
```{r}
lobstr::ast(!x %in% y)
```
R tiene más de 30 operadores infijos divididos en 18 grupos de precedencia. Si bien los detalles se describen en `?Syntax`, muy pocas personas han memorizado el orden completo. Si hay alguna confusión, ¡use paréntesis!
```{r}
lobstr::ast((1 + 2) * 3)
```
Tenga en cuenta la aparición de los paréntesis en el AST como una llamada a la función `(`.
### Asociatividad
La segunda fuente de ambigüedad se presenta por el uso repetido de la misma función de infijo. Por ejemplo, ¿es '1 + 2 + 3' equivalente a '(1 + 2) + 3' o a '1 + (2 + 3)'? Esto normalmente no importa porque `x + (y + z) == (x + y) + z`, es decir, la suma es asociativa, pero es necesaria porque algunas clases de S3 definen `+` de forma no asociativa. Por ejemplo, ggplot2 sobrecarga `+` para construir una trama compleja a partir de piezas simples; esto no es asociativo porque las capas anteriores se dibujan debajo de las capas posteriores (es decir, `geom_point()` + `geom_smooth()` no produce el mismo gráfico que `geom_smooth()` + `geom_point()`).
En R, la mayoría de los operadores son **asociativos a la izquierda**, es decir, las operaciones de la izquierda se evalúan primero:
```{r}
lobstr::ast(1 + 2 + 3)
```
Hay dos excepciones: exponenciación y asignación.
```{r}
lobstr::ast(2^2^3)
lobstr::ast(x <- y <- z)
```
### Analizar y desanalizar {#sec-parsing}
\index{parsing} \index{parsing!parse\_expr@\texttt{parse\_expr()}}
La mayoría de las veces, escribe código en la consola y R se encarga de convertir los caracteres que ha escrito en un AST. Pero ocasionalmente tiene código almacenado en una cadena y desea analizarlo usted mismo. Puedes hacerlo usando `rlang::parse_expr()`:
```{r}
x1 <- "y <- x + 10"
x1
is.call(x1)
x2 <- rlang::parse_expr(x1)
x2
is.call(x2)
```
`parse_expr()` siempre devuelve una sola expresión. Si tiene varias expresiones separadas por `;` o `\n`, deberá usar `rlang::parse_exprs()`. Devuelve una lista de expresiones:
```{r}
x3 <- "a <- 1; a + 1"
rlang::parse_exprs(x3)
```
Si se encuentra trabajando con cadenas que contienen código con mucha frecuencia, debe reconsiderar su proceso. Lea el @sec-quasiquotation y considere si puede generar expresiones utilizando la cuasicita de manera más segura.
::: base
\index{parsing!parse@\texttt{parse()}}
El equivalente básico de `parse_exprs()` es `parse()`. Es un poco más difícil de usar porque está especializado para analizar código R almacenado en archivos. Debe proporcionar su cadena al argumento `texto` y devolverá un vector de expresión (@sec-expression-vectors). Recomiendo convertir la salida en una lista:
```{r}
as.list(parse(text = x1))
```
:::
\index{deparsing} \index{expr\_text()}
Lo contrario de analizar es **deparsear**: dada una expresión, desea la cadena que la generaría. Esto sucede automáticamente cuando imprime una expresión, y puede obtener la cadena con `rlang::expr_text()`:
```{r}
z <- expr(y <- x + 10)
expr_text(z)
```
El análisis y la eliminación no son perfectamente simétricos porque el análisis genera un árbol de sintaxis *abstracto*. Esto significa que perdemos los acentos graves en los nombres, comentarios y espacios en blanco ordinarios:
```{r}
cat(expr_text(expr({
# This is a comment
x <- `x` + 1
})))
```
::: base
\index{deparse()}
Tenga cuidado al usar el equivalente base R, `deparse()`: devuelve un vector de caracteres con un elemento para cada línea. Siempre que lo use, recuerde que la longitud de la salida puede ser mayor que uno y planifique en consecuencia.
:::
### Ejercicios
1. R usa paréntesis de dos maneras ligeramente diferentes, como se ilustra en estas dos llamadas:
```{r, eval = FALSE}
f((1))
`(`(1 + 1)
```
Compare y contraste los dos usos haciendo referencia al AST.
2. `=` también se puede utilizar de dos maneras. Construya un ejemplo simple que muestre ambos usos.
3. ¿`-2^2` produce 4 o -4? ¿Por qué?
4. ¿Qué devuelve `!1 + !1`? ¿Por qué?
5. ¿Por qué `x1 <- x2 <- x3 <- 0` funciona? Describe las dos razones.
6. Compara los AST de `x + y %+% z` y `x ^ y %+% z`. ¿Qué has aprendido sobre la precedencia de las funciones de infijo personalizadas?
7. ¿Qué sucede si llamas a `parse_expr()` con una cadena que genera múltiples expresiones? p.ej. `parse_expr("x + 1; y + 1")`
8. ¿Qué sucede si intenta analizar una expresión no válida? p.ej. `"a +"` o `"f())"`.
9. `deparse()` produce vectores cuando la entrada es larga. Por ejemplo, la siguiente llamada produce un vector de longitud dos:
```{r, eval = FALSE}
expr <- expr(g(a + b + c + d + e + f + g + h + i + j + k + l +
m + n + o + p + q + r + s + t + u + v + w + x + y + z))
deparse(expr)
```
¿Qué hace `expr_text()` en su lugar?
10. `pairwise.t.test()` asume que `deparse()` siempre devuelve un vector de un carácter de longitud. ¿Puedes construir una entrada que viole esta expectativa? ¿Lo que sucede?
## Walking AST con funciones recursivas {#sec-ast-funs}
\index{recursion!over ASTs} \index{ASTs!computing with}
Para concluir el capítulo, voy a utilizar todo lo que ha aprendido sobre los AST para resolver problemas más complicados. La inspiración proviene del paquete de herramientas de código base, que proporciona dos funciones interesantes:
- `findGlobals()` localiza todas las variables globales utilizadas por una función. Esto puede ser útil si desea verificar que su función no dependa inadvertidamente de variables definidas en su entorno principal.
- `checkUsage()` comprueba una variedad de problemas comunes, incluidas las variables locales no utilizadas, los parámetros no utilizados y el uso de coincidencias de argumentos parciales.
Obtener todos los detalles de estas funciones correctamente es complicado, por lo que no desarrollaremos completamente las ideas. En su lugar, nos centraremos en la gran idea subyacente: la recursividad en el AST. Las funciones recursivas se ajustan naturalmente a las estructuras de datos de tipo árbol porque una función recursiva se compone de dos partes que corresponden a las dos partes del árbol:
- El **caso recursivo** maneja los nodos en el árbol. Por lo general, hará algo con cada hijo de un nodo, por lo general llamando a la función recursiva nuevamente, y luego combinará los resultados nuevamente. Para las expresiones, deberá manejar llamadas y listas de pares (argumentos de función).
- El **caso base** maneja las hojas del árbol. Los casos base aseguran que la función eventualmente termine, resolviendo directamente los casos más simples. Para las expresiones, debe manejar símbolos y constantes en el caso base.
Para que este patrón sea más fácil de ver, necesitaremos dos funciones auxiliares. Primero definimos `expr_type()` que devolverá "constante" para constante, "símbolo" para símbolos, "call", para llamadas, "pairlist" para listas de pares y el "tipo" de cualquier otra cosa:
```{r}
expr_type <- function(x) {
if (rlang::is_syntactic_literal(x)) {
"constant"
} else if (is.symbol(x)) {
"symbol"
} else if (is.call(x)) {
"call"
} else if (is.pairlist(x)) {
"pairlist"
} else {
typeof(x)
}
}
expr_type(expr("a"))
expr_type(expr(x))
expr_type(expr(f(1, 2)))
```
Combinaremos esto con un contenedor alrededor de la función de cambio:
```{r}
switch_expr <- function(x, ...) {
switch(expr_type(x),
...,
stop("Don't know how to handle type ", typeof(x), call. = FALSE)
)
}
```
Con estas dos funciones en la mano, podemos escribir una plantilla básica para cualquier función que recorra el AST usando `switch()` (@sec-switch):
```{r}
recurse_call <- function(x) {
switch_expr(x,
# Casos base
symbol = ,
constant = ,
# Casos recursivos
call = ,
pairlist =
)
}
```
Por lo general, resolver el caso base es fácil, así que lo haremos primero y luego verificaremos los resultados. Los casos recursivos son más complicados y, a menudo, requerirán alguna programación funcional.
### Encontrar F y T
Comenzaremos con una función que determina si otra función usa las abreviaturas lógicas `T` y `F` porque usarlas a menudo se considera una mala práctica de codificación. Nuestro objetivo es devolver `TRUE` si la entrada contiene una abreviatura lógica y `FALSE` en caso contrario.
Primero encontremos el tipo de `T` versus `TRUE`:
```{r}
expr_type(expr(TRUE))
expr_type(expr(T))
```
`TRUE` se analiza como un vector lógico de longitud uno, mientras que `T` se analiza como un nombre. Esto nos dice cómo escribir nuestros casos base para la función recursiva: una constante nunca es una abreviatura lógica, y un símbolo es una abreviatura si es "F" o "T":
```{r}
logical_abbr_rec <- function(x) {
switch_expr(x,
constant = FALSE,
symbol = as_string(x) %in% c("F", "T")
)
}
logical_abbr_rec(expr(TRUE))
logical_abbr_rec(expr(T))
```
He escrito la función `logical_abbr_rec()` asumiendo que la entrada será una expresión, ya que esto simplificará la operación recursiva. Sin embargo, cuando se escribe una función recursiva, es común escribir un contenedor que proporciona valores predeterminados o hace que la función sea un poco más fácil de usar. Aquí normalmente crearemos un envoltorio que cita su entrada (aprenderemos más sobre eso en el próximo capítulo), por lo que no necesitamos usar `expr()` cada vez.
```{r}
logical_abbr <- function(x) {
logical_abbr_rec(enexpr(x))
}
logical_abbr(T)
logical_abbr(FALSE)
```
A continuación, debemos implementar los casos recursivos. Aquí queremos hacer lo mismo para las llamadas y para las listas de pares: aplique recursivamente la función a cada subcomponente y devuelva `TRUE` si algún subcomponente contiene una abreviatura lógica. Esto se facilita con `purrr::some()`, que itera sobre una lista y devuelve `TRUE` si la función de predicado es verdadera para cualquier elemento.
```{r}
logical_abbr_rec <- function(x) {
switch_expr(x,
# Casos base
constant = FALSE,
symbol = as_string(x) %in% c("F", "T"),
# Casos recursivos
call = ,
pairlist = purrr::some(x, logical_abbr_rec)
)
}
logical_abbr(mean(x, na.rm = T))
logical_abbr(function(x, na.rm = T) FALSE)
```
### Encontrar todas las variables creadas por asignación
`logical_abbr()` es relativamente simple: solo devuelve un solo `TRUE` o `FALSE`. La siguiente tarea, enumerar todas las variables creadas por asignación, es un poco más complicada. Comenzaremos de manera simple y luego haremos que la función sea progresivamente más rigurosa. \index{find\_assign()}
Comenzamos mirando el AST para la asignación:
```{r}
ast(x <- 10)
```
La asignación es un objeto de llamada donde el primer elemento es el símbolo `<-`, el segundo es el nombre de la variable y el tercero es el valor a asignar.
A continuación, debemos decidir qué estructura de datos vamos a utilizar para los resultados. Aquí creo que será más fácil si devolvemos un vector de caracteres. Si devolvemos símbolos, necesitaremos usar una `list()` y eso hace las cosas un poco más complicadas.
Con eso en la mano, podemos comenzar implementando los casos base y proporcionando un envoltorio útil alrededor de la función recursiva. Aquí los casos base son sencillos porque sabemos que ni un símbolo ni una constante representan una asignación.
```{r}
find_assign_rec <- function(x) {
switch_expr(x,
constant = ,
symbol = character()
)
}
find_assign <- function(x) find_assign_rec(enexpr(x))
find_assign("x")
find_assign(x)
```
A continuación implementamos los casos recursivos. Esto es más fácil gracias a una función que debería existir en purrr, pero actualmente no existe. `flat_map_chr()` espera que `.f` devuelva un vector de caracteres de longitud arbitraria y aplana todos los resultados en un solo vector de caracteres.
<!-- GVW: En este punto, ¿los lectores habrán visto las convenciones `.x` y `.f` lo suficiente como para no necesitar explicación? -->
```{r}
flat_map_chr <- function(.x, .f, ...) {
purrr::flatten_chr(purrr::map(.x, .f, ...))
}
flat_map_chr(letters[1:3], ~ rep(., sample(3, 1)))
```
El caso recursivo para las listas de pares es sencillo: iteramos sobre cada elemento de la lista de pares (es decir, cada argumento de función) y combinamos los resultados. El caso de las llamadas es un poco más complejo: si se trata de una llamada a `<-` entonces deberíamos devolver el segundo elemento de la llamada:
```{r}
find_assign_rec <- function(x) {
switch_expr(x,
# Casos base
constant = ,
symbol = character(),
# Casos recursivos
pairlist = flat_map_chr(as.list(x), find_assign_rec),
call = {
if (is_call(x, "<-")) {
as_string(x[[2]])
} else {
flat_map_chr(as.list(x), find_assign_rec)
}
}
)
}
find_assign(a <- 1)
find_assign({
a <- 1
{
b <- 2
}
})
```
Ahora necesitamos hacer que nuestra función sea más robusta al presentar ejemplos destinados a romperla. ¿Qué sucede cuando asignamos a la misma variable varias veces?
```{r}
find_assign({
a <- 1
a <- 2
})
```
Es más fácil arreglar esto en el nivel de la función contenedora:
```{r}
find_assign <- function(x) unique(find_assign_rec(enexpr(x)))
find_assign({
a <- 1
a <- 2
})
```
¿Qué sucede si tenemos llamadas anidadas a `<-`? Actualmente solo devolvemos el primero. Eso es porque cuando ocurre `<-` terminamos inmediatamente la recursividad.
```{r}
find_assign({
a <- b <- c <- 1
})
```
En su lugar, tenemos que adoptar un enfoque más riguroso. Creo que es mejor mantener la función recursiva enfocada en la estructura de árbol, así que voy a extraer `find_assign_call()` en una función separada.
```{r}
find_assign_call <- function(x) {
if (is_call(x, "<-") && is_symbol(x[[2]])) {
lhs <- as_string(x[[2]])
children <- as.list(x)[-1]
} else {
lhs <- character()
children <- as.list(x)
}
c(lhs, flat_map_chr(children, find_assign_rec))
}
find_assign_rec <- function(x) {
switch_expr(x,
# Casos base
constant = ,
symbol = character(),
# Casos recursivos
pairlist = flat_map_chr(x, find_assign_rec),
call = find_assign_call(x)
)
}
find_assign(a <- b <- c <- 1)
find_assign(system.time(x <- print(y <- 5)))
```
La versión completa de esta función es bastante complicada, es importante recordar que la escribimos trabajando a nuestro modo escribiendo componentes simples.
### Ejercicios
1. `logical_abbr()` devuelve `TRUE` para `T(1, 2, 3)`. ¿Cómo podrías modificar `logical_abbr_rec()` para que ignore las llamadas a funciones que usan `T` o `F`?
2. `logical_abbr()` trabaja con expresiones. Actualmente falla cuando le das una función. ¿Por qué? ¿Cómo podrías modificar `logical_abbr()` para que funcione? ¿Sobre qué componentes de una función necesitará recurrir?
```{r, eval = FALSE}
logical_abbr(function(x = TRUE) {
g(x + T)
})
```
3. Modifique `find_assign` para detectar también la asignación usando funciones de reemplazo, es decir, `names(x) <- y`.
4. Escriba una función que extraiga todas las llamadas a una función específica.
## Estructuras de datos especializadas {#sec-expression-special}
Hay dos estructuras de datos y un símbolo especial que debemos cubrir en aras de la exhaustividad. No suelen ser importantes en la práctica.
### Listas de pares {#sec-pairlists}
\index{pairlists}
Las listas de pares son un remanente del pasado de R y han sido reemplazadas por listas en casi todas partes. El único lugar donde es probable que vea listas de pares en R[^expressions-4] es cuando trabaja con llamadas a la función `función`, ya que los argumentos formales de una función se almacenan en una lista de pares:
[^expressions-4]: Si está trabajando en C, encontrará listas de pares con más frecuencia. Por ejemplo, los objetos de llamada también se implementan mediante listas de pares.
```{r}
f <- expr(function(x, y = 10) x + y)
args <- f[[2]]
args
typeof(args)
```
Afortunadamente, cada vez que encuentre una lista de pares, puede tratarla como una lista normal:
```{r}
pl <- pairlist(x = 1, y = 2)
length(pl)
pl$x
```
Detrás de escena, las listas de pares se implementan utilizando una estructura de datos diferente, una lista vinculada en lugar de una matriz. Eso hace que subdividir una lista de pares sea mucho más lento que subdividir una lista, pero esto tiene poco impacto práctico.
### Argumentos faltantes {#sec-empty-symbol}
\index{symbols|empty} \index{missing arguments}
El símbolo especial que necesita un poco más de discusión es el símbolo vacío, que se usa para representar argumentos faltantes (¡no valores faltantes!). Solo necesita preocuparse por el símbolo faltante si está creando funciones mediante programación con argumentos faltantes; volveremos a eso en la @sec-unquote-missing.
Puedes crear un símbolo vacío con `missing_arg()` (o `expr()`):
```{r}
missing_arg()
typeof(missing_arg())
```
Un símbolo vacío no imprime nada, así que puedes comprobar si tienes uno con `rlang::is_missing()`:
```{r}
is_missing(missing_arg())
```
Los encontrará en la naturaleza en funciones formales:
```{r}
f <- expr(function(x, y = 10) x + y)
args <- f[[2]]
is_missing(args[[1]])
```
Esto es particularmente importante para `...` que siempre está asociado con un símbolo vacío:
```{r}
f <- expr(function(...) list(...))
args <- f[[2]]
is_missing(args[[1]])
```
El símbolo vacío tiene una propiedad peculiar: si lo vincula a una variable, luego accede a esa variable, obtendrá un error:
```{r, error = TRUE}
m <- missing_arg()
m
```
¡Pero no lo hará si lo almacena dentro de otra estructura de datos!
```{r}
ms <- list(missing_arg(), missing_arg())
ms[[1]]
```
Si necesita preservar la falta de una variable, `rlang::maybe_missing()` suele ser útil. Le permite referirse a una variable potencialmente faltante sin desencadenar el error. Consulte la documentación para casos de uso y más detalles.
### Vectores de expresión {#sec-expression-vectors}
\index{expression vectors} \index{expression vectors!expression@\texttt{expression()}}
Finalmente, necesitamos discutir brevemente el vector de expresión. Los vectores de expresión solo son producidos por dos funciones base: `expression()` y `parse()`:
```{r}
exp1 <- parse(text = c("
x <- 4
x
"))
exp2 <- expression(x <- 4, x)
typeof(exp1)
typeof(exp2)
exp1
exp2
```
Al igual que las llamadas y las listas de pares, los vectores de expresión se comportan como listas:
```{r}
length(exp1)
exp1[[1]]
```
Conceptualmente, un vector de expresión es solo una lista de expresiones. La única diferencia es que llamar a `eval()` en una expresión evalúa cada expresión individual. No creo que esta ventaja merezca la introducción de una nueva estructura de datos, por lo que en lugar de vectores de expresión, solo uso listas de expresiones.