My R package ale implements a get()
method. Normally, this would conflict with the base R base::get()
function, but I implement it such that it calls my package's custom get() methods when the user passes one of my package's objects but for everything else, it transparently hands over to base::get()
so that there is no problem. So far, so good.
However, when a package is attached with library() R automatically prints a conflict message to alert users to the potential problem:
# To reproduce this issue, you need to install the current
# development version of the package at this specific commit:
# # install.packages('pak')
# pak::pak('tripartio/ale@e0ff702')
#
library(ale)
#>
#> Attaching package: 'ale'
#> The following object is masked from 'package:base':
#>
#> get
Created on 2025-03-03 with reprex v2.1.1
This is fine and responsible on R's part (my question is NOT about trying to suppress this message--I leave that to the user's choice). However, I have added a clarifying package startup message to reassure users that there is no problem. I have the following .onAttach()
function in my aaa.R
file:
# My actual .onAttach() is more complicated, but I simplify here
# to focus on the key issues
.onAttach <- function(libname, pkgname) {
packageStartupMessage("The 'get()' function for 'ale' works such that the masked base::get() still works fine without modification.")
}
The result when users load the package now is:
# Note: you must first restart R before each time you run library(ale) to see what any changes produce
library(ale)
#> The 'get()' function for 'ale' works such that the masked base::get() still works fine without modification.
#>
#> Attaching package: 'ale'
#> The following object is masked from 'package:base':
#>
#> get
Created on 2025-03-03 with reprex v2.1.1
This is what I want, except I want to print my custom message AFTER R's conflict message, not before. I've tried many things but I have not been able to successfully flip the order. For example, here are some things I've tried that have NOT worked:
# Delay the message using setHook()
.onAttach <- function(libname, pkgname) {
setHook(
packageEvent(pkgname, "attach"),
function(...) {
packageStartupMessage("The 'get()' function for 'ale' works such that the masked base::get() still works fine without modification.")
}
)
}
# Use defer() from {withr}
.onAttach <- function(libname, pkgname) {
withr::defer({
packageStartupMessage("The 'get()' function for 'ale' works such that the masked base::get() still works fine without modification.")
}, envir = parent.env(environment()))
}
# Sys.sleep() before my custom message:
# 5 seconds delay but same order
.onAttach <- function(libname, pkgname) {
Sys.sleep(5)
packageStartupMessage("The 'get()' function for 'ale' works such that the masked base::get() still works fine without modification.")
}
# .onLoad() instead of .onAttach():
# No change--same order
.onLoad <- function(libname, pkgname) {
setHook(
packageEvent(pkgname, "attach"),
function(...) {
packageStartupMessage("The 'get()' function for 'ale' works such that the masked base::get() still works fine without modification.")
}
)
}
Here is the output that I want; I appreciate any help in getting me there:
# Note: you must first restart R before each time you run library(ale) to see what any changes produce
library(ale)
#>
#> Attaching package: 'ale'
#> The following object is masked from 'package:base':
#>
#> get
#>
#> The 'get()' function for 'ale' works such that the masked base::get() still works fine without modification.
You could try to take a creative approach by using R's task callback system to print the packageStartupMessage after .onAttach
ran. I am writing this answer because @Tripartio asked me to do so in the comments and this turned out to be working.
addTaskCallback
registers an R function that is to be called each time a top-level task is completed.
If the (return)value is FALSE, the callback is removed from the task list and will not be called again by this mechanism
.onAttach <- function(libname, pkgname) {
# Schedule the message to appear after the current task is complete
invisible(
addTaskCallback(function(expr, value, ok, visible) {
packageStartupMessage("\nNote: The 'get()' function for 'ale' works such that the masked base::get() still works fine without modification.")
return(FALSE) # to remove this callback after it runs once
})
)
}
DISCLAIMER:
I found out, that all startup messages need to be suppressable to fit CRAN guidelines. I do not know, if this solution fits these guidelines, but since this also outputs a type packageStartupMessage
you should be fine. Source