Skip to content

Propose a faster version of check_installed() #1776

@shikokuchuo

Description

@shikokuchuo

This function provides a much better user experience for code using 'suggests' packages over some formulation of
if (requireNamespace("x", quietly = TRUE)).

However, it's much more expensive to use (a full 3 orders of magnitude):

bench::mark(rlang::check_installed("later"), requireNamespace("later", quietly = TRUE), check = FALSE)
#> # A tibble: 2 × 6
#>   expression                             min median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr>                           <bch> <bch:>     <dbl> <bch:byt>    <dbl>
#> 1 "rlang::check_installed(\"later\")"  397µs  443µs     2189.    5.54MB     17.0
#> 2 "requireNamespace(\"later\", quietl… 558ns  613ns  1294407.        0B      0

Created on 2025-02-12 with reprex v2.1.1

In purrr, I employ caching to ensure it's not run time every time a function is called e.g. https://github.com/tidyverse/purrr/blob/main/R/map.R#L214-L217 but I notice in other codebases it's often used unconditionally at the top of a function body (FYI @hadley).

Rather than require the user to implement these optimizations, it might be nice to handle this at source by short-cutting detection if the package is already loaded. This can be easily done using .getNamespace(), a really cheap function which returns either the namespace environment if already loaded or else NULL.

Note that this would be different to current behaviour as check_installed() actually checks if the package is installed on disk (which contributes to the slowness). Hence a question is: do we hide this behind an argument, or would it be cleaner to introduce another function e.g. check_available()?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions