Search code examples
rr-package

Use function from a package but different versions simultaneously


I have a multiple versions of the same package foo (all with one function bar), that I all want to use in the same script.

Following this question, I can load v1 of the package with library("foo", lib.loc = "pkgs/v1"). But this loads all of the functions from the package.

Now I want to assign foo::bar from the version v1 to bar_v1 and foo::bar from version v2 to bar_v2 to call them independently. But I do not see the option to only load one function of the library given a lib location (eg a solution would be to specify a lib.loc in the function call bar_v1 <- foo::bar).

Is this possible in R?

MWE

I have created a test package here github.com/DavZim/testPkg, which has one function foo which prints the package version (hard coded). The package has two releases, one for each version.

To get the tar.gz files of the package, you can use this

# Download Files from https://github.com/DavZim/testPkg
download.file("https://github.com/DavZim/testPkg/releases/download/v0.1.0/testPkg_0.1.0.tar.gz", "testPkg_0.1.0.tar.gz")
download.file("https://github.com/DavZim/testPkg/releases/download/v0.2.0/testPkg_0.2.0.tar.gz", "testPkg_0.2.0.tar.gz")

Then to setup the folder structure in the form of

pkgs/
  0.1.0/
    testPkg/
  0.2.0/
    testPkg/

I use

if (dir.exists("pkgs")) unlink("pkgs", recursive = TRUE)

dir.create("pkgs")
dir.create("pkgs/0.1.0")
dir.create("pkgs/0.2.0")

# install the packages locally
install.packages("testPkg_0.1.0.tar.gz", lib = "pkgs/0.1.0", repos = NULL)
install.packages("testPkg_0.2.0.tar.gz", lib = "pkgs/0.2.0", repos = NULL)

Now the question is what do I write in myscript.R?

Ideally I would have something like this

bar_v1 <- some_function(package = "testPkg", function = "foo", lib.loc = "pkgs/0.1.0")
bar_v2 <- some_function(package = "testPkg", function = "foo", lib.loc = "pkgs/0.2.0")

bar_v1() # calling testPkg::foo from lib.loc pkgs/0.1.0
#> [1] "Hello World from Version 0.1.0"

bar_v2() # calling testPkg::foo from lib.loc pkgs/0.2.0
#> [1] "Hello World from Version 0.2.0"

Non-working Try

Playing around with it, I thought something like this might work. But it doesn't...

lb <- .libPaths()

.libPaths("pkgs/0.1.0")
v1 <- testPkg::foo
v1()
#> [1] "Hello from 0.1.0"

.libPaths("pkgs/0.2.0")
v2 <- testPkg::foo
v2()
#> [1] "Hello from 0.1.0"

.libPaths(lb)

v1()
#> [1] "Hello from 0.1.0"
v2()
#> [1] "Hello from 0.1.0"   #! This should be 0.2.0!

Interestingly, if I swap around the versions to load 0.2.0 first then 0.1.0, I get this

lb <- .libPaths()

.libPaths("pkgs/0.2.0")
v1 <- testPkg::foo
v1()
#> [1] "Hello from 0.2.0"

.libPaths("pkgs/0.1.0")
v2 <- testPkg::foo
v2()
#> [1] "Hello from 0.2.0"

.libPaths(lb)

v1()
#> [1] "Hello from 0.2.0"
v2()
#> [1] "Hello from 0.2.0"

Solution

  • 1) Successive loads Assume that we have source packages for testPkg in the current directory and they are named testPkg_0.1.0.tar.gz and testPkg_0.2.0.tar.gz. Now create pkgs, pkgs/0.1.0 and pkgs/0.2.0 directories to act as release libraries and then install those source packages into those libraries.

    Now assuming each has a function foo which does not depend on other objects in the package, load each package in turn, rename foo, and detach/unload the package. Now both can be accessed under the new names.

    dir.create("pkgs")
    dir.create("pkgs/0.1.0")
    dir.create("pkgs/0.2.0")
    
    install.packages("testPkg_0.1.0.tar.gz", "pkgs/0.1.0", NULL)
    install.packages("testPkg_0.2.0.tar.gz", "pkgs/0.2.0", NULL)
    
    library("testPkg", lib.loc = "pkgs/0.1.0")
    fooA <- foo
    detach(unload = TRUE)
    
    library("testPkg", lib.loc = "pkgs/0.2.0")
    fooB <- foo
    detach(unload = TRUE)
    
    fooA
    fooB
    

    2) Change package name Another approach is to install one release normally and then for the other release download its source, change its name in the DESCRIPTION file and then install it under the new name. Then both can be used.

    Assuming testPkg_0.1.0.tar.gz and testPkg_0.2.0.tar.gz source packages both have function foo and that the two tar.gz files are in current directory we can accomplish this as follows. Note that changer will both change the DESCRIPTION file to use name testPkgTest and the directory name of the source package to the same.

    library(changer)
    
    install.packages("testPkg_0.1.0.tar.gz", repos = NULL)
    
    untar("testPkg_0.2.0.tar.gz")
    changer("testPkg", "testPkgTest")
    install.packages("testPkgTest", type = "source", repos = NULL)
    
    testPkg::foo()
    ## [1] "Hello World from Version 0.1.0"
    testPkgTest::foo()
    ## [1] "Hello World from Version 0.2.0"
    

    Old

    3) import Below we suggested the import package but unfortunately, as was pointed out in the comments, the code below using this package does not actually work and imports the same package twice. I have created an issue on the import github site. https://github.com/rticulate/import/issues/74


    Suppose we have source packages mypkg_0.2.4.tar.gz and mypkg_0.2.5.tar.gz in the current directory and that each has a function myfun. Then this will create a library for each, install them into the respective libraries and import myfun from each. These will be located in A and B on the search path.

    Note that the import package should be installed but not loaded, i.e. no library(import) statement should be used. You may wish to read the documentation of the import package since variations of this are possible.

    # use development version of import -- the CRAN version (1.3.0) has
    # a bug in the .library= argument
    devtools::install_github("rticulate/import")
    
    dir.create("mypkglib")
    dir.create("mypkglib/v0.2.4")
    dir.create("mypkglib/v0.2.5")
    
    install.packages("mypkg_0.2.4.tar.gz", "mypkglib/v0.2.4", NULL)
    install.packages("mypkg_0.2.5.tar.gz", "mypkglib/v0.2.5", NULL)
    
    import::from("mypkg", .library = "mypkglib/v0.2.4", .into = "A", myfun)
    import::from("mypkg", .library = "mypkglib/v0.2.5", .into = "B", myfun)
    
    search()
    ls("A")
    ls("B")
    
    get("myfun", "A")
    get("myfun", "B")
    

    Another possibility is to put them both into imports (used by default) with different names

    import::from("mypkg", .library = "mypkglib/v0.2.4", myfunA = myfun)
    import::from("mypkg", .library = "mypkglib/v0.2.5", myfunB = myfun)
    
    search()
    ls("imports")
    myfunA
    myfunB