概括`...`(三个点)参数调度:参数集的S4方法,包括`...`

Posted

技术标签:

【中文标题】概括`...`(三个点)参数调度:参数集的S4方法,包括`...`【英文标题】:Generalizing `...` (three dots) argument dispatch: S4 methods for argument set including `...` 【发布时间】:2015-01-13 20:18:18 【问题描述】:

实际问题

是否可以为一组包含 ... 的签名参数定义方法(相对于...专有)? “开箱即用”是不可能的,但理论上是可能(涉及一些调整)还是由于 S4 机制的方式而根本无法完成的事情设计的?

我正在寻找类似的东西

setGeneric(
  name = "foo",
  signature = c("x", "..."),
  def = function(x, ...) standardGeneric("foo")      
)
setMethod(
  f = "foo", 
  signature = signature(x = "character", "..." = "ThreedotsRelevantForMe"), 
 definition = function(x, ...) bar(x = x)
)

Martin Morgan 谢天谢地给我指了dotsMethods,上面写着:

目前,“...”不能与其他形式参数混合:泛型函数的签名只是“...”,或者它不包含“...”。 (这个限制可能会在未来的版本中取消。)

背景

考虑以下尝试从一个简单的案例中概括基于... 的调度机制(只有一个 更多函数应该使用通过... 传递的参数;例如使用@987654328 @ in plot() 用于将参数传递给par())到涉及以下方面的场景(取自here):

当您想将参数传递给多个,因此r,收件人, 当这些接收者可以位于c调用堆栈的不同层 他们甚至可能使用相同的参数名称,但将不同的含义与他们自己的作用域/闭包/框架/环境中的这些参数相关联

还请注意,虽然这样做确实是一种好习惯,但***函数/接口应该不一定需要关注定义(很多) 随后调用函数/接口的显式参数,以便正确传递参数。 IMO,这个选择应该留给开发人员,因为有时一个或另一个选择更有意义。

如果我能以某种方式将当前通过withThreedots() 处理的调度(AFAICT 需要涉及... 的实际拆分)替换为 S4 调度程序,那就太酷了,因此理想情况下只需在foobar() 中调用foo(x = x, ...) 而不是withThreedots("foo", x = x, ...)

定义

withThreedots <- function(fun, ...) 
  threedots <- list(...)
  idx <- which(names(threedots) %in% sprintf("args_%s", fun))
  eval(substitute(
    do.call(FUN, c(THREE_THIS, THREE_REST)),
    list(
      FUN = as.name(fun),
      THREE_THIS = if (length(idx)) threedots[[idx]], 
      THREE_REST = if (length(idx)) threedots[-idx] else threedots
    )
  ))

foobar <- function(x, ...) 
  withThreedots("foo", x = x, ...)

foo <- function(x = x, y = "some text", ...) 
  message("foo/y")
  print(y)
  withThreedots("bar", x = x, ...)

bar <- function(x = x, y = 1, ...) 
  message("bar/y")
  print(y)
  withThreedots("downTheLine", x = x, ...)

downTheLine <- function(x = x, y = list(), ...) 
  message("downTheLine/y")
  print(y)

申请

foobar(x = 10) 
foobar(x = 10, args_foo = list(y = "hello world!")) 
foobar(x = 10, args_bar = list(y = 10)) 
foobar(x = 10, args_downTheLine = list(y = list(a = TRUE))) 

foobar(x = 10, 
       args_foo = list(y = "hello world!"), 
       args_bar = list(y = 10),
       args_downTheLine = list(y = list(a = TRUE))
)

# foo/y
# [1] "hello world!"
# bar/y
# [1] 10
# downTheLine/y
# $a
# [1] TRUE

概念方法(主要是伪代码)

我想我正在寻找类似这样的东西:

定义

setGeneric(
  name = "foobar",
  signature = c("x"),
  def = function(x, ...) standardGeneric("foobar")
)
setMethod(
  f = "foobar", 
  signature = signature(x = "ANY"), 
  definition = function(x, ...) pkg.foo::foo(x = x, ...)
)

假设:foo() 定义在包/命名空间 pkg.foo

setGeneric(
  name = "foo",
  signature = c("x", "y", "..."),
  def = function(x, y = "some text", ...) standardGeneric("foo")      
)
setMethod(
  f = "foo", 
  signature = signature(x = "ANY", y = "character", "..." = "Threedots.pkg.foo.foo"), 
  definition = function(x, y, ...) 
    message("foo/y")
    print(y)
    pkg.bar::bar(x = x, ...)
  
)

假设:bar() 定义在包/命名空间pkg.bar

setGeneric(
  name = "bar",
  signature = c("x", "y", "..."),
  def = function(x, y = 1, ...) standardGeneric("bar")      
)
setMethod(
  f = "bar", 
  signature = signature(x = "ANY", y = "numeric", "..." = "Threedots.pkg.bar.bar"), 
  definition = function(x, y, ...) 
    message("bar/y")
    print(y)
    pkg.a::downTheLine(x = x, ...)
)
setGeneric(
  name = "downTheLine",
  signature = c("x", "y", "..."),
  def = function(x, y = list(), ...) standardGeneric("downTheLine")      
)

