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

StackOverflow https://stackoverflow.com/questions/10099128

  •  30-05-2021
  •  | 
  •  

Question

I'm updating an old package and shortening a bunch of really long function names. How do I let a user know the the old function has been deprecated? I document everything with roxygen2 so I'm wondering if #' @alias is what I should use? Thoughts?

Was it helpful?

Solution

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.

OTHER TIPS

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.

In the case of converting overly long function names to shorter versions, I'd recommend just exporting both names as the same function (see @Brandon's comment). This would allow old code to continue working while offering new users a more convenient alternative.

In my mind, the only reason to tag something as .Deprecated (see @GSEE) would be if you plan to fundamentally change the functionality or stop supporting some feature in a future release. If this is the case, you can use the .Defunct or .Deprecated.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top