-
Notifications
You must be signed in to change notification settings - Fork 1
/
Translation.qmd
709 lines (523 loc) · 27.3 KB
/
Translation.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
# Traducir código R {#sec-translation}
```{r, include = FALSE}
source("common.R")
library(dbplyr) # para suprimir los mensajes de inicio a continuación
```
## Introducción
La combinación de entornos de primera clase, alcance léxico y metaprogramación nos brinda un poderoso conjunto de herramientas para traducir código R a otros lenguajes. Un ejemplo completo de esta idea es dbplyr, que impulsa los backends de la base de datos para dplyr, lo que le permite expresar la manipulación de datos en R y traducirlos automáticamente a SQL. Puedes ver la idea clave en `translate_sql()` que toma el código R y devuelve el SQL equivalente:
```{r}
library(dbplyr)
con <- simulate_dbi()
translate_sql(x ^ 2, con = con)
translate_sql(x < 5 & !is.na(x), con = con)
translate_sql(!first %in% c("John", "Roger", "Robert"), con = con)
translate_sql(select == 7, con = con)
```
Traducir R a SQL es complejo debido a las muchas idiosincrasias de los dialectos de SQL, por lo que aquí desarrollaré dos lenguajes específicos de dominio (DSL) simples pero útiles: uno para generar HTML y el otro para generar ecuaciones matemáticas en LaTeX.
Si está interesado en obtener más información sobre los idiomas específicos del dominio en general, le recomiendo *Idiomas específicos del dominio* [@dsls]. Analiza muchas opciones para crear un DSL y proporciona muchos ejemplos de diferentes idiomas.
### Estructura {.unnumbered}
- La @sec-html crea un DSL para generar HTML, usando quasiquotation y purrr para generar una función para cada etiqueta HTML, luego ordena la evaluación para acceder fácilmente a ellos.
- La @sec-latex) transforma matemáticamente el código R en su equivalente LaTeX usando una combinación de evaluación ordenada y caminata de expresión.
### Requisitos previos {.unnumbered}
Este capítulo reúne muchas técnicas discutidas en otras partes del libro. En particular, deberá comprender los entornos, las expresiones, la evaluación ordenada y un poco de programación funcional y S3. Usaremos [rlang](https://rlang.r-lib.org) para herramientas de metaprogramación y [purrr](https://purrr.tidyverse.org) para programación funcional.
```{r setup, message = FALSE}
library(rlang)
library(purrr)
```
## HTML {#sec-html}
\index{HTML}
HTML (lenguaje de marcado de hipertexto) subyace en la mayor parte de la web. Es un caso especial de SGML (Lenguaje de marcado generalizado estándar), y es similar pero no idéntico a XML (Lenguaje de marcado extensible). HTML se ve así:
``` html
<body>
<h1 id='first'>A heading</h1>
<p>Some text & <b>some bold text.</b></p>
<img src='myimg.png' width='100' height='100' />
</body>
```
Incluso si nunca antes ha mirado HTML, aún puede ver que el componente clave de su estructura de codificación son las etiquetas, que se ven como `<tag></tag>` o `<tag/>`. Las etiquetas se pueden anidar dentro de otras etiquetas y entremezclarse con el texto. Hay más de 100 etiquetas HTML, pero en este capítulo nos centraremos en unas pocas:
- `<body>` es la etiqueta de nivel superior que contiene todo el contenido.
- `<h1>` define un encabezado de nivel superior.
- `<p>` define un párrafo.
- `<b>` texto envalentonado.
- `<img>` incrusta una imagen.
Las etiquetas pueden tener **atributos** con nombre que se parecen a `<tag nombre1='valor1' nombre2='valor2'></tag>`. Dos de los atributos más importantes son `id` y `class`, que se utilizan junto con CSS (hojas de estilo en cascada) para controlar la apariencia visual de la página.
**Etiquetas anuladas**, como `<img>`, no tienen hijos y se escriben `<img />`, no `<img></img>`. Dado que no tienen contenido, los atributos son más importantes, y `img` tiene tres que se usan con casi todas las imágenes: `src` (donde vive la imagen), `width` y `height`.
Debido a que `<` y `>` tienen significados especiales en HTML, no puede escribirlos directamente. En su lugar, debe usar los **escapes** de HTML: `>` y `<`. Y dado que esos escapes usan `&`, si quieres un ampersand literal, tienes que escapar como `&`.
### Objetivo
Nuestro objetivo es facilitar la generación de HTML desde R. Para dar un ejemplo concreto, queremos generar el siguiente HTML:
``` html
<body>
<h1 id='first'>A heading</h1>
<p>Some text & <b>some bold text.</b></p>
<img src='myimg.png' width='100' height='100' />
</body>
```
Usando el siguiente código que coincida lo más posible con la estructura del HTML:
```{r, eval = FALSE}
with_html(
body(
h1("Un encabezado", id = "first"),
p("Un poco de textp &", b("un poco de texto en negrita.")),
img(src = "myimg.png", width = 100, height = 100)
)
)
```
Este DSL tiene las siguientes tres propiedades:
- El anidamiento de llamadas a funciones coincide con el anidamiento de etiquetas.
- Los argumentos sin nombre se convierten en el contenido de la etiqueta y los argumentos con nombre se convierten en sus atributos.
- `&` y otros caracteres especiales se escapan automáticamente.
### Escapar
\index{escaping}
Escapar es tan fundamental para la traducción que será nuestro primer tema. Hay dos desafíos relacionados:
- En la entrada del usuario, necesitamos escapar automáticamente `&`, `<` y `>`.
- Al mismo tiempo, debemos asegurarnos de que `&`, `<` y `>` que generamos no tengan doble escape (es decir, que no generemos accidentalmente `&amp;`, `&lt ;` y `&gt;`).
La forma más sencilla de hacer esto es crear una clase S3 (@sec-s3-classes) que distinga entre texto normal (que necesita escape) y HTML (que no).
```{r escape}
html <- function(x) structure(x, class = "advr_html")
print.advr_html <- function(x, ...) {
out <- paste0("<HTML> ", x)
cat(paste(strwrap(out), collapse = "\n"), "\n", sep = "")
}
```
Luego escribimos un escape genérico. Tiene dos métodos importantes:
- `escape.character()` toma un vector de caracteres regular y devuelve un vector HTML con caracteres especiales (`&`, `<`, `>`) escapados.
- `escape.advr_html()` deja solo el HTML escapado.
```{r}
escape <- function(x) UseMethod("escape")
escape.character <- function(x) {
x <- gsub("&", "&", x)
x <- gsub("<", "<", x)
x <- gsub(">", ">", x)
html(x)
}
escape.advr_html <- function(x) x
```
Ahora comprobamos que funciona
```{r}
escape("This is some text.")
escape("x > 1 & y < 2")
# Doble escape no es un problema
escape(escape("This is some text. 1 > 2"))
# Y el texto que sabemos que es HTML no se escapa.
escape(html("<hr />"))
```
Convenientemente, esto también permite que un usuario opte por nuestro escape si sabe que el contenido ya está escapado.
### Funciones básicas de etiquetas
A continuación, escribiremos una función de una etiqueta a mano, luego descubriremos cómo generalizarla para que podamos generar una función para cada etiqueta con código.
Comencemos con `<p>`. Las etiquetas HTML pueden tener tanto atributos (por ejemplo, id o clase) como elementos secundarios (como `<b>` o `<i>`). Necesitamos alguna forma de separarlos en la llamada a la función. Dado que los atributos tienen nombre y los hijos no, parece natural usar argumentos con nombre y sin nombre para ellos, respectivamente. Por ejemplo, una llamada a `p()` podría verse así:
```{r, eval = FALSE}
p("Texo. ", b(i("some bold italic text")), class = "mypara")
```
Podríamos enumerar todos los atributos posibles de la etiqueta `<p>` en la definición de la función, pero eso es difícil porque hay muchos atributos y porque es posible usar \[atributos personalizados\] (http://html5doctor.com/html5- atributos-de-datos-personalizados/). En su lugar, usaremos `...` y separaremos los componentes en función de si tienen nombre o no. Con esto en mente, creamos una función auxiliar que envuelve `rlang::list2()` (@sec-tidy-dots) y devuelve los componentes con nombre y sin nombre por separado:
```{r named}
dots_partition <- function(...) {
dots <- list2(...)
if (is.null(names(dots))) {
is_named <- rep(FALSE, length(dots))
} else {
is_named <- names(dots) != ""
}
list(
named = dots[is_named],
unnamed = dots[!is_named]
)
}
str(dots_partition(a = 1, 2, b = 3, 4))
```
Ahora podemos crear nuestra función `p()`. Note que hay una nueva función aquí: `html_attributes()`. Toma una lista con nombre y devuelve la especificación del atributo HTML como una cadena. Es un poco complicado (en parte, porque trata con algunas idiosincrasias de HTML que no he mencionado aquí), pero no es tan importante y no introduce nuevas ideas de programación, así que no lo discutiré en detalle. Puede encontrar la [fuente en línea](https://github.com/hadley/adv-r/blob/master/dsl-html-attributes.r) si desea resolverlo usted mismo.
<!-- GVW: ¿Es posible/útil mostrar una versión muy simple de `html_attributes`, luego señalar uno o dos casos en los que falla y luego decirles que lean la fuente? Siempre me pongo nervioso cuando alguien me dice "no tienes que preocuparte por los detalles de esto". -->
```{r p}
source("dsl-html-attributes.r")
p <- function(...) {
dots <- dots_partition(...)
attribs <- html_attributes(dots$named)
children <- map_chr(dots$unnamed, escape)
html(paste0(
"<p", attribs, ">",
paste(children, collapse = ""),
"</p>"
))
}
p("Some text")
p("Some text", id = "myid")
p("Some text", class = "important", `data-value` = 10)
```
### Funciones de etiquetas {#sec-tag-functions}
Es sencillo adaptar `p()` a otras etiquetas: solo necesitamos reemplazar `"p"` con el nombre de la etiqueta. Una forma elegante de hacerlo es crear una función con `rlang::new_function()` (@sec-new-function), utilizando la eliminación de comillas y `paste0()` para generar las etiquetas de inicio y finalización.
```{r}
tag <- function(tag) {
new_function(
exprs(... = ),
expr({
dots <- dots_partition(...)
attribs <- html_attributes(dots$named)
children <- map_chr(dots$unnamed, escape)
html(paste0(
!!paste0("<", tag), attribs, ">",
paste(children, collapse = ""),
!!paste0("</", tag, ">")
))
}),
caller_env()
)
}
tag("b")
```
Necesitamos la extraña sintaxis `exprs(... = )` para generar el argumento vacío `...` en la función de etiqueta. Consulte la @sec-empty-symbol para obtener más detalles.
Ahora podemos ejecutar nuestro ejemplo anterior:
```{r}
p <- tag("p")
b <- tag("b")
i <- tag("i")
p("Some text. ", b(i("some bold italic text")), class = "mypara")
```
Antes de generar funciones para cada etiqueta HTML posible, necesitamos crear una variante que maneje etiquetas vacías. `void_tag()` es bastante similar a `tag()`, pero arroja un error si hay etiquetas secundarias, como lo capturan los puntos sin nombre. La etiqueta en sí también se ve un poco diferente.
```{r}
void_tag <- function(tag) {
new_function(
exprs(... = ),
expr({
dots <- dots_partition(...)
if (length(dots$unnamed) > 0) {
abort(!!paste0("<", tag, "> must not have unnamed arguments"))
}
attribs <- html_attributes(dots$named)
html(paste0(!!paste0("<", tag), attribs, " />"))
}),
caller_env()
)
}
img <- void_tag("img")
img
img(src = "myimage.png", width = 100, height = 100)
```
### Procesando todas las etiquetas {#sec-html-env}
A continuación, debemos generar estas funciones para cada etiqueta. Comenzaremos con una lista de todas las etiquetas HTML:
```{r}
tags <- c("a", "abbr", "address", "article", "aside", "audio",
"b","bdi", "bdo", "blockquote", "body", "button", "canvas",
"caption","cite", "code", "colgroup", "data", "datalist",
"dd", "del","details", "dfn", "div", "dl", "dt", "em",
"eventsource","fieldset", "figcaption", "figure", "footer",
"form", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header",
"hgroup", "html", "i","iframe", "ins", "kbd", "label",
"legend", "li", "mark", "map","menu", "meter", "nav",
"noscript", "object", "ol", "optgroup", "option", "output",
"p", "pre", "progress", "q", "ruby", "rp","rt", "s", "samp",
"script", "section", "select", "small", "span", "strong",
"style", "sub", "summary", "sup", "table", "tbody", "td",
"textarea", "tfoot", "th", "thead", "time", "title", "tr",
"u", "ul", "var", "video"
)
void_tags <- c("area", "base", "br", "col", "command", "embed",
"hr", "img", "input", "keygen", "link", "meta", "param",
"source", "track", "wbr"
)
```
Si observa esta lista detenidamente, verá que hay bastantes etiquetas que tienen el mismo nombre que las funciones base de R (`body`, `col`, `q`, `source`, `sub`, `summary`, `tabla`). Esto significa que no queremos que todas las funciones estén disponibles de forma predeterminada, ya sea en el entorno global o en un paquete. En su lugar, los pondremos en una lista (como en la @sec-functional-factories) y luego proporcionaremos una ayuda para que sea más fácil usarlos cuando se desee. Primero, hacemos una lista con nombre que contiene todas las funciones de etiqueta:
```{r}
html_tags <- c(
tags %>% set_names() %>% map(tag),
void_tags %>% set_names() %>% map(void_tag)
)
```
Esto nos da una forma explícita (pero detallada) de crear HTML:
```{r}
html_tags$p(
"Some text. ",
html_tags$b(html_tags$i("some bold italic text")),
class = "mypara"
)
```
Entonces podemos terminar nuestro DSL HTML con una función que nos permita evaluar el código en el contexto de esa lista. Aquí abusamos ligeramente de la máscara de datos, pasándole una lista de funciones en lugar de un marco de datos. Este es un truco rápido para mezclar el entorno de ejecución del `código` con las funciones en `html_tags`.
\index{eval\_tidy()}
```{r}
with_html <- function(code) {
code <- enquo(code)
eval_tidy(code, html_tags)
}
```
Esto nos brinda una API sucinta que nos permite escribir HTML cuando lo necesitamos, pero no abarrota el espacio de nombres cuando no lo necesitamos.
```{r}
with_html(
body(
h1("A heading", id = "first"),
p("Some text &", b("some bold text.")),
img(src = "myimg.png", width = 100, height = 100)
)
)
```
Si desea acceder a la función R anulada por una etiqueta HTML con el mismo nombre dentro de `with_html()`, puede usar la especificación `package::function` completa.
### Ejercicios
1. Las reglas de escape para las etiquetas `<script>` son diferentes porque contienen JavaScript, no HTML. En lugar de escapar los corchetes angulares o los símbolos de unión, debe escapar `</script>` para que la etiqueta no se cierre demasiado pronto. Por ejemplo, `script("'</script>'")`, no debería generar esto:
``` html
<script>'</script>'</script>
```
But
``` html
<script>'<\/script>'</script>
```
Adapte `escape()` para seguir estas reglas cuando un nuevo argumento `script` se establezca en `TRUE`.
2. El uso de `...` para todas las funciones tiene algunas desventajas importantes. No hay validación de entrada y habrá poca información en la documentación o autocompletar sobre cómo se usan en la función. Cree una nueva función que, cuando se le proporcione una lista con nombre de etiquetas y sus nombres de atributos (como se muestra a continuación), cree funciones de etiqueta con argumentos con nombre.
```{r, eval = FALSE}
list(
a = c("href"),
img = c("src", "width", "height")
)
```
Todas las etiquetas deben obtener los atributos `class` e `id`.
3. Razona sobre el siguiente código que llama `with_html()` haciendo referencia a objetos del entorno. ¿Funcionará o fallará? ¿Por qué? Ejecute el código para verificar sus predicciones.
```{r, eval = FALSE}
greeting <- "Hello!"
with_html(p(greeting))
p <- function() "p"
address <- "123 anywhere street"
with_html(p(address))
```
4. Actualmente, el HTML no se ve muy bonito y es difícil ver la estructura. ¿Cómo podrías adaptar `tag()` para sangrar y formatear? (Es posible que deba investigar un poco sobre las etiquetas en bloque y en línea).
## LaTeX {#sec-latex}
\index{LaTeX}
El próximo DSL convertirá las expresiones R en sus equivalentes matemáticos de LaTeX. (Esto es un poco como `?plotmath`, pero para texto en lugar de gráficos.) LaTeX es la lingua franca de los matemáticos y estadísticos: es común usar la notación LaTeX cada vez que desea expresar una ecuación en texto, como en un correo electrónico. Dado que muchos informes se producen con R y LaTeX, podría ser útil poder convertir automáticamente expresiones matemáticas de un idioma a otro.
Debido a que necesitamos convertir funciones y nombres, este DSL matemático será más complicado que el HTML DSL. También necesitaremos crear una conversión predeterminada, para que los símbolos que no conocemos obtengan una conversión estándar. Esto significa que ya no podemos usar solo la evaluación: también necesitamos recorrer el árbol de sintaxis abstracta (AST).
### LaTeX matemáticas
Antes de comenzar, veamos rápidamente cómo se expresan las fórmulas en LaTeX. El estándar completo es muy complejo, pero afortunadamente está [bien documentado](http://en.wikibooks.org/wiki/LaTeX/Mathematics), y los comandos más comunes tienen una estructura bastante simple:
- La mayoría de las ecuaciones matemáticas simples se escriben de la misma manera que las escribirías en R: `x * y`, `z ^ 5`. Los subíndices se escriben usando `_` (por ejemplo, `x_1`).
- Los caracteres especiales comienzan con `\`: `\pi` = $\pi$, `\pm` = $\pm$, y así sucesivamente. Hay una gran cantidad de símbolos disponibles en LaTeX: la búsqueda en línea de "símbolos matemáticos de látex" arroja muchas [listas](http://www.sunilpatel.co.uk/latex-type/latex-math-symbols/). Incluso hay [un servicio](http://detexify.kirelabs.org/classify.html) que buscará el símbolo que dibujes en el navegador.
- Las funciones más complicadas se ven como `\name{arg1}{arg2}`. Por ejemplo, para escribir una fracción usarías `\frac{a}{b}`. Para escribir una raíz cuadrada, usarías `\sqrt{a}`.
- Para agrupar elementos, use `{}`: es decir, `x ^ a + b` versus `x ^ {a + b}`.
- En una buena composición tipográfica matemática, se hace una distinción entre variables y funciones. Pero sin información adicional, LaTeX no sabe si `f(a * b)` representa llamar a la función `f` con la entrada `a * b`, o si es una abreviatura de `f * (a * b)`. Si `f` es una función, puede decirle a LaTeX que la escriba usando una fuente vertical con `\textrm{f}(a * b)`. (El `rm` significa "romano", lo contrario de la cursiva).
### Meta
Nuestro objetivo es usar estas reglas para convertir automáticamente una expresión R a su representación LaTeX adecuada. Abordaremos esto en cuatro etapas:
- Convertir símbolos conocidos: `pi` → `\pi`
- Dejar otros símbolos sin cambios: `x` → `x`, `y` → `y`
- Convertir funciones conocidas a sus formas especiales: `sqrt(frac(a, b))` → `\sqrt{\frac{a}{b}}`
- Envuelve funciones desconocidas con `\textrm`: `f(a)` → `\textrm{f}(a)`
Codificaremos esta traducción en la dirección opuesta a lo que hicimos con HTML DSL. Comenzaremos con la infraestructura, porque eso hace que sea fácil experimentar con nuestro DSL, y luego trabajaremos de regreso para generar el resultado deseado.
### `to_math`()
Para comenzar, necesitamos una función contenedora que convierta las expresiones R en expresiones matemáticas LaTeX. Esto funcionará como `to_html()` capturando la expresión no evaluada y evaluándola en un entorno especial. Hay dos diferencias principales:
- El entorno de evaluación ya no es constante, ya que tiene que variar según la entrada. Esto es necesario para manejar símbolos y funciones desconocidos.
- Nunca evaluamos en el entorno de argumentos porque estamos traduciendo cada función a una expresión LaTeX. El usuario necesitará usar explícitamente `!!` para evaluar normalmente.
Esto nos da:
```{r}
to_math <- function(x) {
expr <- enexpr(x)
out <- eval_bare(expr, latex_env(expr))
latex(out)
}
latex <- function(x) structure(x, class = "advr_latex")
print.advr_latex <- function(x) {
cat("<LATEX> ", x, "\n", sep = "")
}
```
A continuación, construiremos `latex_env()`, comenzando de manera simple y haciéndonos progresivamente más complejos.
### Símbolos conocidos
Nuestro primer paso es crear un entorno que convierta los símbolos especiales de LaTeX utilizados para los caracteres griegos, por ejemplo, `pi` a `\pi`. Usaremos el truco de la @sec-subset para vincular el símbolo `pi` al valor `"\pi"`.
```{r}
greek <- c(
"alpha", "theta", "tau", "beta", "vartheta", "pi", "upsilon",
"gamma", "varpi", "phi", "delta", "kappa", "rho",
"varphi", "epsilon", "lambda", "varrho", "chi", "varepsilon",
"mu", "sigma", "psi", "zeta", "nu", "varsigma", "omega", "eta",
"xi", "Gamma", "Lambda", "Sigma", "Psi", "Delta", "Xi",
"Upsilon", "Omega", "Theta", "Pi", "Phi"
)
greek_list <- set_names(paste0("\\", greek), greek)
greek_env <- as_environment(greek_list)
```
Entonces podemos comprobarlo:
```{r}
latex_env <- function(expr) {
greek_env
}
to_math(pi)
to_math(beta)
```
¡Se ve bien hasta ahora!
### Símbolos desconocidos
Si un símbolo no es griego, queremos dejarlo como está. Esto es complicado porque no sabemos de antemano qué símbolos se utilizarán y no podemos generarlos todos. En su lugar, usaremos el enfoque descrito en la @sec-ast-funs: recorrer el AST para encontrar todos los símbolos. Esto nos da `all_names_rec()` y el asistente `all_names()`:
```{r, include = FALSE}
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)
}
}
switch_expr <- function(x, ...) {
switch(expr_type(x),
...,
stop("Don't know how to handle type ", typeof(x), call. = FALSE)
)
}
flat_map_chr <- function(.x, .f, ...) {
purrr::flatten_chr(purrr::map(.x, .f, ...))
}
```
<!-- GVW: en la primera lectura, me preguntaba por qué te molestaste en definir `switch_expr`, ya que solo parece usarse una vez. Entonces vi que hay una segunda llamada mucho más abajo. Resaltar esto de alguna manera? -->
```{r}
all_names_rec <- function(x) {
switch_expr(x,
constant = character(),
symbol = as.character(x),
call = flat_map_chr(as.list(x[-1]), all_names)
)
}
all_names <- function(x) {
unique(all_names_rec(x))
}
all_names(expr(x + y + f(a, b, c, 10)))
```
Ahora queremos tomar esa lista de símbolos y convertirla en un entorno para que cada símbolo se asigne a su representación de cadena correspondiente (por ejemplo, `eval(quote(x), env)` produce `"x"`). Nuevamente usamos el patrón de convertir un vector de caracteres con nombre en una lista y luego convertir la lista en un entorno.
```{r}
latex_env <- function(expr) {
names <- all_names(expr)
symbol_env <- as_environment(set_names(names))
symbol_env
}
to_math(x)
to_math(longvariablename)
to_math(pi)
```
Esto funciona, pero necesitamos combinarlo con el entorno de símbolos griegos. Dado que queremos dar preferencia al griego sobre los valores predeterminados (por ejemplo, `to_math(pi)` debe dar `"\\pi"`, no `"pi"`), `symbol_env` debe ser el padre de `greek_env`. Para hacer eso, necesitamos hacer una copia de `greek_env` con un nuevo padre. Esto nos da una función que puede convertir símbolos conocidos (griegos) y desconocidos.
```{r}
latex_env <- function(expr) {
# Símbolos desconocidos
names <- all_names(expr)
symbol_env <- as_environment(set_names(names))
# Símbolos conocidos
env_clone(greek_env, parent = symbol_env)
}
to_math(x)
to_math(longvariablename)
to_math(pi)
```
### Funciones conocidas
A continuación agregaremos funciones a nuestro DSL. Comenzaremos con un par de ayudantes que facilitan la adición de nuevos operadores binarios y unarios. Estas funciones son muy simples: solo ensamblan cadenas.
```{r}
unary_op <- function(left, right) {
new_function(
exprs(e1 = ),
expr(
paste0(!!left, e1, !!right)
),
caller_env()
)
}
binary_op <- function(sep) {
new_function(
exprs(e1 = , e2 = ),
expr(
paste0(e1, !!sep, e2)
),
caller_env()
)
}
unary_op("\\sqrt{", "}")
binary_op("+")
```
Usando estos ayudantes, podemos mapear algunos ejemplos ilustrativos de conversión de R a LaTeX. Tenga en cuenta que con las reglas de alcance léxico de R ayudándonos, podemos proporcionar fácilmente nuevos significados para funciones estándar como `+`, `-` y `*`, e incluso `(` y `{`.
```{r}
# Operadores binarios
f_env <- child_env(
.parent = empty_env(),
`+` = binary_op(" + "),
`-` = binary_op(" - "),
`*` = binary_op(" * "),
`/` = binary_op(" / "),
`^` = binary_op("^"),
`[` = binary_op("_"),
# Agrupamiento
`{` = unary_op("\\left{ ", " \\right}"),
`(` = unary_op("\\left( ", " \\right)"),
paste = paste,
# Otras funciones matemáticas
sqrt = unary_op("\\sqrt{", "}"),
sin = unary_op("\\sin(", ")"),
log = unary_op("\\log(", ")"),
abs = unary_op("\\left| ", "\\right| "),
frac = function(a, b) {
paste0("\\frac{", a, "}{", b, "}")
},
# Etiquetado
hat = unary_op("\\hat{", "}"),
tilde = unary_op("\\tilde{", "}")
)
```
Nuevamente modificamos `latex_env()` para incluir este entorno. Debería ser el último entorno en el que R busca nombres para que expresiones como `sin(sin)` funcionen.
```{r}
latex_env <- function(expr) {
# Funciones conocidas
f_env
# Símbolos predeterminados
names <- all_names(expr)
symbol_env <- as_environment(set_names(names), parent = f_env)
# Símbolos conocidos
greek_env <- env_clone(greek_env, parent = symbol_env)
greek_env
}
to_math(sin(x + pi))
to_math(log(x[i]^2))
to_math(sin(sin))
```
### Funciones desconocidas
Finalmente, agregaremos un valor predeterminado para las funciones que aún no conocemos. No podemos saber de antemano cuáles serán las funciones desconocidas, por lo que nuevamente recorremos el AST para encontrarlas:
```{r}
all_calls_rec <- function(x) {
switch_expr(x,
constant = ,
symbol = character(),
call = {
fname <- as.character(x[[1]])
children <- flat_map_chr(as.list(x[-1]), all_calls)
c(fname, children)
}
)
}
all_calls <- function(x) {
unique(all_calls_rec(x))
}
all_calls(expr(f(g + b, c, d(a))))
```
Necesitamos un cierre que genere las funciones para cada llamada desconocida:
```{r}
unknown_op <- function(op) {
new_function(
exprs(... = ),
expr({
contents <- paste(..., collapse = ", ")
paste0(!!paste0("\\mathrm{", op, "}("), contents, ")")
})
)
}
unknown_op("foo")
```
Y de nuevo la actualizamos `latex_env()`:
```{r}
latex_env <- function(expr) {
calls <- all_calls(expr)
call_list <- map(set_names(calls), unknown_op)
call_env <- as_environment(call_list)
# Funciones conocidas
f_env <- env_clone(f_env, call_env)
# Símbolos predeterminados
names <- all_names(expr)
symbol_env <- as_environment(set_names(names), parent = f_env)
# Símbolos conocidos
greek_env <- env_clone(greek_env, parent = symbol_env)
greek_env
}
```
Esto completa nuestros requisitos originales:
```{r}
to_math(sin(pi) + f(a))
```
Sin duda, podría llevar esta idea más allá y traducir tipos de expresiones matemáticas, pero no debería necesitar ninguna herramienta de metaprogramación adicional.
### Ejercicios
1. Añadir escape. Los símbolos especiales que deben escaparse agregando una barra invertida delante de ellos son `\`, `$` y `%`. Al igual que con HTML, deberá asegurarse de no terminar con doble escape. Por lo tanto, deberá crear una clase S3 pequeña y luego usarla en los operadores de funciones. Eso también le permitirá incrustar LaTeX arbitrario si es necesario.
2. Complete el DSL para admitir todas las funciones que admite `plotmath`.