html.json (10192B)
1 { 2 "hash": "7befea3ac5a9457dd6cb99fed880c7dd", 3 "result": { 4 "engine": "knitr", 5 "markdown": "---\nengine: knitr\ntitle: Big picture\n---\n\n## Learning objectives:\n\n- Become familiar with some metaprogramming principals and how they relate to each other\n- Review vocabulary associated with metaprogramming\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(rlang)\nlibrary(lobstr)\n```\n:::\n\n\n\n## Code is data\n\n- **expression** - Captured code (*call*, *symbol*, *constant*, or *pairlist*)\n- Use `rlang::expr()`[^1] to capture code directly\n\n\n::: {.cell}\n\n```{.r .cell-code}\nexpr(mean(x, na.rm = TRUE))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> mean(x, na.rm = TRUE)\n```\n\n\n:::\n:::\n\n\n- Use `rlang::enexpr()` to capture code indirectly\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncapture_it <- function(x) { # 'automatically quotes first argument'\n enexpr(x)\n}\ncapture_it(a + b + c)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> a + b + c\n```\n\n\n:::\n:::\n\n\n- 'Captured' code can be modified (like a list)!\n - First element is the function, next elements are the arguments\n\n\n::: {.cell}\n\n```{.r .cell-code}\nf <- expr(f(x = 1, y = 2))\nnames(f)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] \"\" \"x\" \"y\"\n```\n\n\n:::\n\n```{.r .cell-code}\nff <- fff <- f # Create two copies\n\nff$z <- 3 # Add an argument to one\nfff[[2]] <- NULL # Remove an argument from another\n\nf\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> f(x = 1, y = 2)\n```\n\n\n:::\n\n```{.r .cell-code}\nff\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> f(x = 1, y = 2, z = 3)\n```\n\n\n:::\n\n```{.r .cell-code}\nfff\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> f(y = 2)\n```\n\n\n:::\n:::\n\n\n> More on this next week!\n\n[^1]: Equivalent to `base::bquote()`\n\n## Code is a tree\n\n- **Abstract syntax tree** (AST) - Almost every language represents code as a tree\n- Use `lobstr::ast()` to inspect these code trees\n\n\n::: {.cell}\n\n```{.r .cell-code}\nast(f1(f2(a, b), f3(1)))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> █─f1 \n#> ├─█─f2 \n#> │ ├─a \n#> │ └─b \n#> └─█─f3 \n#> └─1\n```\n\n\n:::\n\n```{.r .cell-code}\nast(1 + 2 * 3)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> █─`+` \n#> ├─1 \n#> └─█─`*` \n#> ├─2 \n#> └─3\n```\n\n\n:::\n:::\n\n\n\n## Code can generate code\n\n- `rlang::call2()` creates function call\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncall2(\"f\", 1, 2, 3)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> f(1, 2, 3)\n```\n\n\n:::\n:::\n\n\n- Going backwards from the tree, can use functions to create calls\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncall2(\"f1\", call2(\"f2\", \"a\", \"b\"), call2(\"f3\", 1))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> f1(f2(\"a\", \"b\"), f3(1))\n```\n\n\n:::\n\n```{.r .cell-code}\ncall2(\"+\", 1, call2(\"*\", 2, 3))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> 1 + 2 * 3\n```\n\n\n:::\n:::\n\n\n- `!!` bang-bang - **unquote operator**\n - inserts previously defined expressions into the current one\n\n\n::: {.cell}\n\n```{.r .cell-code}\nxx <- expr(x + x)\nyy <- expr(y + y)\nexpr(xx / yy) # Nope!\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> xx/yy\n```\n\n\n:::\n\n```{.r .cell-code}\nexpr(!!xx / !!yy) # Yup!\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> (x + x)/(y + y)\n```\n\n\n:::\n:::\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncv <- function(var) {\n var <- enexpr(var) # Get user's expression\n expr(sd(!!var) / mean(!!var)) # Insert user's expression\n}\n\ncv(x)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> sd(x)/mean(x)\n```\n\n\n:::\n\n```{.r .cell-code}\ncv(x + y)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> sd(x + y)/mean(x + y)\n```\n\n\n:::\n:::\n\n\n- Avoid `paste()` for building code\n - Problems with non-syntactic names and precedence among expressions\n\n> \"You might think this is an esoteric concern, but not worrying about it when generating SQL code in web applications led to SQL injection attacks that have collectively cost billions of dollars.\"\n\n## Evaluation runs code\n\n- **evaluate** - run/execute an expression\n- need both expression and environment\n- `eval()` uses current environment if not set\n- manual evaluation means you can tweak the environment!\n\n\n::: {.cell}\n\n```{.r .cell-code}\nxy <- expr(x + y)\n\neval(xy, env(x = 1, y = 10))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] 11\n```\n\n\n:::\n\n```{.r .cell-code}\neval(xy, env(x = 2, y = 100))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] 102\n```\n\n\n:::\n:::\n\n\n\n## Customizing evaluations with functions\n- Can also bind names to functions in supplied environment\n- Allows overriding function behaviour\n- This is how dplyr generates SQL for working with databases\n\nFor example...\n\n::: {.cell}\n\n```{.r .cell-code}\nstring_math <- function(x) {\n e <- env(\n caller_env(),\n `+` = function(x, y) paste(x, y),\n `*` = function(x, y) strrep(x, y)\n )\n\n eval(enexpr(x), e)\n}\n\ncohort <- 9\nstring_math(\"Hello\" + \"cohort\" + cohort)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] \"Hello cohort 9\"\n```\n\n\n:::\n\n```{.r .cell-code}\nstring_math((\"dslc\" + \"is\" + \"awesome---\") * cohort)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] \"dslc is awesome---dslc is awesome---dslc is awesome---dslc is awesome---dslc is awesome---dslc is awesome---dslc is awesome---dslc is awesome---dslc is awesome---\"\n```\n\n\n:::\n:::\n\n\n\n## Customizing evaluation with data\n\n- Look for variables inside data frame\n- **Data mask** - typically a data frame\n- use `rlang::eval_tidy()` rather than `eval()`\n\n\n::: {.cell}\n\n```{.r .cell-code}\ndf <- data.frame(x = 1:5, y = sample(5))\neval_tidy(expr(x + y), df)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] 2 4 6 9 9\n```\n\n\n:::\n:::\n\n\nCatch user input with `enexpr()`...\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwith2 <- function(df, expr) {\n eval_tidy(enexpr(expr), df)\n}\n\nwith2(df, x + y)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] 2 4 6 9 9\n```\n\n\n:::\n:::\n\n\nBut there's a bug!\n\n- Evaluates in environment inside `with2()`, but the expression likely refers\n to objects in the Global environment\n \n\n::: {.cell}\n\n```{.r .cell-code}\nwith2 <- function(df, expr) {\n a <- 1000\n eval_tidy(enexpr(expr), df)\n}\n\ndf <- data.frame(x = 1:3)\na <- 10\nwith2(df, x + a)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] 1001 1002 1003\n```\n\n\n:::\n:::\n\n\n- Solved with Quosures...\n \n## Quosures\n\n- **Quosures** bundles expression with an environment\n- Use `enquo()` instead of `enexpr()` (with `eval_tidy()`)\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwith2 <- function(df, expr) {\n a <- 1000\n eval_tidy(enquo(expr), df)\n}\n\ndf <- data.frame(x = 1:3)\na <- 10\nwith2(df, x + a)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] 11 12 13\n```\n\n\n:::\n:::\n\n\n> \"Whenever you use a data mask, you must always use `enquo()` instead of `enexpr()`.\n\nThis comes back in Chapter 20.\n\n### Which environment is bundled?\n- The environment where the expression is created (i.e. the parent of where\n `enquo()` is called)\n\nHere, the global environment\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwith2 <- function(df, expr) {\n a <- 1000\n eq <- enquo(expr)\n message(\"with2() Parent/Calling environment: \")\n print(rlang::caller_env())\n message(\"with2() environment: \")\n print(rlang::current_env())\n message(\"Quosure details: \")\n print(eq) # Print the details of the quosure\n eval_tidy(eq, df)\n}\n\na <- 10000\ndf <- data.frame(x = 1:3)\nwith2(df, x + a)\n```\n\n::: {.cell-output .cell-output-stderr}\n\n```\n#> with2() Parent/Calling environment:\n```\n\n\n:::\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> <environment: R_GlobalEnv>\n```\n\n\n:::\n\n::: {.cell-output .cell-output-stderr}\n\n```\n#> with2() environment:\n```\n\n\n:::\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> <environment: 0x0000018dede5ddd0>\n```\n\n\n:::\n\n::: {.cell-output .cell-output-stderr}\n\n```\n#> Quosure details:\n```\n\n\n:::\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> <quosure>\n#> expr: ^x + a\n#> env: global\n```\n\n\n:::\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] 10001 10002 10003\n```\n\n\n:::\n:::\n\n\n\nHere, the `fun1()` environment\n\n::: {.cell}\n\n```{.r .cell-code}\nfun1 <- function(df) {\n a <- 10\n message(\"fun1() Parent/Calling environment: \")\n print(rlang::caller_env())\n message(\"fun1() environment: \")\n print(rlang::current_env())\n with2(df, x + a)\n}\n\na <- 10000\ndf <- data.frame(x = 1:3)\nfun1(df)\n```\n\n::: {.cell-output .cell-output-stderr}\n\n```\n#> fun1() Parent/Calling environment:\n```\n\n\n:::\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> <environment: R_GlobalEnv>\n```\n\n\n:::\n\n::: {.cell-output .cell-output-stderr}\n\n```\n#> fun1() environment:\n```\n\n\n:::\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> <environment: 0x0000018df3e748f8>\n```\n\n\n:::\n\n::: {.cell-output .cell-output-stderr}\n\n```\n#> with2() Parent/Calling environment:\n```\n\n\n:::\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> <environment: 0x0000018df3e748f8>\n```\n\n\n:::\n\n::: {.cell-output .cell-output-stderr}\n\n```\n#> with2() environment:\n```\n\n\n:::\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> <environment: 0x0000018df3ebc698>\n```\n\n\n:::\n\n::: {.cell-output .cell-output-stderr}\n\n```\n#> Quosure details:\n```\n\n\n:::\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> <quosure>\n#> expr: ^x + a\n#> env: 0x0000018df3e748f8\n```\n\n\n:::\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] 11 12 13\n```\n\n\n:::\n:::\n\n", 6 "supporting": [ 7 "17_files" 8 ], 9 "filters": [ 10 "rmarkdown/pagebreak.lua" 11 ], 12 "includes": {}, 13 "engineDependencies": {}, 14 "preserve": {}, 15 "postProcess": true 16 } 17 }