In a recent blog post I made the argument that you should consider implementing the function base::use()
into your workflow. Following the blog post, there was an interesting discussion on Reddit with some important caveats and limitations of use()
. Here, I will summarise three interrelated points that I believe are important additions to my previous post.
1. Do not use use()
more than once per package
As I wrote in my previous post, use()
is just a wrapper around library()
. This also comes with the limitation that we only load a package once, and if you use use()
, you only get to make one call for each package. For example, using use()
twice on {dplyr} will only load the function(s) specified the first time you use use()
. In the example below, the second use of use()
will not load dplyr::filter()
into the global namespace.
use("dplyr", "select")
use("dplyr", "filter")
The issue is that you do not even get an error or warning about dplyr::filter()
not being part of the global namespace. Konrad Rudolph writes about why this is a problem on Mastodon (see also this blog post by Thomas Sandmann). If your workflow relies on several different R-scripts, it can be problematic to rely on use()
without refactoring your script. Accordingly, use()
is by no means a perfect solution.
2. Always be explicit, sometimes
Another argument is to just always make sure that you are explicit about which package you rely on when using a function, such as using dplyr::filter()
instead of filter()
. While I believe it is good to be explicit in your code and not rely on any implicit assumptions, I also see specific exceptions that also matter for the use of use()
.
For example, consider a simple script where the only thing we need to do is to create a figure with {ggplot2}. Here is one way of doing it where we load the package and rely on the relevant functions in the package to create a figure.
library("ggplot2")
ggplot(mtcars, aes(disp)) +
geom_histogram() +
theme(panel.background = element_blank(),
axis.text = element_blank())
If we want to make everything explicit, the code is no longer as easy to read and we might be more likely to make mistakes (by not having an easy overview of everything that is going on). There is nothing wrong with the example below, and I would even go for it if I was going to use {ggplot2} in production (e.g., in a Shiny app), but for this particular example it would not be my preferred solution.
ggplot2::ggplot(mtcars, ggplot2::aes(disp)) +
ggplot2::geom_histogram() +
ggplot2::theme(panel.background = ggplot2::element_blank(),
axis.text = ggplot2::element_blank())
This is also relevant for use()
as it might simply be better to use library("ggplot2")
than having to specify all functions of relevance from {ggplot2}. A similar example can be {data.table} where it will be difficult to imagine making all function calls explicit and still have code that is easy to read and use. Or if you had to write magrittr::`%>%`
when using the pipe operator it would no longer make sense to use the pipe operator.
3. Think inside the {box}
An alternative to base::use()
is box::use()
(see documentation and examples here). There are many advantages to using box::use()
, including that you do not have the limitation of only being able to use use()
once per package.
While there definitely are a lot of scenarios where it will make sense to use {box}, I do not see box::use()
being universally better than base::use()
. It all depends on the specific context. If I have a simple project where all I need to do is to load a function or two from one package, I find it better to use base::use()
without having to rely on yet another package (in this case {box}).
In sum, while I would still recommend people to check out base::use()
and consider potential use cases, as described in my previous post, it is important to keep the caveats and limitations outlined above in mind.