Documenting re-exported functions in an R package
Asked Answered
M

3

6

I'm in the process of splitting one of my R packages into two, since it incorporates two logically distinct sets of functionality, one of which is more general than the other. However, since the original package is reasonably popular, and is depended upon by at least one other package, I don't want to break compatibility.

R's namespace system provides a way to handle this, by importing the functions now in the split-off package (which is RNifti), and then re-exporting them from the downstream package (which is RNiftyReg). That way, third-party users and packages can load just RNiftyReg, and still see functions that now actually belong in RNifti. Moreover, the documentation for these functions still works, since the RNifti namespace is loaded along with RNiftyReg.

However, R CMD check complains, because the re-exported functions are not documented in RNiftyReg.

So my question is: what is the best practice in this scenario?

I seem to have three options, none of which appeal very much.

  • Break existing code by requiring the new package, RNifti, to be loaded alongside RNiftyReg for all the previously-available functions to be available. Clearly that's undesirable.
  • Duplicate all the documentation for these functions in the downstream package, RNiftyReg. That should keep everyone happy but is messy to maintain, and can easily get out of sync if the packages are not always updated together.
  • Provide a single catch-all documentation page for all of these functions in RNiftyReg, pointing to the full documentation in RNifti. But that still needs to be kept in sync with respect to function arguments, and it requires users to use the ungainly ?RNifti::somefun syntax to see the "real" documentation.

Is there a way around this, or it simply unwise to re-export code like this?

Morell answered 21/9, 2016 at 14:43 Comment(0)
M
8

From some further poking around, it looks like \docType{import}, added in R version 3.1.1, is the best available mechanism for handling this scenario (as noted in this roxygen2 issue). This seems to effectively behave like my third option above, but it has the advantage of not documenting the function arguments explicitly, so they don't have to be kept in sync.

It seems that roxygen2 syntax like

#' @export
RNifti::xform

generates the right form of .Rd file, and satisfies R CMD check. I wasn't using this particular syntax before, which is why the issue remained outstanding.

I've confirmed that this works with an unmodified third-party package, so it looks like the best bet.

Morell answered 21/9, 2016 at 15:54 Comment(2)
Thanks for pointing that out. I wasn't aware of the docType import.Unsettled
It looks like you now need to also include #' @importFrom <package> <function> to get roxygen to document it correctly. See e.g. github.com/tidyverse/dplyr/blob/master/R/reexport-tibble.rCessation
U
2

I would not import all these functions only to export them again. For these kind of things, the most logic thing to do seems to make the package RNiftyReg dependent on the package RNifti.

So you add to the Depends field in the DESCRIPTION file:

Depends:
    RNifti

To be 100% sure that you use the correct functions from RNiftyReg, you can call them using the :: operator in your code, eg:

RNifti::aNiceFunction(arg1, arg2)

You can add RNifti to the Depends field and still import the package through the NAMESPACE file. This is necessary if you want to keep the functions of RNifti available for packages that only import the namespace of RNiftyReg.

In that case you do NOT mention it in the Imports: field of the DESCRIPTION file. As the manual says, a package should be mentioned only in one of both. And as you want the namespace of RNifti to be attached, you have to mention it in the Depends field.

So this is actually your 1st option, but done in such a way that the user hardly notices this happens. library('RNiftyReg') will now automatically attach RNifti as well.

Unsettled answered 21/9, 2016 at 14:55 Comment(6)
Yes, this is a perfectly sensible solution. I guess I've been gradually conditioned to use Imports over Depends, so now I tend to forget the latter still exists... :)Morell
If it were just one or two functions, I would make the documentation page for the function in RNiftyReg be as minimal as possible with several express statements to see \code{\link[RNifti]{fn_name}}. But if you're truly dependent on RNifti for RNiftyReg to function, then Joris Meys has given sage advice.Sitwell
Actually, this isn't a full solution, because while it works for user sessions where library(RNiftyReg) is called, it doesn't work for third-party packages that only import RNiftyReg. Running R CMD check on one such package still reports "missing or unexported objects".Morell
@JonClayden Did you try it? If RNifti is added in the Depends field, R CMD check will attach that one before running the checks afaik. Furthermore, using the full RNifti::myfun() notation will make sure all functions are found. Such behaviour can only happen imho if you rely on functions not exported by RNifti in the 3rd package, but that's a design no-no if you ask me. You can import RNifti in the RNiftyReg namespace as explained above. No need to export the RNifti functions as long as you have RNifti under Depends.Unsettled
Yes, I tried it. The third package imports RNiftyReg, so it loads its namespace but does not attach the package. Since RNiftyReg is not attached, neither is RNifti, and hence the functions in RNifti are not available. The third package would have to depend on RNiftyReg and/or import RNifti to make this work, and I don't want to impose that requirement on third partiesMorell
@JonClayden I see your point, that's right (but a design flaw imho). Then you can still import RNifti in the namespace of RNiftyReg, but keep RNifti in the Depends field as I explained before. When the namespace of RNiftyReg is imported in a third package, the one of RNifti is imported with it and everything should work as before.Unsettled
S
0

For anyone wondering, you can get a list of re-exported functions using:

exported <- getNamespaceExports("package")
all_ls <- ls(envir = asNamespace("package"))
exported[!exported %in% all_ls]

So, for dplyr for example:

exported <- getNamespaceExports("dplyr")
all_ls <- ls(envir = asNamespace("dplyr"))
# let's sort them too
exported[!exported %in% all_ls]
#>  [1] ".data"         "%>%"           "add_row"       "all_of"        "any_of"       
#>  [6] "as_data_frame" "as_label"      "as_tibble"     "contains"      "data_frame"   
#> [11] "data_frame_"   "ends_with"     "enexpr"        "enexprs"       "enquo"        
#> [16] "enquos"        "ensym"         "ensyms"        "everything"    "expr"         
#> [21] "frame_data"    "glimpse"       "intersect"     "last_col"      "lst"          
#> [26] "lst_"          "matches"       "num_range"     "one_of"        "quo"          
#> [31] "quo_name"      "quos"          "setdiff"       "setequal"      "starts_with"  
#> [36] "sym"           "syms"          "tbl_sum"       "tibble"        "tribble"      
#> [41] "type_sum"      "union"  
Selfsacrifice answered 18/11, 2022 at 8:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.