10

这是一个非常简单和普遍的问题,但我还没有看到它已经讨论过。我希望我没有错过任何东西。

我开始设计具有多层函数的大型程序,虽然在其他编程语言中有明确的策略,但我在 R 中找不到关于如何处理也将具有“参数”的函数的“参数”的规范解决方案”。我在“参数”和“参数”之间做出了概念上的区别,即使它们实际上与函数相同:输入。前者将设置在更高的级别,并且不会经常更改,而后者是函数将处理的真实数据。

让我们考虑这个简单的例子: 简单模式

“WORKER”使用不同的参数多次查询感兴趣的子函数 SF(),但使用相同的参数,设置为“above”。当然,同样的问题也适用于具有多层的更复杂的情况。

我看到了两种处理方法: 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)    
  }
}

我发现每次评估子函数时都传递子函数的“类型”参数不是最佳选择。似乎@hadley 对闭包的引用和@Greg 的解释是我需要的解决方案的好方法。

4

3 回答 3

3

我认为您可能正在寻找词法范围。R 使用词法作用域,这意味着如果您在 F 内部定义函数 WORKER 和 SF,那么它们将能够访问当前值param而不被传递。

如果您不能利用词法作用域(SF 必须在 F 之外定义),那么另一种选择是创建一个新环境来存储您的参数,然后如果所有需要的函数都可以访问这个环境(或者通过显式传递,或者通过继承(使这个环境成为函数的封闭环境))然后 F 可以分配param到这个环境中,其他函数可以访问该值。

于 2013-08-31T17:53:51.250 回答
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()函数)。

于 2013-09-01T01:13:11.163 回答
0

尽管这个问题是灰色的,但我认为触及我已经看到解决此类问题的其他几种方法并在答案槽中提供答案可能会很有用。请注意,看到并报告了一个模式与认可它是不一样的!

闭包

正如评论和修改后的答案中提到的,闭包肯定是一个很好的答案。也就是说,你可以在函数中定义一个函数,即生成器函数,并将生成器函数的信息携带到生成的函数中。

generator_function <- function(param) {
  function() {
    param   
  }
}

generated_function <- generator_function(0)
generated_function()

在问题的上下文中,这可能建议定义computeErrorinside of gradientDecent,然后computeError可以type在其环境中进行。

一旦你了解了闭包,我想你会发现它们非常强大。但是,起初考虑它们有点挑战性。此外,如果您不习惯它们,并且生成的函数最终与生成器函数的输入解耦,那么调试它们可能会有点挑战性,因为可能会混淆其值type是什么以及它来自哪里。为了解决第一个问题,我衷心推荐pryr::unenclose. 其次,如果需要,我会让比我更聪明的人加入进来。

设置一个选项

原始参数经常?options直接或通过 getter/setter 函数(例如)设置为选项(cf knitr)。但是,我也经常看到函数设置为选项。就个人而言,我不喜欢这种模式,因为它在包之间的完成相当不一致,并且选项通常隐藏在特定函数的文档中,但是当您需要使更高级别函数的选项执行时,您的实际调用可能是对更高级别的函数您想要的内容可能会隐藏在文档中以获得低阶功能。

...

一些作者通过自由使用点来避免参数意大利面条。一路下来都是点。这种方法非常强大。它只能工作 10 次中的 9 次。缺点是,至少对我而言,点可能难以调试和记录。例如,在调试方面,因为您的函数输入都不是严格的,所以输入错误的参数名称似乎很难被发现。

其他

当然还有其他模式!成吨的。人们传递环境并构建列表等。什么答案对你来说是“正确的”可能是你个人风格的混合体,什么是有效的,以及当你几个月后回去看它时你会清楚什么.

于 2016-07-02T12:47:54.737 回答