基于变量名

时间:2015-08-22 21:00:58

标签: r pryr

在R中,常见的函数调用模式如下所示:

child = function(a, b, c) {
  a * b - c
}

parent = function(a, b, c) {
  result = child(a=a, b=b, c=c)
}

这种重复的名称很有帮助,因为如果孩子的参数的顺序发生变化,或者如果要将其他变量添加到列表中,它可以防止潜在的潜在错误:

childReordered = function(c, b, a) {  # same args in different order
  a * b - c
}

parent = function(a, b, c) {
  result = childReordered(a, b, c)  # probably an error
}

但是当参数的名称变长时,这会变得很麻烦:

child = function(longVariableNameA, longVariableNameB, longVariableNameC) {
 longVariableNameA * longVariableNameB - longVariableNameC
}

parent = function(longVariableNameA, longVariableNameB, longVariableNameC) {
  child(longVariableNameA=longVariableNameA, longVariableNameB=longVariableNameB, longVariableNameC=longVariableNameB)
}

我希望能够获得命名参数的安全性,而无需再次实际输入名称。当我只能修改父母而不是孩子时,我希望能够做到这一点。我想象这样的事情:

parent = function(a, b, c) {
  result = child(params(a, b, c))
}

其中params()是一个新函数,它根据变量的名称将未命名的参数转换为命名参数。例如:

child(params(c,b,a)) == child(a=a, b=b, c=c)

' pryr'接近这一点,但我还没有想出如何将它们结合起来做我想做的事情。 named_dots(c,b,a)返回list(c=c, b=b, a=a)standardise_call()也有类似的操作,但我还没有弄清楚如何将结果转换为可以传递给未经修改的内容的内容{ {1}}。

我希望能够使用隐式和显式命名参数的混合:

child()

能够混合一些未命名的常量(而不是变量)并将它们传递给子节点时将它们视为命名参数也是很好的。

child(params(b=3, c=a, a)) == child(b=3, c=a, a=a)

但是以非R方式,我更倾向于提出错误,而不是试图弄清楚程序员可能犯的错误:

child(params(7, c, b=x)) == child(a=7, c=c, b=x)  # desired
named_dots(7, c, b=x) == list("7"=7, c=c, b=x)  # undesired

是否有工具可以执行此操作?将现有功能拼凑在一起以实现我想要的简单方法?更好的方法来实现在不改变参数列表的情况下获得安全性的相同目标而不会笨拙的重复?改进了我建议的语法,使其更安全?

赏金前澄清child(params(c, 7, b=x)) # ERROR: unnamed parameters must be first parent()函数都应视为不可更改。我对使用不同的界面进行包装不感兴趣。相反,我在这里寻找一种以一般方式编写建议的child()函数的方法,可以动态地重写参数列表,以便params()parent()可以直接使用安全但非详细的语法。

奖励后的澄清:反转父子关系并使用do.call()是一种有用的技巧,它不是我在这里寻找的那种。相反,我正在寻找一种接受“......”的方法。参数,修改它以具有命名参数,然后以封闭函数将接受的形式返回它。有可能正如其他人所说,这是不可能的。就个人而言,我目前认为可以使用C级扩展,我希望这个扩展已经存在。或许child()包可以满足我的需求吗? https://github.com/crowding/vadr#dot-dot-dot-lists-and-missing-values

部分功劳:我觉得愚蠢只是让赏金到期。如果没有完整的解决方案,我会将其授予给出至少一个必要步骤的概念证明的任何人。例如,修改' ...'函数内的参数,然后在不使用do.call()的情况下将其传递给另一个函数。或者返回未经修改的' ...'父母可以使用它的方式的参数。或者任何最能指向直接解决方案的东西,甚至是一些有用的链接:http://r.789695.n4.nabble.com/internal-manipulation-of-td4682090.html但我不愿意将其授予一个以(否则完全合理)为前提的答案。#34 ;你不想做那些",或者那是不可能的,所以这里是另一种选择"。

赏金奖励:有几个非常有用且实用的答案,但我选择将赏金奖励给@crowding。虽然他(可能正确地)声称我想要的东西是不可能的,但我认为他的答案最接近理想主义者。接近我的目标。我也认为他的vadr包可能是解决方案的一个很好的起点,是否符合我(可能不切实际的)设计目标。接受的回答是'如果有人想出办法做不可能的事情,仍然需要抓住。感谢其他答案和建议,并希望他们能帮助某些人将这些部分组合在一起,以获得更强大的R语法。

6 个答案:

答案 0 :(得分:5)

我认为尝试覆盖line-height: 193px;的构建参数匹配功能有点危险,所以这是一个使用R的解决方案。

目前尚不清楚do.call有多少是可变的

parent

第二个选项,基于# This ensures that only named arguments to formals get passed through parent = function(a, b, c) { do.call("child", mget(names(formals(child)))) }

的“魔力”
write.csv

