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
. Alsouse
returns a module;import
returns an environment. Don't see how this could be changed.) - Merge with master