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

4

43

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?

Millais answered 11/4, 2012 at 2:39 Comment(3)
see ?DeprecatedLarock
@alias only allows the user to find documentation of the function through ?help. It's better to define both inline and export both the old and the new. some_func <- new_func_name <- function(x) and use the @alias directive. You'll need to @export some_func new_fund_name if you want them to both be in the namespace. See ?namespace_roclet. To me it doesn't sound like you're changing the function, only changing the name - so ?Deprecated doesn't really seem to apply.Bevon
Thanks so much @BrandonBertelsen I'd suggest you post it as an answer so it is useful to others.Millais
R
56

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.

Realgar answered 13/4, 2012 at 17:39 Comment(1)
Thanks for taking the time to write this great response. Cheers.Millais
B
23

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
Buchanan answered 26/12, 2012 at 5:57 Comment(2)
@mathematical.coffee: Thanks! Probably because I provided my answer 8 months after the bounty winner. :) I tried the above advice and wasn't happy with the result. So, when I finally got a result I was happy with I posted my answer.Buchanan
I found the bounty winner helpful, but this definitely adds to it. I needed that extra bit of roxygen2 love.Kaif
M
5

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.

Miletus answered 10/11, 2015 at 17:4 Comment(1)
This seems the most legit answer imo. It is intuitively what I would have gone for. Maybe it is better to add also some comment about the deprecation cycle going on under the hood, as we want to get rid of the old function name at some point in time.Devorahdevore
Q
3

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.

Qulllon answered 11/4, 2012 at 15:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.