Using un-exported function from another R package?
Summarising comments from @baptise, and etc...:
:::
not allowed on CRAN, so options:- ask author to export it so you can use it in your package via standard imports or suggests.
- copy / lift a version of it and clearly cite within your package.
How to use 'unexported' datasets from an R package within another package
I'm not sure why you get this error. I created a small test package with only the function below and checks run without any issues:
#' Loads random dataset from wpp2019
#'
#' @export
#'
#' @importFrom utils data
load_random_data <- function() {
# check if package is installed
if (requireNamespace("wpp2019", quietly = TRUE)) {
# get name of random dataset
rand <- sample(data(package = "wpp2019")[["results"]][, 3], 1)
x <- utils::data(list = rand, package = "wpp2019", envir = environment())
return(get(x))
} else {
stop("Install package from https://github.com/PPgp/wpp2019 first.")
}
}
I found the way in which data
evaluates the name of the dataset a little confusing. So maybe the example functions helps to clear things up.
@user2554330 nudged me to write a more general function to load datasets from packages. So here it is with some extra bells and whistles:
#' Load dataset from from a package
#'
#' @param title character. Title of a dataset in the package
#' @param package character. Name of a package in which the dataset is present.
#'
#' @export
#'
#' @importFrom utils data
load_data <- function(title, package = "wpp2019") {
# check if package is installed
if (requireNamespace(package, quietly = TRUE)) {
# check if dataset is in the package
if (title %in% data(package = package)[["results"]][, 3]) {
return(get(utils::data(list = title,
package = package,
envir = environment())))
} else {
stop("Dataset '", title, "' not found in package ", package, ".")
}
} else {
stop("Package '", package, "' not installed.")
}
}
And a quick test if this works:
dat <- load_data("UNlocations")
ncol(dat)
#> 32
load_data("UNLocations")
#> Error in load_data("UNLocations"): Dataset 'UNLocations' not found in package wpp2019.
load_data("UNLocations", "not_installed")
#> Error in load_data("UNLocations", "not_installed"): Package 'not_installed' not installed.
As you can see, the functions fails with more comprehensible error messages.
how to attach a package including the non-exported functions?
The best way to do bug fixes like that is to set the environment of your modified function to the same environment as the original. For example, to fix extpack::badfn
use
badfn <- function(...) { ... } # new version in your global workspace
environment(badfn) <- environment(extpack::badfn)
This means it will see the private functions when it makes calls, but it won't replace extpack::badfn
in the original location, so other extpack
functions that call it will still call the original one. If you want them to call yours instead, use
assignInNamespace("badfn", badfn, "extpack")
after making the change above.
If some other package imports extpack::badfn
, what it gets will depend on the exact order of operations, so in a case like that you're best to bite the bullet and rebuild the whole package with your fixes in place.
How to add unexported functions to R Package
R does not provide infrastructure for hiding source code[1]. Doing that is against the R developers' values and wishes.
So, what you want to do is not possible with R.
[1] http://r.789695.n4.nabble.com/how-to-hide-code-of-any-function-td4474822.html
How to call R function (which should not be exported) from Rcpp?
You have to get a bit meta here. It is possible to get an environment containing all the unexported functions in a package using the base R function asNamespace
. This function itself can be used inside Rcpp. You then create a new Environment
from the output of that function, from which you can harvest the unexported function.
As an example, let's get the unexported function ggplot2:::as_lower_ascii
to do some work on a string we pass to an Rcpp function:
#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::export]]
CharacterVector fun(CharacterVector input){
Function asNamespace("asNamespace");
Environment ggplot_env = asNamespace("ggplot2");
Function to_lower_ascii = ggplot_env["to_lower_ascii"];
return to_lower_ascii(input);
}
So if we source this, then back in R we can do:
fun("HELLO WORLD")
#> [1] "hello world"
How can I use a function inside an R package that has not be exported?
You are trying to solve a non-problem.
If you want a user to use a function, export it.
If you don't want a user to use a function, do not export it.
That said...
There is a possibility that you are getting caught up on how functions are passed as arguments to other functions. Functions are first class objects in R, so they can be passed around very easily. Consider the following example:
m <- function(x, y) x + y
n <- function(x, y) x - y
k1 <- function(x, y, FUN) FUN(x, y)
k1(10, 5, FUN = m)
# [1] 15
k1(10, 5, FUN = n)
# [1] 5
k2 <- function(x, y, FUN = m) FUN(x, y)
k2(10, 5) # uses `m()` by default
# [1] 15
k2(10, 5, FUN = m)
# [1] 15
k2(10, 5, FUN = n)
# [1] 5
If you really don't want to users to access the functions directly but want to give them choice over which to use, then define the auxiliary functions in the body of the main function and use, for example, a switch()
to choose between them:
fun <- function(x, method = c("A", "B")) {
m <- match.arg(method)
a <- function(x) x^2
b <- function(x) sqrt(x)
switch(m, A = a(x), B = b(x))
}
fun(2)
# [1] 4
fun(2, "A")
# [1] 4
fun(2, "B")
# [1] 1.414214
R Package: how import works when my exported function does not call explicitly a function from other packages, but a subroutine does
My understanding is that there are three "correct" ways to do the import. By "correct," I mean that they will pass CRAN checks and function properly. Which option you choose is a matter of balancing various advantages and is largely subjective.
I'll review these options below using the terminology
primary_function
the function in your package that you wish to exporthidden
the unexported function in your package used byprimary_function
thirdpartypkg::blackbox
,blackbox
is an exported function from thethirdpartypkg
package.
Option 1 (no direct import / explicit function call)
I think this is the most common approach. thirdpartypkg
is declared in the DESCRIPTION file, but nothing is imported from thirdpartypkg
in the NAMESPACE file. In this option, it is necessary to use the thirdpartypkg::blackbox
construct to get the desired behavior.
# DESCRIPTION
Imports: thirdpartypkg
# NAMESPACE
export(primary_function)
#' @name primary_function
#' @export
primary_function <- function(x, y, z){
# do something here
hidden(a = y, b = x, z = c)
}
# Unexported function
#' @name hidden
hidden <- function(a, b, c){
# do something here
thirdpartypkg::blackbox(a, c)
}
Option 2 (direct import / no explicit function call)
In this option, you directly import the blackbox
function. Having done so, it is no longer necessary to use thirdpartypkg::blackbox
; you may simply call blackbox
as if it were a part of your package. (Technically it is, you imported it to the namespace, so there's no need to reach to another namespace to get it)
# DESCRIPTION
Imports: thirdpartypkg
# NAMESPACE
export(primary_function)
importFrom(thirdpartypkg, blackbox)
#' @name primary_function
#' @export
primary_function <- function(x, y, z){
# do something here
hidden(a = y, b = x, z = c)
}
# Unexported function
#' @name hidden
#' @importFrom thirdpartypkg blackbox
hidden <- function(a, b, c){
# do something here
# I CAN USE blackbox HERE AS IF IT WERE PART OF MY PACKAGE
blackbox(a, c)
}
Option 3 (direct import / explicit function call)
Your last option combines the the previous two options and imports blackbox
into your namespace, but then uses the thirdpartypkg::blackbox
construct to utilize it. This is "correct" in the sense that it works. But it can be argued to be wasteful and redundant.
The reason I say it is wasteful and redundant is that, having imported blackbox
to your namespace, you're never using it. Instead, you're using the blackbox
in the thirdpartypkg
namespace. Essentially, blackbox
now exists in two namespaces, but only one of them is ever being used. Which begs the question of why make the copy at all.
# DESCRIPTION
Imports: thirdpartypkg
# NAMESPACE
export(primary_function)
importFrom(thirdpartypkg, blackbox)
#' @name primary_function
#' @export
primary_function <- function(x, y, z){
# do something here
hidden(a = y, b = x, z = c)
}
# Unexported function
#' @name hidden
#' @importFrom thirdpartypkg blackbox
hidden <- function(a, b, c){
# do something here
# I CAN USE blackbox HERE AS IF IT WERE PART OF MY PACKAGE
# EVEN THOUGH I DIDN'T. CONSEQUENTLY, THE blackbox I IMPORTED
# ISN'T BEING USED.
thirdpartypkg::blackbox(a, c)
}
Considerations
So which is the best approach to use? There isn't really an easy answer to that. I will say that Option 3 is probably not the approach to take. I can tell you that Wickham advises against Option 3 (I had been developing under that framework and he advised me against it).
If we make the choice between Option 1 and Option 2, the considerations we have to make are 1) efficiency of writing code, 2) efficiency of reading code, and 3) efficiency of executing code.
When it comes to the efficiency of writing code, it's generally easier to @importFrom thirdpartypkg blackbox
and avoid having to use the ::
operator. It just saves a few key strokes. This adversely affects readability of code, however, because now it isn't immediately apparent where blackbox
comes from.
When it comes to efficiency of reading code, it's superior to omit @importFrom
and use thirdpartypkg::blackbox
. This makes it obvious where blackbox
comes from.
When it comes to efficiency of executing code, it's better to @importFrom
. Calling thirdpartypkg::blackbox
is about 0.1 milliseconds slower than using @importFrom
and calling blackbox
. That isn't a lot of time, so probably isn't much of a consideration. But if your package uses hundreds of ::
constructs and then gets thrown into looping or resampling processes, those milliseconds can start to add up.
Ultimately, I think the best guidance I've read (and I don't know where) is that if you are going to call blackbox
more than a handful of times, it's worth using @importFrom
. If you will only call it three or four times in a package, go ahead and use the ::
construct.
Exported functions from another package
Golang Tour specify exported name as
A name is exported if it begins with a capital letter. And When
importing a package, you can refer only to its exported names. Any
"unexported" names are not accessible from outside the package.
Change the name of reverse
func to Reverse
to make it exportable to main package. Like below
package stringutil
func Reverse(s string) string {
r := []rune(s)
for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return string(r)
}
Related Topics
Print Pretty Data.Frames/Tables to Console
How Achieve Identical Facet Sizes and Scales in Several Multi-Facet Ggplot2 Graphics
How to Check If a Column Is a Date in R
R Dplyr Rowwise Mean or Min and Other Methods
Create Sequential Counter That Restarts on a Condition Within Panel Data Groups
Save a Ggplot2 Time Series Plot Grob Generated by Ggplotgrob
Use Pipe Operator %>% with Replacement Functions Like Colnames()<-
Specify Widths and Heights of Plots with Grid.Arrange
Modify Glm Function to Adopt User-Specified Link Function in R
How to Group by Two Columns in R
Ggplot: Colour Points by Groups Based on User Defined Colours
How to Append a Whole Dataframe to a CSV in R
Calculating Time Difference Between Two Columns