R语言里的“宏”
最近在看R,不过还是习惯性地对语言特性感兴趣。R也可以操作代码本身,有一个类似lisp 宏的系统。不过他更像IO language,利用延迟计算+代码操作,可以完成类似lisp宏的功能,定义新的“语法结构”。
一个例子:
我们实现一种机制可以部分地给一个函数的参数来得到一个新的函数(柯瑞化)。
比如我们有一个函数如下:
f <- function(a, b, c) a
如果我们只给第一个参数值,会得到一个新的函数如下:
bind(f(1)) # => function(b, c) f(1, b, c)
这里为了实现简单点,我们使用这样的占位符: ., ._1, ._2 etc.
bind(f(1, ._1, ._2)) # => function(._1, ._2) f(1, ._1, ._2)
实现:
先给出一个实现如下,然后我们再解释
bind <- function(f) {
parent <- parent.frame() env <- new.env(parent=parent) f <- match.call()[[2]] fn <- f[[1]] fargs <- list() rargs <- sapply(as.list(f[-1]), function(arg) { if (length(grep("^\\.(_\\d+)?$", as.character(arg))) > 0) { fargs[[as.character(arg)]] <<- NA arg } else { eval(arg, env, env) } }) fc <- as.call(c(fn, rargs)) eval(call("function", as.pairlist(fargs), fc)) }
parent <- parent.frame(); env<- new.env(parent=parent)得到调用bind时的环境,我们在后面会使用他,因为非占位符需要在那个环境下计算值。
R对大多数情况下是惰性的。这里如果我们不用bind的参数,他们不会被计算的。同时match.call()可以得到整个函数调用的语法树。如果我们修改函数调用的语法树来完成其他事情,其实也就是“宏”的功能了。一个不同于lisp的地方是,这里我们不需要区分估值阶段(evaluate phase),惰性估值给了我们机会去修改语法树。
fn <- f[[1]]得到被bind的函数名字。
rargs <- sapply(…)检查如果是占位符就把他加入到新的函数的形式参数里,如果不是就计算值。
最后两行生成一个新的函数。
测试:
f <- function(a, b, c) { b } a <- 1 x <- bind(f(b=., c=._2, a=a)) x(.=1)
一个更好的例子是https://github.com/smbache/magrittr, 他实现了一个%>%,其实完成了上面的bind机制。