wahani / modules

Modules in R

Home Page:https://cran.r-project.org/package=modules

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

import/export pattern

michaelsbradleyjr opened this issue · comments

I very much appreciate how your modules package allows me to keep tight control over what gets loaded into top-level scripts and sub-modules.

Here's a sketch of a pattern that I use a fair bit, owing to conflicts that can arise when loading packages with base::library:

# qualified imports
# =================
dt <- modules::module({
  modules::import(data.table)
  as.data.table     -> as.data.table
  as.xts.data.table -> as.xts.data.table
  copy              -> copy
})

xts <- modules::module({
  modules::import(xts)
  .index -> dot.index
  last   -> last
})

# unqualified imports
# -------------------
modules::import(
  # library
  data.table,
  # functions
  `:=`,
  data.table
)

modules::import(
  # library
  xts,
  # functions
  xts
)

Is there an easy way to rig up "qualified imports" of everything that data.table and xts export, so that I have all of their public functions available via dt$ and xts$ without having to explicitly -> those functions? Yes, I realize I can refer to any of them with data.table:: or xts::, but in practice I find I prefer the module pattern sketched above.

Thanks for such a great library!

Here's what I came up with, so far:

qual_import <- function(pkg) {
  eval(substitute(
    modules::module({
      modules::import(pkg)
      .env <- environment()
      lapply(
        getNamespaceExports(pkg_str),
        function(...) assign(sub("^\\.", "dot.", utils::head(..., 1)),
                             eval(as.name(utils::head(..., 1))), pos = .env)
    )}),
    {pkg_sym <- substitute(pkg)
     if ("character" %in% typeof(pkg_sym)) pkg_sym <- as.name(pkg)
     list(pkg = pkg_sym,
          pkg_str = toString(pkg_sym))}
))}

Thank you.

It might be worth adding the functionality such that

dt <- modules::import("data.table", attach = FALSE)

does what you want to do. I was thinking about this functionality anyway. This would also be more consistent with the use function. Generally the problem with this pattern is that some packages expect other packages to be attached to the search path (especially when S4 methods are involved). I don't see how this can be taken care of in this feature, yet...

Your current solution looks complicated which is probably due to the non standard evaluation in the import function. Also you want to have the possibility to pass arguments to the constructor and I hope this would not be necessary given the above feature - please correct me if I am wrong.

What you can do until I add this functionality is to use the following snippet:

dt <- modules::module({
  import("data.table")
  expose(parent.env(environment()))
})

dt$data.table(x = 1:10)

Is the dt module what you are interested in?

Yes, the dt module is my aim. I will experiment with using the interim solution you propose, alongside the qual_import function I cooked up, and see if there are surprises with either.

When you wrote, "you want to have the possibility to pass arguments to the constructor" – I'm not sure what you meant. Can you clarify?

I like the idea of being able to use module::import with an attach option.

At the moment it is not easy to parameterize the construction of a module. In your case you might be interested in passing down the package name as an argument, pkg, and make it somehow available during the initialization of the module. This is one of the two reasons you need the nse in qual_import. The other one is that even if you could pass down an argument like pkg you would still have to fight against the nse in import. This is hopefully not necessary when you can just write dt <- import("data.table").

(Also it might be necessary to relax the restrictions on names exported by modules -- to allow .name... This also means we get a lot of stuff we don't want to see, like the tables for methods and classes.)

@michaelsbradleyjr you can now use:

devtools::install_github("wahani/modules", ref = "iss2")
dt <- modules::use("package:data.table")
dt <- modules::import("data.table", attach = FALSE)

I couldn't really make up my mind. There are some implementation issues why I also like use to take care of it. But I guess that this behavior may come as a surprise for a user. Feedback is welcome...

Will give it a shot, thanks!

The modules::import("some.lib", attach = FALSE) facility you added has been working really well for me.

Okay thanks for the feedback. The additional argument in import it is then.

  • Remove the use("package:data.table") feature
  • Do we need some doc? (Hopefully not - a bit inconsistent is the use of different defaults for attach. Also use returns a module; import returns an environment. Don't see how this could be changed.)
  • Merge with master