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