From 0876f7d5a895edd6308361add89051aab6cb11d8 Mon Sep 17 00:00:00 2001 From: Hugo Gruson <10783929+Bisaloo@users.noreply.github.com> Date: Wed, 4 Dec 2024 15:57:43 +0100 Subject: [PATCH] Fix linting error for missing alt text By replacing the screenshot by text --- .../s3-generic/index/execute-results/html.json | 4 ++-- posts/s3-generic/error_trace.png | Bin 21936 -> 0 bytes posts/s3-generic/index.qmd | 15 ++++++++++++--- 3 files changed, 14 insertions(+), 5 deletions(-) delete mode 100644 posts/s3-generic/error_trace.png diff --git a/_freeze/posts/s3-generic/index/execute-results/html.json b/_freeze/posts/s3-generic/index/execute-results/html.json index 603c3855..332d92e1 100644 --- a/_freeze/posts/s3-generic/index/execute-results/html.json +++ b/_freeze/posts/s3-generic/index/execute-results/html.json @@ -1,8 +1,8 @@ { - "hash": "a7adb40d82c3a70a9c5373d2db0aafe1", + "hash": "15b56e83f806626a59144920db1cadad", "result": { "engine": "knitr", - "markdown": "---\ntitle: \"Convert Your R Function to an S3 Generic: Benefits, Pitfalls & Design Considerations\"\nauthor:\n - name: \"Hugo Gruson\"\n orcid: \"0000-0002-4094-1476\"\ndate: \"2023-02-20\"\ncategories: [R, R package, object orientation, S3, interoperability]\nformat:\n html: \n toc: true\n---\n\n\nTo build a tight and well-integrated data pipeline, it may be desirable to rely on [object orientation (OO)](https://en.wikipedia.org/wiki/Object-oriented_programming) to automatically pass valuable information from one step to the other. OO and data classes can also act as a compatibility layer standardising outputs from various tools under a common structure.\n\nBut many packages and software start as standalone projects, and don't always stem from a careful consideration of the larger ecosystem. In this situation, developers often see little benefit of using an OO system in their project initially.\n\nBut as the project matures, and as the position of the tool in the wider ecosystem becomes clearer, they may want to start using OO to benefit from the better integration it may provide with other tools upstream and downstream in the data pipeline.\nHowever, by then, their tool likely has an established community of users, and it is important to tread carefully with breaking changes.\n\nIn this blog post, we show that it's possible to start using an S3 OO system almost invisibly in your R package, with minimal disruption to your users. We detail some minor changes that will nonetheless occur, and which pitfalls you should be looking out for. Finally, we take a step back and reflect how you should ensure you are a good open-source citizen in this endeavour.\n\n## Benefits\n\nLet's reuse the example function from [one of our previous posts](../statistical-correctness/):\n\n\n::: {.cell}\n\n```{.r .cell-code}\n#' @export\ncentroid <- function(coords, weights) {\n\n # ...\n\n}\n```\n:::\n\n\nSince we wrote and released this function, someone may have designed a clever data class to store coordinates of a set of points and their weights. Let's imagine they use the following class that they call `pointset`:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nexample_pointset <- structure(\n list(\n coords = list(c(0, 1, 5, 3), c(8, 6, 4, 3), c(10, 2, 3, 7)),\n weights = c(1, 1, 1, 1)\n ),\n class = \"pointset\"\n)\n```\n:::\n\n\nThey may also have developed nice utilities for this class so there is a clear motivation for you to integrate with their class since it's less work you'll have to do. Plus, you immediately become compatible with any package that uses the same class.\n\nWe will not spend too much time on the practical steps to operate this conversion since this is already covered in details in [the dedicated chapter of Advanced R, by Hadley Wickham](https://adv-r.hadley.nz/s3.html), as well as [this blog post from Nick Tierney](https://njtierney.github.io/r/missing%20data/rbloggers/2016/11/06/simple-s3-methods/) [^S3]. But the final result would be:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n#' Compute the centroid of a set of points\n#'\n#' @param coords Coordinates of the points as a list of vectors. Each element of\n#' the list is a point.\n#' @param weights Vector of weights applied to each of the points\n#'\n#' @returns A vector of coordinates of the same length of each element of \n#' `coords`\n#' \n#' @examples\n#' centroid(\n#' list(c(0, 1, 5, 3), c(8, 6, 4, 3), c(10, 2, 3, 7)),\n#' weights = c(1, 1, 1)\n#' )\n#' \n#' @export\ncentroid <- function(coords, weights) {\n\n UseMethod(\"centroid\") \n\n}\n\n#' @rdname centroid\n#' \n#' @export\ncentroid.default <- function(coords, weights) {\n\n # ...\n\n}\n\n#' @rdname centroid\n#' \n#' @export\ncentroid.pointset <- function(coords, weights = NULL) {\n\n centroid(coords$coords, coords$weights)\n\n}\n```\n:::\n\n\n[^S3]: Note that we focus here on the S3 framework but R has other object orientation frameworks, as discussed in [the relevant section of the 'Advanced R' book by Hadley Wickham](https://adv-r.hadley.nz/oo.html)\n\n## What subtle changes should you be looking out for?\n\nYou may already have noticed a couple of minor changes in the example above but some changes are even less evident and easy to forget, hence this blog post.\n\n### All methods must have the same arguments as the generic\n\nYou can see that the method for `pointset` class, `centroid.pointset()` has a `weights` argument, even though it is not used because weights are already contained in the `coords` object.\nThis seems clunky and potentially confusing for users.\nBut this is mandatory because all methods must have the same arguments as the generic.\n\nAnother option here could have been to remove `weights` from the generic, and add `...` instead, thus allowing to pass `weights` as an extra argument only in selected methods. This is more idiomatic in R, and in line with the [recommendation from the official 'Writing R Extensions' document (\"always keep generics simple\")](https://cran.r-project.org/doc/manuals/R-exts.html#Generic-functions-and-methods):\n\n\n::: {.cell}\n\n```{.r .cell-code}\n#' @export\ncentroid <- function(coords, ...) { \n UseMethod(\"centroid\") \n}\n\n#' @rdname centroid\n#' \n#' @export\ncentroid.default <- function(coords, weights, ...) {\n\n coords_mat <- do.call(rbind, coords)\n \n return(apply(coords_mat, 2, weighted.mean, w = weights))\n \n}\n```\n:::\n\n\nBut this extra `...` argument, which is documented as \"ignored\", may be confusing as well.\n\n### More complex documentation presentation\n\nOn the topic of arguments, another pitfall related to the conversion to an S3 generic is the change in the documentation. Below is a collage of before / after the change. This is quite minor and some users may not even notice it but I remember it was very confusing to me when I started using R and I didn't really know what S3 or OO was: \"what do you mean, 'Default S3 method', which case applies to me?\"\n\n::: {layout-ncol=2 layout-valign=\"bottom\"}\n![Screenshot of the `centroid()` documentation before conversion to an S3 generic](before_conversion.png)\n\n![Screenshot of the `centroid()` documentation after conversion to an S3 generic](after_conversion.png)\n:::\n\nThe answer is that \"Default S3 method\" lists the arguments for `centroid.default()`, i.e., the method which is used if no other method is defined for your class.\nArguments for all methods are usually documented together but you should only focus on those present in the call after the comment stating \"S3 method for class 'XXX'\" for the class you're working with.\n\n### More complicated error traceback\n\nAnother situation where converting to an S3 adds an extra layer of complexity is where you are trying to follow the error [traceback](https://en.wikipedia.org/wiki/Stack_trace):\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncentroid(3)\n```\n:::\n\n\n![](error_trace.png)\n\nIn this example, we see one extra line that did not exist when `centroid()` was a regular function, rather than a generic:\n\n> centroid.default(3) at centroid.R#19\n\nThis line corresponds to the dispatch operation.\n\nHowever, this slight difference in behaviour is likely not a big issue as we mostly expect experienced users to interact with the traceback. These users are likely to be familiar with S3 dispatch and understand the traceback in any case.\n\n### Extra source of bugs during dispatch\n\nOn a related note, the extra step introduced by this conversion to generic is another potential source of bugs. This doesn't really impact your users directly but it does mean that as a developer, you will maintaining slightly more complex code and you will need to be more careful when making any changes.\nHowever, as always, a robust testing suite should help you catch any error before it makes it to production.\n\n## Where should the generic & methods live?\n\nIn the previous section, we mentioned that you may want to rely on existing, established S3 classes. How does it work in practice when you want to add a method for a class outside of your package? Do you need to import the package where the class is defined?\nOn the other side of the fence, as a class developer, is it okay to provide methods for generics provided in other packages?\nIf you have the choice, should the method live in the package defining the generic or the class?\n\n### Where should the generic live?\n\nThe generic should always live in the package implementing the actual computation in the function in the first place. For example, if you defined the original `centroid()` function in a package called geometryops, the S3 generic should also be defined in that package, not in the package defining the `pointset` class.\n\nIt is possible in theory to overwrite a function defined by another package with a generic (\"overloading\"). For example, we could overload base R `table()` function with:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntable <- function(...) { \n UseMethod(...)\n}\n\ntable.default <- function(\n ...,\n exclude = if (useNA == \"no\") c(NA, NaN),\n useNA = c(\"no\", \"ifany\", \"always\"),\n dnn = list.names(...), deparse.level = 1\n) {\n\n base::table(\n ...,\n exclude = exclude,\n useNA = useNA,\n dnn = dnn\n )\n\n}\n```\n:::\n\n\nBut this is generally considered bad practice, and possibly rude [^1]. As a rule of thumb, you should usually avoid:\n\n- name collisions with functions from other packages (especially base or recommended package);\n- light wrappers around a function from another package as this may be seen as an attempt to steal citations and credit.\n\n[^1]: Every rule has its exceptions though such as the [generics](https://generics.r-lib.org/) package, built by prominent members of the R developer community, which overloads base R functions such as `as.factor()` or `as.difftime()`.\n\n### Where should the methods live?\n\nFor methods, there is more flexibility than for generics. They could either in the package defining the class, or in the package defining the generic. Let's present the practical setup in both cases, as well as each strategy pros & cons.\n\n#### Method in the class package\n\nThis is the strategy used when you defined a new class and provide it with a `print()`, a `summary()`, or a `plot()` method. The generics for these functions are defined in R base.\n\n\n::: {.cell}\n\n```{.r .cell-code}\n#' @export\nplot.myclass <- function(x, y, ...) {\n \n # code for a beautiful plot for your custom class\n \n}\n```\n:::\n\n\nIf you opt for this strategy, you will need to depend on the package providing the method, as `Imports`. For example, a package defining a `fit.myclass()` method for the `fit()` generic defined in the [generics](https://generics.r-lib.org/) package would have the following `DESCRIPTION` and `NAMESPACE`.\n\n```{.yml filename=\"DESCRIPTION\"}\nImports:\n generics\n```\n\n```{.r filename=\"fit.myclass.R\"}\n#' @export\n#' @importFrom generics fit\nfit.myclass <- function(x, ...) {\n # your code here\n}\n```\n\n```{.r filename=NAMESPACE}\n# Generated by roxygen2: do not edit by hand\n\nS3method(fit,myclass)\nimportFrom(generics,fit)\n```\n\n::: {.callout-important}\n\n##### Importing the generic\n\nIt's worth insisting that you need to import the generic in your `NAMESPACE` for the method to be recognized and exported correctly by roxygen2. In this specific situation, simply explicitly prefixing the generic call (`generic::fit()`) is not enough.\n\n:::\n\nBut this can lead to a [rapid increase in the number of dependencies](https://www.mail-archive.com/r-package-devel@r-project.org/msg02720.html) if you provide methods for generics from various packages.\nSince R 3.6, you can also put generics in `Suggests` and [use delayed assignment](https://roxygen2.r-lib.org/articles/namespace.html#s3-methods-for-generics-in-suggested-packages):\n\n```{.yml filename=\"DESCRIPTION\"}\nSuggests:\n generics\n```\n\n```{.r filename=\"fit.myclass.R\"}\n#' @exportS3Method generics::fit\nfit.myclass <- function(x, ...) {\n # your code here\n}\n```\n\n```{.r filename=NAMESPACE}\n# Generated by roxygen2: do not edit by hand\n\nS3method(generics::fit,myclass)\n```\n\n#### Method in the generic package\n\nAlternatively, you can define the method in the package defining the generic. This is the approach taken in the [report package](https://easystats.github.io/report/) from example, which defines the `report()` generic and methods for various model outputs produced by different package.\n\nIn theory, no `Imports` or `Suggests` is required here:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n#' @export\nmygeneric <- function(x, ...) { \n UseMethod(x)\n}\n\n#' @export\nmygeneric.externalclass <- function(x, ...) {\n # your code here\n}\n```\n:::\n\n\nHowever, if you end up providing many methods for a specific class, you could put the package defining it in the uncommon `Enhances` field. `Enhances` is defined in '[Writing R Extensions](https://cran.r-project.org/doc/manuals/r-release/R-exts.html)' as:\n\n> The ‘Enhances’ field lists packages “enhanced” by the package at hand, e.g., by providing methods for classes from these packages.\n\nIt may be a good idea to explicitly signal the strong relationship between both packages so that the package defining the method is checked as a reverse dependency, and informed of potential breaking changes as discussed below.\nYou may see an example of this in the [slam package](https://cran.r-project.org/package=slam), which provides his methods for both base matrices and sparse matrices, as defined in the Matrix and the spam packages.\n\n#### Coordination between maintainers\n\nNo matter the strategy you end up choosing, we strongly recommend you keep an open communication channel between the class package and the generic package developer (provided they are not the same person) as breaking changes will impact both parties.\n\n## Conclusion\n\nAs we've seen here, there are clear benefits to converting your standard function to an S3 generic. This can be done **almost** transparently but we've highlighting some subtle changes you may want to consider before pulling the switch.\n\n::: {.callout-tip}\n\n### Spreading the S3 love\n\nIf you like S3 and find it helpful to convert your function to an S3 class, you should keep propagating the S3 love by also adding an S3 class to your function output.\n\nWith this in mind, in the very first example where we converted our `centroid()` function to an S3 generic to handle `pointset` objects, we could also make our output a `pointset` object.\n:::\n", + "markdown": "---\ntitle: \"Convert Your R Function to an S3 Generic: Benefits, Pitfalls & Design Considerations\"\nauthor:\n - name: \"Hugo Gruson\"\n orcid: \"0000-0002-4094-1476\"\ndate: \"2023-02-20\"\ncategories: [R, R package, object-orientated programming, S3, interoperability]\nformat:\n html: \n toc: true\n---\n\n\n\nTo build a tight and well-integrated data pipeline, it may be desirable to rely on [object orientation (OO)](https://en.wikipedia.org/wiki/Object-oriented_programming) to automatically pass valuable information from one step to the other. OO and data classes can also act as a compatibility layer standardising outputs from various tools under a common structure.\n\nBut many packages and software start as standalone projects, and don't always stem from a careful consideration of the larger ecosystem. In this situation, developers often see little benefit of using an OO system in their project initially.\n\nBut as the project matures, and as the position of the tool in the wider ecosystem becomes clearer, they may want to start using OO to benefit from the better integration it may provide with other tools upstream and downstream in the data pipeline.\nHowever, by then, their tool likely has an established community of users, and it is important to tread carefully with breaking changes.\n\nIn this blog post, we show that it's possible to start using an S3 OO system almost invisibly in your R package, with minimal disruption to your users. We detail some minor changes that will nonetheless occur, and which pitfalls you should be looking out for. Finally, we take a step back and reflect how you should ensure you are a good open-source citizen in this endeavour.\n\n## Benefits\n\nLet's reuse the example function from [one of our previous posts](../statistical-correctness/):\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\n#' @export\ncentroid <- function(coords, weights) {\n\n # ...\n\n}\n```\n:::\n\n\n\nSince we wrote and released this function, someone may have designed a clever data class to store coordinates of a set of points and their weights. Let's imagine they use the following class that they call `pointset`:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nexample_pointset <- structure(\n list(\n coords = list(c(0, 1, 5, 3), c(8, 6, 4, 3), c(10, 2, 3, 7)),\n weights = c(1, 1, 1, 1)\n ),\n class = \"pointset\"\n)\n```\n:::\n\n\n\nThey may also have developed nice utilities for this class so there is a clear motivation for you to integrate with their class since it's less work you'll have to do. Plus, you immediately become compatible with any package that uses the same class.\n\nWe will not spend too much time on the practical steps to operate this conversion since this is already covered in details in [the dedicated chapter of Advanced R, by Hadley Wickham](https://adv-r.hadley.nz/s3.html), as well as [this blog post from Nick Tierney](https://njtierney.github.io/r/missing%20data/rbloggers/2016/11/06/simple-s3-methods/) [^S3]. But the final result would be:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\n#' Compute the centroid of a set of points\n#'\n#' @param coords Coordinates of the points as a list of vectors. Each element of\n#' the list is a point.\n#' @param weights Vector of weights applied to each of the points\n#'\n#' @returns A vector of coordinates of the same length of each element of \n#' `coords`\n#' \n#' @examples\n#' centroid(\n#' list(c(0, 1, 5, 3), c(8, 6, 4, 3), c(10, 2, 3, 7)),\n#' weights = c(1, 1, 1)\n#' )\n#' \n#' @export\ncentroid <- function(coords, weights) {\n\n UseMethod(\"centroid\") \n\n}\n\n#' @rdname centroid\n#' \n#' @export\ncentroid.default <- function(coords, weights) {\n\n # ...\n\n}\n\n#' @rdname centroid\n#' \n#' @export\ncentroid.pointset <- function(coords, weights = NULL) {\n\n centroid(coords$coords, coords$weights)\n\n}\n```\n:::\n\n\n\n[^S3]: Note that we focus here on the S3 framework but R has other object orientation frameworks, as discussed in [the relevant section of the 'Advanced R' book by Hadley Wickham](https://adv-r.hadley.nz/oo.html)\n\n## What subtle changes should you be looking out for?\n\nYou may already have noticed a couple of minor changes in the example above but some changes are even less evident and easy to forget, hence this blog post.\n\n### All methods must have the same arguments as the generic\n\nYou can see that the method for `pointset` class, `centroid.pointset()` has a `weights` argument, even though it is not used because weights are already contained in the `coords` object.\nThis seems clunky and potentially confusing for users.\nBut this is mandatory because all methods must have the same arguments as the generic.\n\nAnother option here could have been to remove `weights` from the generic, and add `...` instead, thus allowing to pass `weights` as an extra argument only in selected methods. This is more idiomatic in R, and in line with the [recommendation from the official 'Writing R Extensions' document (\"always keep generics simple\")](https://cran.r-project.org/doc/manuals/R-exts.html#Generic-functions-and-methods):\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\n#' @export\ncentroid <- function(coords, ...) { \n UseMethod(\"centroid\") \n}\n\n#' @rdname centroid\n#' \n#' @export\ncentroid.default <- function(coords, weights, ...) {\n\n coords_mat <- do.call(rbind, coords)\n \n return(apply(coords_mat, 2, weighted.mean, w = weights))\n \n}\n```\n:::\n\n\n\nBut this extra `...` argument, which is documented as \"ignored\", may be confusing as well.\n\n### More complex documentation presentation\n\nOn the topic of arguments, another pitfall related to the conversion to an S3 generic is the change in the documentation. Below is a collage of before / after the change. This is quite minor and some users may not even notice it but I remember it was very confusing to me when I started using R and I didn't really know what S3 or OO was: \"what do you mean, 'Default S3 method', which case applies to me?\"\n\n::: {layout-ncol=2 layout-valign=\"bottom\"}\n![Screenshot of the `centroid()` documentation before conversion to an S3 generic](before_conversion.png)\n\n![Screenshot of the `centroid()` documentation after conversion to an S3 generic](after_conversion.png)\n:::\n\nThe answer is that \"Default S3 method\" lists the arguments for `centroid.default()`, i.e., the method which is used if no other method is defined for your class.\nArguments for all methods are usually documented together but you should only focus on those present in the call after the comment stating \"S3 method for class 'XXX'\" for the class you're working with.\n\n### More complicated error traceback\n\nAnother situation where converting to an S3 adds an extra layer of complexity is where you are trying to follow the error [traceback](https://en.wikipedia.org/wiki/Stack_trace):\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncentroid(3)\n```\n\n::: {.cell-output .cell-output-error}\n\n```\nError in do.call(rbind, coords): second argument must be a list\n```\n\n\n:::\n:::\n\n\n\nThe traceback for the chunk above is:\n\n> ```\n> 4: stop(\"second argument must be a list\")\n> 3: do.call(rbind, coords) at #3\n> 2: centroid.default(3) at #2\n> 1: centroid(3)\n> ```\n\nIn this example, we see one extra line that did not exist when `centroid()` was a regular function, rather than a generic:\n\n> ```\n> centroid.default(3) at #2\n> ```\n\nThis line corresponds to the dispatch operation.\n\nHowever, this slight difference in behaviour is likely not a big issue as we mostly expect experienced users to interact with the traceback. These users are likely to be familiar with S3 dispatch and understand the traceback in any case.\n\n### Extra source of bugs during dispatch\n\nOn a related note, the extra step introduced by this conversion to generic is another potential source of bugs. This doesn't really impact your users directly but it does mean that as a developer, you will maintaining slightly more complex code and you will need to be more careful when making any changes.\nHowever, as always, a robust testing suite should help you catch any error before it makes it to production.\n\n## Where should the generic & methods live?\n\nIn the previous section, we mentioned that you may want to rely on existing, established S3 classes. How does it work in practice when you want to add a method for a class outside of your package? Do you need to import the package where the class is defined?\nOn the other side of the fence, as a class developer, is it okay to provide methods for generics provided in other packages?\nIf you have the choice, should the method live in the package defining the generic or the class?\n\n### Where should the generic live?\n\nThe generic should always live in the package implementing the actual computation in the function in the first place. For example, if you defined the original `centroid()` function in a package called geometryops, the S3 generic should also be defined in that package, not in the package defining the `pointset` class.\n\nIt is possible in theory to overwrite a function defined by another package with a generic (\"overloading\"). For example, we could overload base R `table()` function with:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntable <- function(...) { \n UseMethod(...)\n}\n\ntable.default <- function(\n ...,\n exclude = if (useNA == \"no\") c(NA, NaN),\n useNA = c(\"no\", \"ifany\", \"always\"),\n dnn = list.names(...), deparse.level = 1\n) {\n\n base::table(\n ...,\n exclude = exclude,\n useNA = useNA,\n dnn = dnn\n )\n\n}\n```\n:::\n\n\n\nBut this is generally considered bad practice, and possibly rude [^1]. As a rule of thumb, you should usually avoid:\n\n- name collisions with functions from other packages (especially base or recommended package);\n- light wrappers around a function from another package as this may be seen as an attempt to steal citations and credit.\n\n[^1]: Every rule has its exceptions though such as the [generics](https://generics.r-lib.org/) package, built by prominent members of the R developer community, which overloads base R functions such as `as.factor()` or `as.difftime()`.\n\n### Where should the methods live?\n\nFor methods, there is more flexibility than for generics. They could either in the package defining the class, or in the package defining the generic. Let's present the practical setup in both cases, as well as each strategy pros & cons.\n\n#### Method in the class package\n\nThis is the strategy used when you defined a new class and provide it with a `print()`, a `summary()`, or a `plot()` method. The generics for these functions are defined in R base.\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\n#' @export\nplot.myclass <- function(x, y, ...) {\n \n # code for a beautiful plot for your custom class\n \n}\n```\n:::\n\n\n\nIf you opt for this strategy, you will need to depend on the package providing the method, as `Imports`. For example, a package defining a `fit.myclass()` method for the `fit()` generic defined in the [generics](https://generics.r-lib.org/) package would have the following `DESCRIPTION` and `NAMESPACE`.\n\n```{.yml filename=\"DESCRIPTION\"}\nImports:\n generics\n```\n\n```{.r filename=\"fit.myclass.R\"}\n#' @export\n#' @importFrom generics fit\nfit.myclass <- function(x, ...) {\n # your code here\n}\n```\n\n```{.r filename=NAMESPACE}\n# Generated by roxygen2: do not edit by hand\n\nS3method(fit,myclass)\nimportFrom(generics,fit)\n```\n\n::: {.callout-important}\n\n##### Importing the generic\n\nIt's worth insisting that you need to import the generic in your `NAMESPACE` for the method to be recognized and exported correctly by roxygen2. In this specific situation, simply explicitly prefixing the generic call (`generic::fit()`) is not enough.\n\n:::\n\nBut this can lead to a [rapid increase in the number of dependencies](https://www.mail-archive.com/r-package-devel@r-project.org/msg02720.html) if you provide methods for generics from various packages.\nSince R 3.6, you can also put generics in `Suggests` and [use delayed assignment](https://roxygen2.r-lib.org/articles/namespace.html#s3-methods-for-generics-in-suggested-packages):\n\n```{.yml filename=\"DESCRIPTION\"}\nSuggests:\n generics\n```\n\n```{.r filename=\"fit.myclass.R\"}\n#' @exportS3Method generics::fit\nfit.myclass <- function(x, ...) {\n # your code here\n}\n```\n\n```{.r filename=NAMESPACE}\n# Generated by roxygen2: do not edit by hand\n\nS3method(generics::fit,myclass)\n```\n\n#### Method in the generic package\n\nAlternatively, you can define the method in the package defining the generic. This is the approach taken in the [report package](https://easystats.github.io/report/) from example, which defines the `report()` generic and methods for various model outputs produced by different package.\n\nIn theory, no `Imports` or `Suggests` is required here:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\n#' @export\nmygeneric <- function(x, ...) { \n UseMethod(x)\n}\n\n#' @export\nmygeneric.externalclass <- function(x, ...) {\n # your code here\n}\n```\n:::\n\n\n\nHowever, if you end up providing many methods for a specific class, you could put the package defining it in the uncommon `Enhances` field. `Enhances` is defined in '[Writing R Extensions](https://cran.r-project.org/doc/manuals/r-release/R-exts.html)' as:\n\n> The ‘Enhances’ field lists packages “enhanced” by the package at hand, e.g., by providing methods for classes from these packages.\n\nIt may be a good idea to explicitly signal the strong relationship between both packages so that the package defining the method is checked as a reverse dependency, and informed of potential breaking changes as discussed below.\nYou may see an example of this in the [slam package](https://cran.r-project.org/package=slam), which provides his methods for both base matrices and sparse matrices, as defined in the Matrix and the spam packages.\n\n#### Coordination between maintainers\n\nNo matter the strategy you end up choosing, we strongly recommend you keep an open communication channel between the class package and the generic package developer (provided they are not the same person) as breaking changes will impact both parties.\n\n## Conclusion\n\nAs we've seen here, there are clear benefits to converting your standard function to an S3 generic. This can be done **almost** transparently but we've highlighting some subtle changes you may want to consider before pulling the switch.\n\n::: {.callout-tip}\n\n### Spreading the S3 love\n\nIf you like S3 and find it helpful to convert your function to an S3 class, you should keep propagating the S3 love by also adding an S3 class to your function output.\n\nWith this in mind, in the very first example where we converted our `centroid()` function to an S3 generic to handle `pointset` objects, we could also make our output a `pointset` object.\n:::\n", "supporting": [], "filters": [ "rmarkdown/pagebreak.lua" diff --git a/posts/s3-generic/error_trace.png b/posts/s3-generic/error_trace.png deleted file mode 100644 index f48aefc9e4006926d9c0be0acb56aed2941e451f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21936 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 diff --git a/posts/s3-generic/index.qmd b/posts/s3-generic/index.qmd index 04920180..1d1a0527 100644 --- a/posts/s3-generic/index.qmd +++ b/posts/s3-generic/index.qmd @@ -141,15 +141,24 @@ Arguments for all methods are usually documented together but you should only fo Another situation where converting to an S3 adds an extra layer of complexity is where you are trying to follow the error [traceback](https://en.wikipedia.org/wiki/Stack_trace): -```{r, eval = FALSE} +```{r, error = TRUE} centroid(3) ``` -![](error_trace.png) +The traceback for the chunk above is: + +> ``` +> 4: stop("second argument must be a list") +> 3: do.call(rbind, coords) at #3 +> 2: centroid.default(3) at #2 +> 1: centroid(3) +> ``` In this example, we see one extra line that did not exist when `centroid()` was a regular function, rather than a generic: -> centroid.default(3) at centroid.R#19 +> ``` +> centroid.default(3) at #2 +> ``` This line corresponds to the dispatch operation.