这是一个非常简单和一般性的问题,但我还没有看到它已经讨论过了。我希望我没有错过任何东西。
我开始设计具有多层功能的大型程序,虽然其他编程语言中有明确的策略,但我无法在R中找到关于如何处理函数的“参数”的规范解决方案。有争论”。我在“参数”和“参数”之间做了一个概念上的区别,即使它们实际上与函数相同:输入。前者将设置在更高级别,而不是经常更改,而后者是函数将处理的真实数据。
让我们考虑这个简单的例子:
感兴趣的子函数SF()被“WORKER”用不同的参数多次查询,但具有相同的参数,设置在“上方”。当然,同样的问题适用于具有多个层的更复杂的案例。
我看到两种处理方式: 1.传递一切,但是: 一个。你最终会在函数调用中包含无数的参数,或者包含所有这些参数的结构。 湾因为R为调用函数创建了参数的副本,所以它可能效率不高。 2.每次更改参数时动态评估函数,并将它们“硬连线”到函数定义中。但我不知道该怎么做,尤其是以干净的方式。
这一切看起来都不太可爱,所以我想知道你们是否对此事有意见?也许我们可以使用R的一些环境特征? :-)
谢谢!
编辑:因为对于某些人而言,代码比图表更好,这里是一个虚拟示例,其中我使用了方法“1.”,将所有参数传递过来。如果我有很多层和子功能,将所有参数传递给中间层(这里,WORKER())似乎不是很好。 (从代码和性能角度来看)
F <- function(){
param <- getParam()
result <- WORKER(param)
return(result)
}
getParam <- function(){
return('O')
}
WORKER <- function(param) {
X <- LETTERS[1:20]
interm.result <- sapply(X,SF,param) # The use of sapply here negates maybe the performance issue?
return(which(interm.result=='SO'))
}
SF <- function(x,param) {
paste0(x,param)
}
编辑2 :以上示例的简单性误导了一些看待我的问题的人,所以这里有一个更具体的例子,使用离散的梯度下降。同样,我保持简单,所以一切都可以用同样的大功能编写,但这不是我想要为我的真正问题做的事情。
gradientDescent <- function(initialPoint= 0.5, type = 'sin', iter_max = 100){
point <- initialPoint
iter <- 1
E <- 3
deltaError <- 1
eprev <- 0
while (abs(deltaError) > 10^(-2) | iter < iter_max) {
v_points <- point + -100:100 / 1000
E <- sapply(v_points, computeError, type)
point <- v_points[which.min(E)]
ef <- min(E)
deltaError <- ef - eprev
eprev <- ef
iter <- iter+1
}
print(point)
return(point)
}
computeError <- function(point, type) {
if (type == 'sin') {
e <- sin(point)
} else if (type == 'cos') {
e <- cos(point)
}
}
我发现每次评估子函数的“type”参数都是非理想的。似乎@hadley对闭包和@Greg的解释所带来的参考是我所需解决方案的良好轨迹。
答案 0 :(得分:3)
我认为你可能在寻找词汇范围。 R使用词法作用域,这意味着如果在F中定义函数WORKER和SF,那么它们将能够访问param
的当前值而不传递它。
如果你不能利用词法作用域(SF必须在F之外定义),那么另一种选择是创建一个新的环境来存储你的参数,然后如果所有需要的函数都可以访问这个环境(通过显式传递,或通过继承传递(使此环境成为函数的封闭环境))然后F可以将param
分配到此环境中,其他函数可以访问该值。
答案 1 :(得分:2)
冒着为别人说话的风险,我认为你的问题既有兴趣也有缺乏答案的原因是你似乎让这个过于复杂。
当然,鉴于你的例子中显示的任务,我会做更多这样的事情:
SF <- function(x, par) {
paste0(x, par)
}
F <- function(param) {
which(sapply(LETTERS[1:20], FUN = SF, par = param) == "SO")
}
F(param="O")
# S
# 19
或者,使用Greg Snow提到的词汇范围:
F <- function(param) {
SF <- function(x) {
paste0(x, param)
}
which(sapply(LETTERS[1:20], FUN = SF) == "SO")
}
F(param="O")
或者,实际上并利用了paste0()
被矢量化的事实:
F <- function(param) {
which(paste0(LETTERS[1:20], param) == "SO")
}
F("O")
# [1] 19
我理解我的回答可能显得过于简单:你明显有一些更复杂的想法,但我认为你需要更好地告诉我们这是什么。为了获得更多帮助,我建议你按照@ baptiste的第二条评论中的建议,给我们一个不那么抽象的例子,并解释为什么你在没有任何论据的情况下调用F()
和getParam()
(也可能证明为什么你需要一个完全getParam()
功能。)
答案 2 :(得分:0)
尽管这个问题很严重,但我认为触摸其他几种方式可能会有用,我已经看到这种问题已经解决并在答案位置提供答案。请注意,看过并报告过模式与赞同它不一样!
正如评论和修正答案中所提到的,closures肯定是一个很好的答案。也就是说,您可以在函数中定义函数,即生成函数,并将生成器函数中的信息传递到生成的函数中。
generator_function <- function(param) {
function() {
param
}
}
generated_function <- generator_function(0)
generated_function()
在问题的上下文中,这可能会建议在computeError
内定义gradientDecent
,然后computeError
可以在其环境中携带type
。
一旦你闭嘴,我想你会发现它们非常强大。但是,一开始考虑它们有点挑战性。此外,如果您不习惯它们,并且生成的函数最终与生成器函数的输入分离,那么调试它们可能会有点挑战,因为type
的值是什么以及哪里出现混淆它来自。为了解决第一个问题,我衷心建议pryr::unenclose
。对于第二种情况,如果需要,我会让更聪明的头脑而不是我的钟声。
通常直接或通过getter / setter函数(例如?options
)将原始参数设置为选项(c.f。knitr
)。但是,我也看到函数现在又被设置为选项。就个人而言,我不喜欢这种模式,因为它在包中做得相当不一致,并且选项通常隐藏在特定函数的文档中,但是当你需要制作更高级别函数的选项时,你的实际调用可能是更高级别的函数您想要的内容可能会被隐藏在文档中以获得更低阶的功能。
有些作者通过自由使用点来避免参数意大利面。它一直是点。这种方法非常强大。它只能在10次中工作9次。缺点是,至少对我来说,点调试和记录可能具有挑战性。例如,在调试方面,因为没有任何功能输入是严格的,所以错误输入的参数名称似乎很难被捕获。
当然还有其他模式!他们的吨。人们通过环境和建立清单等等。对你来说“正确”的答案可能是你的个人风格,有效的,以及当你回头看几个月后你会清楚的东西。