Are there best/recommended practices to follow when renaming functions in a new version of a package?

Even though you are just shortening function names, I would still treat it with the same fanfare as any change to the public API of the package: with deprecation/defunct stages to the old functions as the new functions are brought in.

In the first phase, for each function you want to shorten the name of (let's call it transmute_my_carefully_crafted_data_structure_into_gold), you keep a function with that signature, but move all the actual code into your newly named function (let's call it alchemy).

Initially:

transmute_my_carefully_crafted_data_structure_into_gold <- function(lead, alpha=NULL, beta=3) {
  # TODO: figure out how to create gold
  # look like we are doing something
  Sys.sleep(10)
  return("gold")
}

First release with new names:

transmute_my_carefully_crafted_data_structure_into_gold <- function(lead, alpha=NULL, beta=3) {
  .Deprecated("alchemy") #include a package argument, too
  alchemy(lead=lead, alpha=alpha, beta=beta)
}

alchemy <- function(lead, alpha=NULL, beta=3) {
  # TODO: figure out how to create gold
  # look like we are doing something
  Sys.sleep(10)
  return("gold")
}

So that transmute_my_carefully_crafted_data_structure_into_gold starts as a thin wrapper around alchemy, with an additional .Deprecated call.

> transmute_my_carefully_crafted_data_structure_into_gold()
[1] "gold"
Warning message:
'transmute_my_carefully_crafted_data_structure_into_gold' is deprecated.
Use 'alchemy' instead.
See help("Deprecated") 
> alchemy()
[1] "gold"

If you make changes to alchemy, it is still carried by transmute_my_carefully_crafted_data_structure_into_gold since that just calls the former. However, you don't change the signature of transmute_my_carefully_crafted_data_structure_into_gold even if alchemy does; in that case you need to map, as well as possible, the old arguments into the new arguments.

In a later release, you can change .Deprecated to .Defunct.

> transmute_my_carefully_crafted_data_structure_into_gold()
Error: 'transmute_my_carefully_crafted_data_structure_into_gold' is defunct.
Use 'alchemy' instead.
See help("Defunct")

Note that this is an error and stops; it does not go ahead and call alchemy.

You could, in some later release, delete this function entirely, but I'd leave it in this state as a signpost.

You mentioned using using roxygen. When you make the first transition to deprecated, you can change the @rdname to package-deprecated, add a line at the beginning of the description saying it is deprecated, add the new function to the @seealso. When it changes to defunct, change the @rdname to package-defunct.


I guess the "right" answer depends on what you want. From my view:

  1. The problem with Jeff's and Brandon's approach is that your index will list both function names and give no indication as to which is the preferred name. Moreover without some sort of .Deprecated call, the user is even more unlikely to know what the preferred way to call the function is.
  2. The problem with Brian's approach was that the process for listing more than one function as deprecated was unclear to me.

So, enter my example below. In another location I define the 'good' versions of the functions (e.g. alchemy, latinSquareDigram). Here I define all of the old 'bad' versions that I want to produce deprecation warnings for. I followed the approach of the car package and changed all of my function calls for the deprecated version to use ... as the argument. This has helped me avoid a bunch of cluttered @param statements. I also have used the @name and @docType directives to make "yourPackageName-deprecated" appear in the index. Maybe somebody has a better way of doing this?

Now each of the deprecated functions still shows up in the index, but it says "Deprecated function(s) in the yourPackageName package" next to them and any calls to them produce a deprecation warning. To remove them from the index one could drop the @aliases directive, but then you would have user-level undocumented code objects which, I take it, is bad form.

#' Deprecated function(s) in the yourPackageName package
#' 
#' These functions are provided for compatibility with older version of
#' the yourPackageName package.  They may eventually be completely
#' removed.
#' @rdname yourPackageName-deprecated
#' @name yourPackageName-deprecated
#' @param ... Parameters to be passed to the modern version of the function
#' @docType package
#' @export  latinsquare.digram Conv3Dto2D Conv2Dto3D dist3D.l
#' @aliases latinsquare.digram Conv3Dto2D Conv2Dto3D dist3D.l
#' @section Details:
#' \tabular{rl}{
#'   \code{latinsquare.digram} \tab now a synonym for \code{\link{latinSquareDigram}}\cr
#'   \code{Conv3Dto2D} \tab now a synonym for \code{\link{conv3Dto2D}}\cr
#'   \code{Conv2Dto3D} \tab now a synonym for \code{\link{conv2Dto3D}}\cr
#'   \code{dist3D.l} \tab now a synonym for \code{\link{dist3D}}\cr
#' }
#'  
latinsquare.digram <- function(...) {
  .Deprecated("latinSquareDigram",package="yourPackageName")
  latinSquareDigram(...)
}
Conv3Dto2D <- function(...) {
  .Deprecated("conv3Dto2D",package="yourPackageName")
  conv3Dto2D(...)
}
Conv2Dto3D <- function(...) {
  .Deprecated("conv2Dto3D",package="yourPackageName")
  conv2Dto3D(...)
}
dist3D.l <- function(...) {
  .Deprecated("dist3D",package="yourPackageName")
  dist3D(...)
}
NULL

I had this problem for some time and was unable to find a good solution. Then I found this. Still, the above answers are overly complicated for the simple case where one just wants to: 1) add an alias so that older code doesn't stop working, 2) the alias must work with the built-in documentation, and 3) it should be done with roxygen2.

First, add a copy of the function:

old_function_name = new_function_name

Then, where new_function_name() is defined, add to the roxygen2:

#' @export new_function_name old_function_name
#' @aliases old_function_name

Now the old function works because it is just a copy of the new function, and the documentation works because you set up the alias. The old version is also exported because it is included in the @export.

Tags:

R

Roxygen