答案 1 :(得分:4)

您无法从函数调用内部将参数更改为函数。下一个最好的方法是在调用周围编写一个简单的包装器。也许这样的事情可以帮助

with_params <- function(f, ...) {
    dots <- substitute(...())
    dots <- setNames(dots, sapply(dots, deparse))
    do.call(f, as.list(dots), envir=parent.frame())
}

我们可以测试

之类的东西
parent1 <- function(a, b, c) {
  child(a, b, c)
}

parent2 <- function(a, b, c) {
  with_params(child, a, b, c)
}

child <- function(a, b, c) {
  a * b - c
}

parent1(5,6,7)
# [1] 23
parent2(5,6,7)
# [1] 23

child <- function(c, a, b) {
  a * b - c
}
parent1(5,6,7)
# [1] 37
parent2(5,6,7)
# [1] 23

请注意,在parent2child时,parent的参数顺序可以更改为ballCollides

答案 2 :(得分:4)

要获得您提出的确切语法并不容易。 R被懒惰地评估,因此只在函数调用开始后才查看函数参数中出现的语法。当解释器遇到params()时,它已经开始调用child()并绑定所有child的参数并执行一些child的代码。

表示,在马离开谷仓后,不能重写child的论点。

(由于各种原因,大多数非懒惰的语言都不允许你这样做)

因此语法需要在其参数中引用“child”和“params”。 vadr%()%运算符可以运行。它将右侧给出的点列表应用于左侧给出的函数。所以你会写:

child %()% params(a, b, c)

其中params在点列表中捕获其参数并操纵它们:

params <- function(...) {
  d <- dots(...)
  ex <- expressions(d)
  nm <- names(d) %||% rep("", length(d))
  for (i in 1:length(d)) {
    if (is.name(ex[[i]]) && nm[[i]] == "") {
      nm[[i]] = as.character(ex[[i]])
    }
  }
  names(d) <- nm
  d
}

答案 3 :(得分:1)

这应该是一个评论,但它不符合限制。我赞扬你的编程目标和纯度,但我认为目标是无法实现的。我们假设params存在,并将其应用于函数list。根据{{​​1}},params的定义。由此得出identical(list(a = a, b = b, c = c) , list(params(a, b, c))identical(a, params(a, b, c))的第一和第二个参数的第一个元素。由此得出identical不依赖于其第二个和后来的论证,这是一个矛盾。证明完毕但我认为你的想法是R中DRY的一个可爱的例子,我对params非常满意,do.call(f, params(a,b,c))还有一个额外的do.call,但没有重复。在您允许的情况下,我希望将其合并到我的软件包中,该软件包收集各种想法以改进R语言。我正在使用的一个相关想法是创建一个函数,允许另一个函数从调用帧中获取缺少的args。也就是说,不是调用f(a = a, b = b),而是可以调用f()而在f内调用类似args = eval(formals(f), parent.frame())的内容,但会将其封装到类似宏的构造args = get.args.by.name中或者其他一些。这与您的想法不同,因为它需要有意识地编程f才能拥有此功能。

答案 4 :(得分:1)

这是一个起初似乎有用的答案。诊断它为什么不可能导致启示它为什么不能(暗示:见@crowding's answer的第一段)。

params<-function(...) {
  dots<-list(...)
  names(dots)<-eval(substitute(alist(...)))
  child.env<-sys.frame(-1)  
  child.fun<-sys.function(sys.parent()+1)
  args<-names(formals(child.fun))
  for(arg in args) {
    assign(arg,dots[[arg]],envir=child.env)
  }
  dots[[args[[1]]]]
}

child1<-function(a,b,c) a*b-c
parent1<-function(a,b,c) child1(params(a,b,c))
parent1(1,2,3)
#> -1

child2<-function(a,c,b) a*b-c #swap b and c in formals
parent2<-function(a,b,c) child2(params(a,b,c)) #mirrors parent1
parent2(1,2,3)
#> -1

即使在1*2-3 == -1b=2的正式参数列表中交换了c=3child1的顺序,两者都会生成child2

答案 5 :(得分:1)

这基本上是对Mnel答案的澄清。如果碰巧回答了您的问题,请不要接受或奖励赏金;它应该去Mnel。首先,我们定义call_as_parent,您用它来调用另一个函数中的函数作为外部函数:

call_as_parent <- function(fun) {
  par.call <- sys.call(sys.parent())
  new.call <- match.call(
    eval(par.call[[1L]], parent.frame(2L)), 
    call=par.call
  )
  new.call[[1L]] <- fun
  eval(new.call, parent.frame())
}

然后我们定义父母和孩子:

child <- function(c, b, a) a - b - c
parent <- function(a, b, c) call_as_parent(child)

最后,一些例子

child(5, 10, 20)
# [1] 5
parent(5, 10, 20)
# [1] -25

请注意第二个示例中的20与c匹配的情况应该如此清楚。