Using Un-Exported Function from Another R Package

Using un-exported function from another R package?

  • Summarising comments from @baptise, and etc...:

  • ::: not allowed on CRAN, so options:

    1. ask author to export it so you can use it in your package via standard imports or suggests.
    2. 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 export
  • hidden the unexported function in your package used by primary_function
  • thirdpartypkg::blackbox, blackbox is an exported function from the thirdpartypkg 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



Leave a reply



Submit