Актуальный вопрос
Можно ли определить методы для набора аргументов подписи, которые включает ...
(в отличие от исключительно для ...
)? Это невозможно "из коробки", но теоретически было бы возможно вообще (с некоторыми улучшениями) или это просто невозможно сделать из-за того, как механизм 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)
)
Мартин Морган благодарно указал мне на dotsMethods
, и он говорит следующее:
В настоящее время "..." нельзя смешивать с другими формальными аргументами: либо подпись общей функции есть только "..." , либо не содержит "..." . (Это ограничение может быть отменено в будущей версии.)
Фон
Рассмотрим следующую попытку обобщения механизма диспетчеризации на основе ...
из простого случая (только функция one должна использовать аргументы, переданные через ...
, например, использование ...
в plot()
для передачи аргументов par()
) в сценарии, включающие следующие аспекты (взятые из здесь):
- когда вы хотите передать аргументы более одного, следовательно
r
, получатели, - когда эти получатели могут находиться на
c
разных уровнях вызывающего стека - и когда они могут даже использовать те же имена аргументов, но связывать различные значения с этими аргументами в их собственном scope/clos/frame/environment
Также обратите внимание, что, хотя действительно может быть хорошей практикой сделать это, функции/интерфейсы верхнего уровня должны не обязательно нуждаться в определении (много) явных аргументы впоследствии вызываемых функций/интерфейсов для правильной передачи аргументов. IMO, этот выбор должен быть оставлен разработчику, поскольку иногда одна или другая альтернатива имеет больше смысла.
Было бы здорово, если бы я мог заменить отправление, которое в настоящее время обрабатывается через withThreedots()
(для которого AFAICT необходимо будет задействовать фактическое разделение ...
) с диспетчером S4 как-то, что в идеале просто может вызвать foo(x = x, ...)
вместо withThreedots("foo", x = x, ...)
в foobar()
:
Определения
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
Концептуальный подход (САМОСТОЯТЕЛЬНЫЙ КОД PSEUDO)
Наверное, я ищу что-то вроде этого:
Определения
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 на регулярных исильные аргументы подписи > threedots) и те элементы, которые должны быть переданы вместе с функциями, которые 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
и field .ns
):
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")
)