Background

Hadley Wickham has recently introduced the conflicted package. The goal is to deal with the problem where multiple packages on the search path provide a function with a particular name and the one on top isn’t the one you wanted. conflicted arranges to signal an error when evaluating a global variable that is defined by multiple packages on the search path, unless an explicit resolution of the conflict has been specified.

Being able to ask for stricter handling of conflicts is definitely a good idea, but it seems it would more naturally belong in base R than in a package. There are several downsides to the package approach, at least as currently implemented, including being fairly heavy-weight, confusing find, and not handling packages that have functions that do things like

foo <- 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 a symbol is used might be described as a dynamic approach. This dynamic approach could be implemented more efficiently in base R by adjusting the global cache mechanism. However there is an alternate approach worth considering, which is to insist that conflicts be resolved at the point where the library or require call occurs. This might be called a static or declarative approach.

To think about these options it is useful to think about a range of activities that might need increasing levels of strictness in conflict handling:

  1. Interactive work in the console.
  2. Interactive work in a notebook.
  3. Code in an Rmarkdown or Sweave document.
  4. Scripts.
  5. 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 we provide the right set of tools to allow it to be expressed concisely. For notebooks I think the static approach is probably better as well. For interactive use it may be a matter of personal preference. Once I have decided I want help with conflicts, my preference would be to be forced to resolve them on package load rather than to have my work flow interrupted at random points to go and sort things out.

Another useful distinction is between anticipated an unanticipated conflicts.

Ideally a conflict resolution framework should provide the option to only require intervention for unanticipated conflicts.

Implementation

A basic implementation of the static approach is quite simple:

This branch contains modified versions of library.R and namespace.R that implement this. This produces

options(conflicts.policy = "strict")
library(dplyr)
## conflict error from filter, lag

library(dplyr, mask.ok = c("filter", "lag",
                           "intersect", "setdiff", "setequal", "union"))
## OK

library(MASS)
## conflict error from select

library(MASS, exclude = "select")
## OK

Resolving Conflicts

To make this easier for commonly used packages, and for implicitly attached packages, conflictRules provides a way to specify default mask.ok/exclude values for packages, e.g. with

conflictRules("dplyr",
              mask.ok = c("filter", "lag",
                          "intersect", "setdiff", "setequal", "union"))
conflictRules("MASS", exclude = "select")

Alternate forms that should all work:

## 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:

A specification that may work for most users who want protection against unanticipated conflicts:

options(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 are doing and all conflicts from loading a package by itself are intentional and OK. With this specification all CRAN and BIOC packages individually load without error.

A strict specification would be

options(conflicts.policy = list(error = TRUE, warn = FALSE))

These can be specified as

options(conflicts.policy = "depends.ok")

or

options(conflicts.policy = "strict")

respectively.

Open Issues

Additional features that might be useful:

Some questions:

TODO list: