Каков предпочтительный способ определения метода S3 в пакете R без введения зависимости?

У меня есть пакет R (в настоящее время его нет в CRAN), который определяет пару методов S3 общих функций из других пакетов (в частности, knitr::knit_print и huxtable::as_huxtable). Однако они не являются ключевой частью моего пакета, поэтому я бы предпочел не создавать зависимость от этих пакетов, когда пользователь устанавливает мой пакет. Вплоть до R 4.0.0 я экспортировал методы S3 без импорта дженериков. Используя roxygen2, моя директива @export была преобразована в директиву export() в NAMESPACE, а не в S3method(). Это отлично работало в версиях R ‹ 4.0.0, потому что R сначала ищет в глобальной среде соответствующий метод generic_function.class, а не полагаясь на правильную регистрацию метода S3. Однако согласно этому блогу на сайте developer.r -project.org, R больше не ищет незарегистрированные методы S3.

Каков наилучший способ обойти это? На данный момент я добавил @importFrom директив в свои roxygen2 блоки и добавил оба пакета в раздел импорта DESCRIPTION. Однако, насколько я понимаю, это будет означать, что любой пользователь, устанавливающий мой пакет, также должен будет установить knitr и huxtable, хотят они того или нет.


person Nick Kennedy    schedule 28.04.2020    source источник
comment
Мне придется подумать об этом еще немного (и, возможно, снова обратиться к написанию расширений R), но вы, возможно, сможете избежать их перечисления в разделе «Предложения» в файле DESCRIPTION.   -  person duckmayr    schedule 28.04.2020
comment
Хм... Глядя на разделы 1.1. .3 и 1.1.3.1 в написании расширений R, кажется, что это, по крайней мере, предполагаемое использование предложений в DESCRIPTION, даже если типичный вариант использования - это примеры и т. д.   -  person duckmayr    schedule 28.04.2020
comment
Спасибо. Они всегда были в предложениях, но пакет не собирается, если я использую S3method в NAMESPACE без importFrom, а если я использую importFrom, пакет также должен быть в Imports (если я что-то не упустил!)   -  person Nick Kennedy    schedule 28.04.2020
comment
Ах, да, я думаю, ты прав. Извините, просто пытаюсь придумать обходные пути   -  person duckmayr    schedule 28.04.2020


Ответы (2)


К счастью, для R >= 3.6.0 вам даже не нужен ответ от caldwellst. Из записи в блоге, на которую вы ссылались выше:

Начиная с R 3.6.0, директивы S3method() в NAMESPACE также можно использовать для выполнения отложенной регистрации метода S3. С S3method(PKG::GEN, CLS, FUN) функция FUN будет зарегистрирована как метод S3 для класса CLS и общего GEN из пакета PKG только при загрузке пространства имен PKG. Это можно использовать в ситуациях, когда метод не нужен «немедленно», а необходимость предварительной загрузки пространства имен pkg (и всех его сильных зависимостей) для выполнения немедленной регистрации считается слишком «затратной».

Кроме того, это также обсуждается в документах для другого предложения vctrs::s3_register():

#' For R 3.5.0 and later, `s3_register()` is also useful when demonstrating
#' class creation in a vignette, since method lookup no longer always involves
#' the lexical scope. For R 3.6.0 and later, you can achieve a similar effect
#' by using "delayed method registration", i.e. placing the following in your
#' `NAMESPACE` file:
#'
#' ```
#' if (getRversion() >= "3.6.0") {
#'   S3method(package::generic, class)
#' }

Таким образом, вам просто нужно не использовать @importFrom, а вместо @export использовать @exportS3Method package::generic (см. https://github.com/r-lib/roxygen2/issues/796 и https://github.com/r-lib/roxygen2/commit/843432ddc05bc2dabc9b5b22c1ae7de507a00508)

Иллюстрация

Итак, для иллюстрации мы можем создать два очень простых пакета, foo и bar. В пакете foo есть только общая функция foo() и метод по умолчанию:

library(devtools)
create_package("foo")

#' foo generic
#'
#' @param x An object
#' @param ... Arguments passed to or from other methods
#' @export
foo <- function(x, ...) {
    UseMethod("foo", x)
}
#' foo default method
#'
#' @param x An object
#' @param ... Arguments passed to or from other methods
#' @export
foo.default <- function(x, ...) {
    print("Called default method for foo.")
}

После document() и install()ing создаем bar:

create_package("bar")

который создает метод bar для foo():

#' bar method for foo
#'
#' @param x A bar object
#' @param ... Arguments passed to or from other methods
#'
#' @exportS3Method foo::foo
foo.bar <- function(x, ...) {
    print("Called bar method for foo.")
}

Важно отметить, что мы должны загрузить пакет foo перед запуском document(), иначе @exportS3Method не сработает. Это,

library(foo)
document()

Но если мы это сделаем, то получим в NAMESPACE для bar следующее:

# Generated by roxygen2: do not edit by hand

S3method(foo::foo,bar)

Мы должны вручную добавить foo в «Предложения» в DESCRIPTION.

Затем, если мы удалим foo, мы все равно сможем установить bar:

> remove.packages("foo")
Removing package from ‘/home/duckmayr/R/x86_64-pc-linux-gnu-library/4.0’
(as ‘lib’ is unspecified)
> install("bar")
✓  checking for file ‘/home/jb/bar/DESCRIPTION’ ...
─  preparing ‘bar’:
✓  checking DESCRIPTION meta-information ...
─  checking for LF line-endings in source and make files and shell scripts
─  checking for empty or unneeded directories
─  building ‘bar_0.0.0.9000.tar.gz’

Running /opt/R/4.0.0/lib/R/bin/R CMD INSTALL \
  /tmp/Rtmp5Xgwqf/bar_0.0.0.9000.tar.gz --install-tests 
* installing to library ‘/home/jb/R/x86_64-pc-linux-gnu-library/4.0’
* installing *source* package ‘bar’ ...
** using staged installation
** R
** byte-compile and prepare package for lazy loading
** help
*** installing help indices
** building package indices
** testing if installed package can be loaded from temporary location
** testing if installed package can be loaded from final location
** testing if installed package keeps a record of temporary installation path
* DONE (bar)
person duckmayr    schedule 28.04.2020

Пакет vctrs предоставляет функцию s3_register, которая динамически регистрирует методы для использования в функции .onLoad. Подробнее о его использовании можно прочитать здесь. себе вы бы хотели:

.onLoad <- function(...) {
  if (requireNamespace("knitr", quietly = TRUE)) {
    vctrs::s3_register("knitr::knit_print", "class_name")
  }
  if (requireNamespace("huxtable", quietly = TRUE)) {
    vctrs::s3_register("huxtable::as_huxtable", "class_name")
  }
}

Документация тоже добрая, поэтому вам не нужно импортировать vctrs:

Чтобы избежать зависимости от vctrs для этой единственной функции, не стесняйтесь копировать и вставлять исходный код функции в свой собственный пакет.

person caldwellst    schedule 28.04.2020