<- function(name){
praise ::cli_inform("Hey {name}, You're so awesome!")
cli }
8 Use other packages
We’re going to level up our message by adding some styling to the message using the cli
package.
I want to introduce this package for two reasons:
- The fundamental message writing will improve your code in many places
- Makes it easier to write useful messages to users
- It will help make your package look sleek and modern
You are going to modify your function to look like this:
Which will then give you
There are always so many different ways to generate solutions. It is worthwhile knowing more about how some different approaches. I wrote about this a blog post, “Glue magic Part 1”
Notice that this is really similar to our previous template:
"Hey PERSON, You're so awesome!"
[1] "Hey PERSON, You're so awesome!"
The way this works is it replaces the thing inside the curly braces with the text you have in a variable name:
"Hey {variable}, You're so awesome"
8.1 pkg::fun
- why the ::
?
You might have noticed that I wrote:
::cli_inform cli
Instead of
library(cli)
cli_inform
One of the major shifts from writing R code and analyses to writing R packages is how you interact with other R packages you want to use.
Normally, in an R script, when you want to use a function from, say, cli
, you use library(cli)
.
However, we do not ever want to call library(cli)
inside a function in an R package. The reason is to do with NAMESPACE conflicts. A popular usecase of this is in the tidyverse
R package - where we get this message when we call library(tidyverse)
.
library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr 1.1.4 ✔ readr 2.1.5
✔ forcats 1.0.1 ✔ stringr 1.5.2
✔ ggplot2 4.0.0 ✔ tibble 3.3.0
✔ lubridate 1.9.4 ✔ tidyr 1.3.1
✔ purrr 1.1.0
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
The conflict message at the bottom tells us that dplyr::filter()
masks stats::filter()
.
This is a key issue with package development: masking.
If all R packages called library(<package>)
on every R package that they depended upon, then we’d have SO MUCH masking. It is almost considered rude or overbearing.
How do you NOT use library??
You say? The solution is to use what is called the “namespaced” form:
::fun() pkg
For example, if I want to use filter
from dplyr
, I do so with:
|>
cars ::filter(speed <= 4) dplyr
speed dist
1 4 2
2 4 10
So for every package we want to use a function from in an R package, we use the “namespaced form”, e.g., dplyr::filter()
.
We also have to declare the dependencies formally, which I’ll discuss in a moment. Right now, I need you to see the errors when you run check, so you can identify this problem yourself later.
So now we need to do the following:
- identify the packages that we use in our R package (e.g.,
cli
) - namespace them. (
cli::cli_inform()
) - Formally add the dependency with:
use_package("cli")
.
For example, looking at our praise function, it is in the namespaced form like so:
<- function(name){
praise ::cli_inform("Hey {name}, You're so awesome!")
cli }
- Update your R code to look like the following:
<- function(name){
praise ::cli_inform("Hey {name}, You're so awesome!")
cli }
- run:
devtools::check()
- Identify the error
- Fix this with
use_package("cli")
8.2 Error: there is no package called ‘pkg’
We get this error and warning related to cli
:
── R CMD check results ───────────────────── praiseme 0.0.0.9000 ────
Duration: 4.1s
❯ checking examples ... ERROR
Running examples in ‘praiseme-Ex.R’ failed
The error most likely occurred in:
> base::assign(".ptime", proc.time(), pos = "CheckExEnv")
> ### Name: praise
> ### Title: Deliver Praise
> ### Aliases: praise
>
> ### ** Examples
>
> praise(person = "Nick")
Error in loadNamespace(x) : there is no package called ‘cli’
Calls: praise ... loadNamespace -> withRestarts -> withOneRestart -> doWithOneRestart
Execution halted
❯ checking dependencies in R code ... WARNING
'::' or ':::' import not declared from: ‘cli’
1 error ✖ | 1 warning ✖ | 0 notes
This all stems from this warning:
❯ checking dependencies in R code ... WARNING
'::' or ':::' import not declared from: ‘cli’
This says we need to declare importing the cli
package. To do that we use the use_package()
function.
8.2.1 use_package(<pkg>) and
pkg::fun()`
We need to give special instructions to our R package so it knows to depend on the cli
package. We do so with the use_package()
function:
use_package("cli")
Which gives us a nice chatty response from usethis
:
✔ Adding cli to Imports field in DESCRIPTION.
☐ Refer to functions with `cli::fun()`.
- Ensure you have
cli::cli_inform(...)
- call
use_package(cli)
If you look at the DESCRIPTION file, you’ll see this new Imports
field:
Package: praiseme
Title: What the Package Does (One Line, Title Case)
Version: 0.0.0.9000
Authors@R:
person("Nicholas", "Tierney", , "nicholas.tierney@gmail.com", role = c("aut", "cre"),
comment = c(ORCID = "https://orcid.org/0000-0003-1460-8722"))
Description: What the package does (one paragraph).
License: MIT + file LICENSE
Encoding: UTF-8
Language: en-GB
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.3
Imports:
cli
The Imports
field is where R lists the R package it depends upon. This is so when you install this package, R can also make sure you have those packages installed.
My opinion is you should depend on as many R packages as you like! It’s far faster, I think, to depend on packages that get the job done, and then maybe later trim back some dependencies and rewrite code.
My reasoning is that it is (generally) really cheap to add dependencies, but more expensive (for my brain), to write them from scratch.
So, be greedy, add dependencies, then prune back.
There is a great cheatsheet for package development that I had stapled to my cubicle wall during my PhD. The website now is very useful, but make sure to check out the PDF.
tidyverse
package is not a dependency in packages
Because the purpose of the R package tidyverse
is only really to load other R packages, it does not contain functions. Don’t put tidyverse
in imports.
conflicted
Outside of the R package development world, it’s a good idea to proactively manage function conflicts. Lest you use stats::filter()
instead of dplyr::filter()
See the conflicted
R package for more on this idea.
- Imports vs Depends. We only really use Imports. Don’t use Depends unless you’re building an extension package, e.g., something that works with ggplot2, where it doesn’t make sense to have the package without ggplot2. Depends is like having
library(pkg)
. Generally, don’t do it. - It’s usually a good idea to state package versions after their name in Imports. Find out the package name with
packageVersion("pkg")
. Generally onlt do>=
, and not==
and never<
Now we are going to discuss how to add tests to your package. This helps build upon your existing skills and will be useful as it helps ensure your package behaves are you expect.