R中的C风格宏

eh57zj3b  于 2023-04-03  发布在  其他
关注(0)|答案(3)|浏览(197)

在C中,宏就像一个函数,但在编译之前,宏调用的任何示例都被宏的文本替换。
我的用例是这样的:

floor_dec <- function(x, sigDig=1) {
  if (x == 0) {return(x)} 
  if (is.infinite(x)) {return(x)}
  if (sigDig <= 0) {stop("Error: sigDig must be a positive integer.")}
  sigDig = round(sigDig)
  mostSig = ceiling(log10(abs(x)))
  floor(x*10^(sigDig-mostSig))*10^-(sigDig-mostSig)
}
ceil_dec <- function(x, sigDig=1) {
  if (x == 0) {return(x)} # This feels familiar
  if (is.infinite(x)) {return(x)} # So does this...
  if (sigDig <= 0) {stop("Error: sigDig must be a positive integer.")} # Wait a minute
  sigDig = round(sigDig)
  mostSig = ceiling(log10(abs(x)))
  ceiling(x*10^(sigDig-mostSig))*10^-(sigDig-mostSig)
}

这两个函数的参数检查部分是完全相同的,所以我想写一次,然后只引用它。问题是我不能把代码直接放进另一个函数。内部函数需要使用一个sentinel变量来通知外部函数提前返回,另一个变量来存储我要返回的值。
我的第一个方法是gtools::defmacro,但它似乎没有达到我想要的效果,似乎和其他函数一样。接下来我尝试使用这样的替换:

.check_dec_parameters <- substitute({
  if (x == 0) {return(x)}
  if (is.infinite(x)) {return(x)}
  if (sigDig <= 0) {stop("Error: sigDig must be a positive integer.")}
})

ceil_dec <- function(x, sigDig=1) {
  eval(.check_dec_parameters)
  sigDig = round(sigDig)
  mostSig = ceiling(log10(abs(x)))
  ceiling(x*10^(sigDig-mostSig))*10^-(sigDig-mostSig)
}

但是.check_dec_parameters中的返回似乎不是从外部函数返回的。stop()工作正常,所以我猜已经完成了一半。谁能告诉我如何到达最后,可能是解释一下?我正在阅读http://adv-r.had.co.nz/Expressions.html,看看我可能遗漏了什么。

vwoqyblh

vwoqyblh1#

另一种方法是使用function operator来添加参数检查:

# define function operator
add_parameter_check <- function(fn) {
  force(fn)
  function(x, sigDig = 1, ...) {   # `...` to accommodate other arguments to `fn`
    if (x == 0) {return(x)} 
    if (is.infinite(x)) {return(x)}
    if (sigDig <= 0) {stop("Error: sigDig must be a positive integer.")}
    fn(x = x, sigDig = sigDig, ...)
  }
}

# define function without parameter checks
floor_dec <- function(x, sigDig = 1) {
  sigDig = round(sigDig)
  mostSig = ceiling(log10(abs(x)))
  floor(x * 10^(sigDig - mostSig)) * 10^-(sigDig - mostSig)
}
# then add parameter checks
floor_dec <- add_parameter_check(floor_dec)

# likewise for ceil_dec()
ceil_dec <- function(x, sigDig = 1) {
  sigDig = round(sigDig)
  mostSig = ceiling(log10(abs(x)))
  ceiling(x * 10^(sigDig - mostSig)) * 10^-(sigDig - mostSig)
}
ceil_dec <- add_parameter_check(ceil_dec)

结果:

floor_dec(12)
# 10
floor_dec(Inf)
# Inf
floor_dec(12, sigDig = -1)
# Error in floor_dec(12, sigDig = -1) : 
#   Error: sigDig must be a positive integer.

ceil_dec(12)
# 20
ceil_dec(Inf)
# Inf
ceil_dec(12, sigDig = -1)
# Error in floor_dec(12, sigDig = -1) : 
#   Error: sigDig must be a positive integer.
cclgggtu

cclgggtu2#

R允许在语言上进行计算,但它与宏文本替换不同。如果没有函数上下文,您无法从eval“返回”。另一种策略是创建 Package 器。例如,您可以这样做

wrap_dec <- function(expr) {
  function(x, sigDig=1) {
    if (x == 0) {return(x)} # This feels familiar
    if (is.infinite(x)) {return(x)} # So does this...
    if (sigDig <= 0) {stop("Error: sigDig must be a positive integer.")} # Wait a minute
    sigDig = round(sigDig)
    mostSig = ceiling(log10(abs(x)))
    eval(expr)    
  }
}

floor_dec <- wrap_dec(quote(floor(x*10^(sigDig-mostSig))*10^-(sigDig-mostSig)))
ceil_dec <- wrap_dec(quote(ceiling(x*10^(sigDig-mostSig))*10^-(sigDig-mostSig)))

floor_dec(1.4, 1)
# [1] 1
ceil_dec(1.2, -1)
# Error in ceil_dec(1.2, -1) : Error: sigDig must be a positive integer.
ceil_dec(0, -1)
# [1] 0
n6lpvg4x

n6lpvg4x3#

返回函数的函数有一个警告,即被调用和返回的函数将具有不同的求值环境,除非您另外安排对environment<-的适当调用。
为了避免这些类型的陷阱,只需定义一个包含“宏”和substitute的模板函数到主体中:

floor_dec <- ceil_dec <- function(x, sigDig = 1) {
    if (x == 0) {return(x)} 
    if (is.infinite(x)) {return(x)}
    if (sigDig <= 0) {stop("Error: sigDig must be a positive integer.")}
    sigDig <- round(sigDig)
    mostSig <- ceiling(log10(abs(x)))
    .__FUNC__.(x*10^(sigDig-mostSig))*10^-(sigDig-mostSig)
}

body(floor_dec) <- do.call(substitute, list(body(floor_dec), list(.__FUNC__. = quote(floor))))
body( ceil_dec) <- do.call(substitute, list(body( ceil_dec), list(.__FUNC__. = quote( ceil))))

另一个选项是bquote,它在后台做同样的事情:

fnm <- c("floor_dec", "ceil_dec")
sym <- expression(floor, ceiling)
for (i in 1:2)
    assign(fnm[i], 
           removeSource(eval(bquote(function(x, sigDig = 1) {
               if (x == 0) {return(x)} 
               if (is.infinite(x)) {return(x)}
               if (sigDig <= 0) {stop("Error: sigDig must be a positive integer.")}
               sigDig <- round(sigDig)
               mostSig <- ceiling(log10(abs(x)))
               .(sym[[i]])(x*10^(sigDig-mostSig))*10^-(sigDig-mostSig)
           }))))

这两种方法都应该感觉(至少在概念上)更接近于使用C预处理器宏,基于将符号替换为代码片段。

相关问题