假设:downTheLine() 定义在包/命名空间pkg.a

setMethod(
  f = "downTheLine", 
  signature = signature(x = "ANY", y = "list", "..." = "Threedots.pkg.a.downTheLine"), 
  definition = function(x, y, ...) 
    message("downTheLine/y")
    print(y)
    return(TRUE)
)

说明调度员需要做什么

关键部分是它必须能够区分... 中与被调用的各个当前 fun 相关的那些元素(基于完整的 S4 调度常规 三点签名参数)以及应该传递给fun可能调用函数的元素>(即... 的更新状态;类似于上面withThreedots() 内部发生的情况):

s4Dispatcher <- function(fun, ...) 
  threedots <- splitThreedots(list(...))
  ## --> automatically split `...`:
  ## 1) into those arguments that are part of the signature list of `fun` 
  ## 2) remaining part: everything that is not part of
  ##    the signature list and that should thus be passed further along as an 
  ##    updated version of the original `...`

  args_this <- threedots$this
  ## --> actual argument set relevant for the actual call to `fun`
  threedots <- threedots$threedots
  ## --> updated `...` to be passed along to other functions

  mthd <- selectMethod(fun, signature = inferSignature(args_this))
  ## --> `inferSignature()` would need to be able to infer the correct
  ## signature vector to be passed to `selectMethod()` from `args_this`

  ## Actual call //
  do.call(mthd, c(args_this, threedots))

以下是“键入的三点参数容器”生成器的外观示例。

请注意,为了使这种机制跨包工作,可能还提供一种声明某个函数的命名空间的可能性(arg ns 和字段 @987654357 @):

require("R6")
Threedots <- function(..., fun, ns = NULL) 
  name <- if (!is.null(ns)) sprintf("Threedots.%s.%s", ns, fun) else 
      sprintf("Threedots.%s", fun)
  eval(substitute(
    INSTANCE <- R6Class(CLASS,
      portable = TRUE,
      public = list(
        .args = "list",     ## Argument list
        .fun = "character", ## Function name
        .ns = "character",  ## Namespace of function
        initialize = function(..., fun, ns = NULL) 
          self$.fun <- fun
          self$.ns <- ns
          self$.args <- structure(list(), names = character())
          value <- list(...)
          if (length(value)) 
            self$.args <- value
          
        
      )
    )
    INSTANCE$new(..., fun = fun, ns = ns)
    ,
    list(CLASS = name, INSTANCE = as.name(name))
  ))

例子

x <- Threedots(y = "hello world!", fun = "foo", ns = "pkg.foo")

x
# <Threedots.pkg.foo.foo>
#   Public:
#     .args: list
#     .fun: foo
#     .ns: pkg.foo
#     initialize: function

class(x)
# [1] "Threedots.pkg.foo.foo" "R6" 

x$.args
# $y
# [1] "hello world!"

实际调用将如下所示:

foobar(x = 10) 
foobar(x = 10, Threedots(y = "hello world!", fun = "foo", ns = "pkg.foo")) 
foobar(x = 10, Threedots(y = 10, fun = "bar", ns = "pkg.bar")) 
foobar(x = 10, Threedots(y = list(a = TRUE), fun = "downTheLine", ns = "pkg.a"))) 

foobar(x = 10, 
       Threedots(y = "hello world!", fun = "foo", ns = "pkg.foo"),
       Threedots(y = 10, fun = "bar", ns = "pkg.bar),
       Threedots(y = list(a = 10), fun = "downTheLine", ns = "pkg.a")
)

【问题讨论】:

【参考方案1】:

查看?setGeneric 并搜索“...”,然后搜索?dotsMethods。可以定义在... 上分派的泛型(仅限,不与其他分派参数混合)。

.A = setClass("A", contains="numeric")
.B = setClass("B", contains="A")

setGeneric("foo", function(...) standardGeneric("foo"))
setMethod("foo", "A", function(...) "foo,A-method")

setGeneric("bar", function(..., verbose=TRUE) standardGeneric("bar"),
           signature="...")
setMethod("bar", "A", function(..., verbose=TRUE) if (verbose) "bar,A-method")

导致

> foo(.A(), .B())
[1] "foo,A-method"
> bar(.A(), .B())
[1] "bar,A-method"
> bar(.A(), .B(), verbose=FALSE)
> 

我不知道这是否适合您的其余场景。

【讨论】:

啊,非常好!非常感谢您的指点,我会对此进行调查! 按原样,这已经很有用了!但是您是否碰巧知道在未来版本中允许混合签名参数的状态/进展?那是我真正需要的。我可以以某种方式调整现有功能以允许这样做吗?

以上是关于概括`...`(三个点)参数调度:参数集的S4方法,包括`...`的主要内容,如果未能解决你的问题,请参考以下文章

调度到动作参数序列

Docker 镜像规范 v1.0.0

Docker 镜像规范 v1.0.0

异常检测(二)——传统统计学方法

死磕jeestie源码类型后面三个点(String...)和数组(String[])的区别

对三维点集的归一化变换