library(rlang)
library(lobstr)参考
下準備
expression
expr:コードをそのまま捕まえる- 作成されたオブジェクト:expression
- call, symbol, constant, pairlistの4つからなる
expr(mean(x, na.rm = TRUE))mean(x, na.rm = TRUE)
expr(10 + 100 + 1000)10 + 100 + 1000
exprは関数の引数なども書いてあるまま保持する
# これはxになる
capture_it <- function(x) {
expr(x)
}
capture_it(a + b + c)x
- 引数は評価した上でコードを保持したい場合は
enexprを使う
capture_it <- function(x) {
enexpr(x)
}
capture_it(a + b + c)a + b + c
abstract syntax tree (AST) による表現
- codeはtree形式で表現できる
- fがcall, aがsymbol, “b”がconstantらしい
lobstr::ast(f(a, "b"))█─f
├─a
└─"b"
+や*も関数であることがわかる
lobstr::ast(1 + 2 * 3)█─`+`
├─1
└─█─`*`
├─2
└─3
codeを生成するcode
call2
call2('f', 1, 2)f(1, 2)
call2('+', 1, call2('*', 2, 3))1 + 2 * 3
!!を使ったcodeの生成- 捕まえておいたコードを評価したいところで評価する
xx <- expr(x + x)
yy <- expr(y + y)
expr(!!xx / !!yy)(x + x)/(y + y)
codeの評価
eval(expr, env):exprをenvで評価
eval(expr(x + y), env(x = 1, y = 10))[1] 11
eval(expr(x + y), env(x = 2, y = 100))[1] 102
- envを指定しないときは現在のenvironmentで評価
x <- 10
y <- 100
eval(expr(x + y))[1] 110
データを環境として扱う
eval_tidy(expr, data):exprをdata内部で評価- dataでcurrent environmentを上書きするのでdata-mask呼ばれる
evalでもできないことはないが、落とし穴があるらしい
df <- data.frame(x = 1:5, y = sample(5))
eval_tidy(expr(x + y), df)[1] 2 5 8 8 7
- dplyrっぽい関数
with2 <- function(df, expr) {
eval_tidy(enexpr(expr), df)
}
with2(df, x + y)[1] 2 5 8 8 7
Quosure
- exprだけを使う方法は問題がでてくる
- 関数の内部で
aを定義してみる
with2 <- function(df, expr) {
a <- 1000
eval_tidy(enexpr(expr), df)
}- さらに関数の外部(global environment)でも
aを定義する
df <- data.frame(x = 1:3)
a <- 10- この状態で
with2を使うと問題が発生- global environmentの
aではなく、関数内部で定義したaが優先的に使われる
- global environmentの
with2(df, x + a)[1] 1001 1002 1003
- quosure:expressionとenvironmentをセットで保持する
- data-maskでは、eval_tidyのdata -> quosureのenv -> global envの順で評価される
with2 <- function(df, expr) {
a <- 1000
eval_tidy(enquo(expr), df)
}
x <- 10
with2(df, x + a)[1] 11 12 13
Quasiquotation
library(rlang)
library(purrr)- pasteっぽい関数を作る
paste('Good', 'morning', 'Hadley')[1] "Good morning Hadley"
- 毎回’’でくくるのがだるい
cement <- function(...) {
args <- ensyms(...) |> map_chr(as_string)
paste(args, collapse = " ")
}
cement(Good, morning, Hadley)[1] "Good morning Hadley"
- morningやeveryoneが変数だったら?
- pasteだとうまくいくけど、cementは想定したふうにはならない
time <- 'afternoon'
name <- 'Alice'
paste('Good', time, name)[1] "Good afternoon Alice"
cement(Good, time, name)[1] "Good time name"
– !!を使って変数を評価すればうまくいく
cement(Good, !!time, !!name)[1] "Good afternoon Alice"