Rev 75738 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed
---title: "Search Path Conflicts"author: Luke Tierneyoutput: html_document---## BackgroundHadley Wickham has recently introduced the `conflicted` package. Thegoal is to deal with the problem where multiple packages on the searchpath provide a function with a particular name and the one on topisn't the one you wanted. `conflicted` arranges to signal an errorwhen evaluating a global variable that is defined by multiple packageson the search path, unless an explicit resolution of the conflict hasbeen specified.Being able to ask for stricter handling of conflicts is definitely agood idea, but it seems it would more naturally belong in base R thanin a package. There are several downsides to the package approach, atleast as currently implemented, including being fairly heavy-weight,confusing `find`, and not handling packages that have functions thatdo things like```rfoo <- function(x) { require(bar); ... }```Not that this is a good idea these days, but it's out there.The approach taken in `conflicted` of signaling an error only when asymbol is used might be described as a dynamic approach. This dynamicapproach could be implemented more efficiently in base R by adjustingthe global cache mechanism. However there is an alternate approachworth considering, which is to insist that conflicts be resolved atthe point where the `library` or `require` call occurs. This might becalled a static or declarative approach.To think about these options it is useful to think about a range ofactivities that might need increasing levels of strictness in conflicthandling:0. Interactive work in the console.1. Interactive work in a notebook.2. Code in an `Rmarkdown` or `Sweave` document.3. Scripts.4. Packages.Packages are handled by `NAMESPACES`. The others could use some help.For 2 and 3 the static approach seems clearly better, as long as weprovide the right set of tools to allow it to be expressedconcisely. For notebooks I think the static approach is probablybetter as well. For interactive use it may be a matter of personalpreference. Once I have decided I want help with conflicts, mypreference would be to be forced to resolve them on package loadrather than to have my work flow interrupted at random points to goand sort things out.Another useful distinction is between anticipated an unanticipatedconflicts.- _Anticipated_ conflicts occur when a single package is attached thatin turn causes other packages to be attached. The package authorwill see the messages and should address any ones that are not asintended. These should usually not require user intervention.- _Unanticipated_ conflicts arise when a user requests that twopackages be attached that may not have been designed to be usedtogether. Conflicts in these cases will typically require the userto make an appropriate choice.Ideally a conflict resolution framework should provide the option toonly require intervention for unanticipated conflicts.## ImplementationA basic implementation of the static approach is quite simple:- Give library/require additional arguments `mask.ok` and `exclude`.- `mask.ok`: these functions can mask variables already on the path.- `exclude`: don't add these to the search path frame.- If `getOption("conflicts.policy")` is `"strict"` then signal an error ifthere are conflicts not covered by `mask.ok`.This branch contains modified versions of `library.R` and`namespace.R` that implement this. This produces```roptions(conflicts.policy = "strict")library(dplyr)## conflict error from filter, laglibrary(dplyr, mask.ok = c("filter", "lag","intersect", "setdiff", "setequal", "union"))## OKlibrary(MASS)## conflict error from selectlibrary(MASS, exclude = "select")## OK```## Resolving ConflictsTo make this easier for commonly used packages, and for implicitlyattached packages, `conflictRules` provides a way to specify default`mask.ok`/`exclude` values for packages, e.g. with```rconflictRules("dplyr",mask.ok = c("filter", "lag","intersect", "setdiff", "setequal", "union"))conflictRules("MASS", exclude = "select")```Alternate forms that should all work:```r## mask, no matter where they are:library(dplyr, mask.ok = c("filter", "lag","intersect", "setdiff", "setequal", "union"))## mask only if in stats/base:library(dplyr, mask.ok = list(stats = c("filter", "lag"),base = c("intersect", "setdiff","setequal", "union")))## mask anything in stats/base:library(dplyr, mask.ok = list(base = TRUE, stats = TRUE))## using conflictRules:conflictRules("dplyr", mask.ok = list(base = TRUE, stats = TRUE))conflictRules("MASS", exclude = "select")## to allow Matrix to be loaded with conflict checking:conflictRules("Matrix", mask.ok = list(stats = TRUE,graphics = "image",utils = c("head", "tail"),base = TRUE))## for BiocGenerics:conflictRules("BiocGenerics", mask.ok = list(base = TRUE,stats = TRUE,parallel = TRUE,graphics = c("image", "boxplot"),utils = "relist"))```Some additional features that have been implemented:- `library()` has some heuristics to avoid warning about S4 overridesthat mask the overrides in `Matrix` (but not those in`BiocGenerics`). These heuristics are disabled by default when strictconflict checking is in force so that they have to be handledexplicitly in some way.- Arguments `include.only` and `attach.required` have been added to`library()`. If `only` is supplied as a character vector, then onlyvariables named there are included in the attached frame. Thedefault value of `attach.required` is `TRUE` if `include.only` ismissing and `FALSE` if `include.only` is supplied.- The error signaled with strict checking is of class `packageConflictsError`with fields `package` and `conflicts`.- A number of aspects of the conflict handling process can becustomized via the `conflicts.policy` option. The value of thisoption should be a list with named elements. Elements that are supported:- `warn`: Sets the default for the `warn.conflicts` argument to`library` and `require`.- `error`: If `TRUE` then conflicts produce errors.- `generics.ok`: If set this determines whether masking a functionwith an S4 generic version is considered a conflict. The default in`FALSE` for strict checking and `TRUE` otherwise.- `can.mask`: A character vector of names of packages that areallowed to be masked without producing an error. Specifying thebase packages can reduce the number of explicit maskingapprovals needed.- `depends.ok`: If `TRUE` then allow all conflicts produced withina single package load.A specification that may work for most users who want protectionagainst unanticipated conflicts:```roptions(conflicts.policy = list(error = TRUE,generics.ok = TRUE,can.mask = c("base", "methods", "utils","grDevices", "graphics", "stats"),depends.ok = TRUE))```This specification assumes that package authors know what they aredoing and all conflicts from loading a package by itself areintentional and OK. With this specification all `CRAN` and `BIOC`packages individually load without error.A strict specification would be```roptions(conflicts.policy = list(error = TRUE, warn = FALSE))```These can be specified as```roptions(conflicts.policy = "depends.ok")```or```roptions(conflicts.policy = "strict")```respectively.## Open IssuesAdditional features that might be useful:- Provide a restart for retrying with adjusted arguments; that could be usedby a GUI front end.- Have a way to say that (some) 'safe' S4 or even S3 generics are OKto mask non-generics, at least if the non-generics are used as the defaultmethod by the generics.- Add a function to compute the conflicts that would happen on attach.Might be useful to lift out and expose the code in `checkConflicts`.- Many package authors will be aware of conflicts, in particular conflictswith base packages. Loading the package is intended to mask the defaultvariables, and warnings are false positives. It would be useful forpackages to be able to declare that these maskings are intentional insome way, e.g. in their `DESCRIPTION` files. Packages can already call`conflictRules` in their `.onAttach` functions, which may be sufficientfor now.- Maybe it would be cleaner to merge the `conflicts.policy`functionality into `conflictRules`.Some questions:- Should `attach` also signal an error on conflicts? It repeats someof the checking logic in `library`, but with some differences.- Do we need to disallow using both exclude and `include.only`?- Should 'strict' mode still warn?TODO list:- Add more documentation.- Add some input checking- Need to check whether the `pos` argument raises any issues.- Also whether there are any issues with non-package frames.<!--Local Variables:mode: poly-markdown+Rmode: flyspellEnd:-->