В R, как я могу распространять общие методы из одного пакета в другой?

У меня есть пакет PackageA с общей функцией:

#' doWork
#' 
#' Do some work!
#'
#' @export
setGeneric(
    "doWork", 
    function(x) {

        standardGeneric("doWork")
    })

setMethod(
    "doWork", 
    signature = c("numeric"), 
    definition = function(x) {

        x == 10 # Some logic... 
    }

В PackageB, который зависит от PackageA, я хотел бы добавить еще несколько методов в doWork:

#' @import PackageA
setMethod(
    "doWork", 
    signature = c("character"), 
    definition = function(x) {

        length(x) == 1 && x == "10" # Some more logic... 
    }

Это работает. Однако это означает, что пользователь PackageB должен также library(PackageA).

Это не удается:

library(PackageB)

doWork("10") # Fails!

Это работает:

library(PackageA)
library(PackageB)

doWork("10")

Я хотел бы использовать generic из PackageA в PackageB, но не требует загрузки PackageA для использования только методов в PackageB.

Как я могу это достичь?

Ответы

Ответ 1

Это фактически документировано, но это не очень понятно; см. раздел 1.5.6 Написание расширений R.

Фокус в том, чтобы импортировать родовое из PackageA, а затем повторно экспортировать его из PackageB. Используя аннотации roxygen, это выглядит так:

#' @importMethodsFrom PackageA doWork
#' @export 
setMethod(
    "doWork", 
    signature = c("character"), 
    definition = function(x) {

        length(x) == 1 && x == "10" # Some more logic... 
    })

Когда вы вызываете devtools::document(), это произойдет, если вы не загрузили первый раз PackageA (вызов library(PackageA)).

Однако, после построения, PackageA не требуется:

> library(PackageB)
> showMethods("doWork")
Function: doWork (package PackageA)
x="character"
x="numeric"

Для справки, автоматически сгенерированный файл NAMESPACE выглядит следующим образом:

exportMethods(doWork)
importMethodsFrom(PackageA, doWork)

Этот метод не дает никаких предупреждений о конфликтах именования и т.д., поэтому он кажется "кошерным".

Ответ 2

Кажется, это работает для меня, но я не вижу, чтобы это было документировано, поэтому я не обязательно предполагал, что это кошерный. pckgA:

#' @export

setGeneric("doWork", function(x) standardGeneric("doWork"))
setMethod("doWork", signature = "numeric", function(x) x == 11)

и pckgB:

#' @export
#' @import pckgA

setGeneric("doWork", getGeneric("doWork", package="pckgA"))
setMethod("doWork", "character", function(x) identical(x, "10"))

Основной трюк состоял в том, чтобы импортировать и реэкспортировать doWork из pckgA в pckgB. Затем, начиная с чистой сессии R:

library(pckgB)
doWork("10")
# [1] TRUE
doWork("11")
# [1] FALSE
doWork(11)
# [1] TRUE
library(pckgA)
doWork(11)
# [1] TRUE
doWork("10")
# [1] TRUE

Вам может потребоваться полностью очистить рабочую область (включая скрытые объекты), чтобы избавиться от каких-либо определений предшествующего метода, чтобы это действительно эффективно срабатывало.