Search code examples
rr-packagetidytable

R package: global import of equivalent package if preferred one fails


This question is inspired by the specific nature of tidytable as an intentional drop-in replacement for entire tidyverse packages and my own circumstances.

I'm developing a couple of R packages at work that rely extensively on tidytable. The R environment is quite limited and centrally controlled (and that's impossible to change) and it would be advantageous for the packages to automatically fall back on dplyr + tidyr + purrr. These are more likely to be centrally added/updated to the correct environment whether I'm around bugging the right people or not.

I'm currently using @import tidytable, as I repeatedly use a long list of its functions. Is it possible to get my package to fall back on dplyr, tidyr and purrr if tidytable fails to load? My package runs the same whether I import one or the others; but I'd prefer tidytable when available as it is noticeably faster.

I'm aware of certain workarounds for one or two functions but not across the whole package, probably because the tidytable/dplyr+tidyr+purrr match not only of functionality but also syntax is rather unusual.


Solution

  • Some assumptions:

    • Your package is not going on CRAN. Disclaimer, this is something I've had a hard time with, and that is getting R CMD check to behave well with tests and documentation and package checks when functions require presence of a package that is in Suggests: and not in Imports:. Perhaps I've done it wrong, I know it is very doable and done in many (popular) packages. So perhaps this is just a precautionary bullet ...
    • You are certain that the code path returns the same results (no extra warnings/errors) regardless of which package(s) is loaded, and it either doesn't care which is loaded or it can easily detect which and work without error.

    Since the packages you need will not be in Imports:, then when somebody does library(yourpackage), R will not automatically import the either or the other package(s). In this case, you have two options, depending on your comfort with using the .onLoad or .onAttach functions (see ?ns-hooks):

    • Write a helper function that calls require(tidytable)1 and, if FALSE, then calls require(tidyverse) (or just the packages you really need, since loading tidyverse can be non-instantaneous). If that is also FALSE, then stop(.). Call this helper function in every one of your package functions that would need it. It adds a little overhead to each function, but generally it's nearly a no-op if the package is already loaded.

    • Or, write that function and then call it from .onLoad once, and it'll be executed when somebody calls library(yourpackage). This should also work when somebody calls yourpackage::somefunc without first calling library(yourpackage). If you want to require that they run library(yourpackage), you can instead use .onAttach, but I don't know if that adds any value here.

    Notes:

    1. If you aren't familiar with the difference between library and require (and their related functions), see https://stackoverflow.com/a/51263513/3358272, https://yihui.org/en/2014/07/library-vs-require/, https://r-pkgs.org/namespace.html#search-path.