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:

  1. The fundamental message writing will improve your code in many places
  2. Makes it easier to write useful messages to users
  3. It will help make your package look sleek and modern

You are going to modify your function to look like this:

praise <- function(name){
  cli::cli_inform("Hey {name}, You're so awesome!")
}

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::cli_inform

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:

pkg::fun()

For example, if I want to use filter from dplyr, I do so with:

cars |> 
  dplyr::filter(speed <= 4)
  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:

praise <- function(name){
  cli::cli_inform("Hey {name}, You're so awesome!")
}
TipYour Turn: run `devtools::check()
  1. Update your R code to look like the following:
praise <- function(name){
  cli::cli_inform("Hey {name}, You're so awesome!")
}
  1. run: devtools::check()
  2. Identify the error
  3. 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>) andpkg::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()`.
NoteYour turn
  1. Ensure you have cli::cli_inform(...)
  2. 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.

TipHow many dependencies should you have?

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.

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.

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.