box

Write reusable, composable and modular R code

https://github.com/klmr/box

Science Score: 26.0%

This score indicates how likely this project is to be science-related based on various indicators:

  • CITATION.cff file
  • codemeta.json file
    Found codemeta.json file
  • .zenodo.json file
    Found .zenodo.json file
  • DOI references
  • Academic publication links
  • Committers with academic emails
  • Institutional organization owner
  • JOSS paper metadata
  • Scientific vocabulary similarity
    Low similarity (16.9%) to scientific vocabulary

Keywords

modules packages r

Keywords from Contributors

rmarkdown
Last synced: 6 months ago · JSON representation

Repository

Write reusable, composable and modular R code

Basic Info
  • Host: GitHub
  • Owner: klmr
  • License: mit
  • Language: R
  • Default Branch: main
  • Homepage: https://klmr.me/box/
  • Size: 5.22 MB
Statistics
  • Stars: 920
  • Watchers: 19
  • Forks: 48
  • Open Issues: 68
  • Releases: 12
Topics
modules packages r
Created almost 13 years ago · Last pushed 12 months ago
Metadata Files
Readme Changelog Contributing License Code of conduct

README.md

box

Write Reusable, Composable and Modular R Code

CRAN status badge R-universe status badge

* [Get started][] * [Documentation][] * [Contributing][] * [Frequently asked questions][FAQ]

📦 Installation

‘box’ can be installed from CRAN:

r install.packages('box')

Alternatively, the current development version can be installed from R-universe (note that it cannot be installed directly from GitHub!):

r install.packages('box', repos = 'https://klmr.r-universe.dev')

🥜 Usage in a nutshell

‘box’ allows organising R code in a more modular way, via two mechanisms:

  1. It enables writing modular code by treating files and folders of R code as independent (potentially nested) modules, without requiring the user to wrap reusable code into packages.
  2. It provides a new syntax to import reusable code (both from packages and modules) that is more powerful and less error-prone than library by allowing explicit control over what names to import, and by restricting the scope of the import.

Reusable code modules

Code doesn’t have to be wrapped into an R package to be reusable. With ‘box’, regular R files are reusable R modules that can be used elsewhere. Just put the export directive #' @export in front of names that should be exported, e.g.:

```r

' @export

hello = function (name) { message('Hello, ', name, '!') }

' @export

bye = function (name) { message('Goodbye ', name, '!') } ```

Existing R scripts without @export directives can also be used as modules. In that case, all names inside the file will be exported, unless they start with a dot (.).

Such modules can be stored in a central module search path (configured via options('box.path')) analogous to the R package library, or locally in individual projects. Let’s assume the module we just defined is stored in a file hello_world.r inside a directory mod, which is inside the module search path. Then the following code imports and uses it:

```r box::use(mod/hello_world)

hello_world$hello('Ross')

> Hello, Ross!

```

Modules are a lot like packages. But they are easier to write and use (often without requiring any set-up), and they offer some other nice features that set them apart from packages (such as the ability to be nested hierarchically).

For more information on writing modules refer to the Get started vignette.

Loading code

box::use provides a universal import declaration. It works for packages just as well as for modules. In fact, ‘box’ completely replaces the base R library and require functions. box::use is more explicit, more flexible, and less error-prone than library. At its simplest, it provides a direct replacement:

Instead of

r library(ggplot2)

You’d write

r box::use(ggplot2[...])

This tells R to import the ‘ggplot2’ package, and to make all its exported names available (i.e. to “attach” them) — just like library. For this purpose, ... acts as a wildcard to denote “all exported names”. However, attaching everything is generally discouraged (hence why it needs to be done explicitly rather than happening implicitly), since it leads to name clashes, and makes it harder to retrace which names belong to what packages.

Instead, we can also instruct box::use to not attach any names when loading a package — or to just attach a few. Or we can tell it to attach names under an alias, and we can also give the package itself an alias.

The following box::use declaration illustrates these different cases:

r box::use( purrr, # 1 tbl = tibble, # 2 dplyr = dplyr[filter, select], # 3 stats[st_filter = filter, ...] # 4 )

Users of Python, JavaScript, Rust and many other programming languages will find this use declaration familiar (even if the syntax differs):

The code

  1. imports the package ‘purrr’ (but does not attach any of its names);
  2. creates an alias tbl for the imported ‘tibble’ package (but does not attach any of its names);
  3. imports the package ‘dplyr’ and additionally attaches the names dplyr::filter and dplyr::select; and
  4. attaches all exported names from ‘stats’, but uses the local alias st_filter for the name stats::filter.

Of the four packages loaded in the code above, only ‘purrr’, ‘tibble’ and ‘dplyr’ are made available by name (as purrr, tbl and dplyr, respectively), and we can use their exports via the $ operator, e.g. purrr$map or tbl$glimpse. Although we’ve also loaded ‘stats’, we did not create a local name for the package itself, we only attached its exported names.

Thanks to aliases, we can safely use functions with the same name from multiple packages without conflict: in the above, st_filter refers to the filter function from the ‘stats’ package; by contrast, plain filter refers to the ‘dplyr’ function. Alternatively, we could also explicitly qualify the package alias, and write dplyr$filter.

Furthermore, unlike with library, the effects of box::use are restricted to the current scope: we can load and attach names inside a function, and this will not affect the calling scope (or elsewhere). So importing code happens locally, and functions which load packages no longer cause global side effects:

``r log = function (msg) { box::use(glue[glue]) # We can now useglue` inside the function: message(glue('[LOG MESSAGE] {msg}')) }

log('test')

> [LOG MESSAGE] test

… But glue remains undefined in the outer scope:

glue('test')

> Error in glue("test"): could not find function "glue"

```

This makes it easy to encapsulate code with external dependencies without creating unintentional, far-reaching side effects.

‘box’ itself is never loaded via library. Instead, its functionality is always used explicitly via box::use.

Getting help

If you encounter a bug or have a feature request, please post an issue report on GitHub. For general questions, posting on Stack Overflow, tagged as r-box, is also an option. Finally, there’s a GitHub Discussions board at your disposal.

Why ‘box’?

‘box’ makes it drastically easier to write reusable code: instead of needing to create a package, each R code file is already a module which can be imported using box::use. Modules can also be nested inside directories, such that self-contained projects can be easily split into separate or interdependent submodules.

To make code reuse more scalable for larger projects, ‘box’ promotes the opposite philosophy of what’s common in R: some notable packages export and attach many hundreds and, in at least one notable case, over a thousand names. This works adequately for small-ish analysis scripts but breaks down for even moderately large software projects because it makes it non-obvious where names are imported from, and increases the risk of name clashes.

To make code more explicit, readable and maintainable, software engineering best practices encourage limiting both the scope of names, as well as the number of names available in each scope.

For instance, best practice in Python is to never use the equivalent of library(pkg) (i.e. from pkg import *). Instead, Python strongly encourages using import pkg or from pkg import a, few, symbols, which correspond to box::use(pkg) and box::use(pkg[a, few, symbols]), respectively. The same is true in many other languages, e.g. C++, Rust and Perl. Some languages (e.g. JavaScript) are even stricter: they don’t support unqualified wildcard imports at all.

The Zen of Python puts this rule succinctly:

Explicit is better than implicit.

Owner

  • Name: Konrad Rudolph
  • Login: klmr
  • Kind: user
  • Location: Basel, CH
  • Company: @Roche

Geneticist 🧬, computer scientist 𝝺 and software engineer 👨‍💻.

GitHub Events

Total
  • Issues event: 6
  • Watch event: 50
  • Issue comment event: 3
  • Push event: 2
  • Pull request event: 2
  • Fork event: 3
Last Year
  • Issues event: 6
  • Watch event: 50
  • Issue comment event: 3
  • Push event: 2
  • Pull request event: 2
  • Fork event: 3

Committers

Last synced: over 2 years ago

All Time
  • Total Commits: 1,039
  • Total Committers: 6
  • Avg Commits per committer: 173.167
  • Development Distribution Score (DDS): 0.013
Past Year
  • Commits: 36
  • Committers: 1
  • Avg Commits per committer: 36.0
  • Development Distribution Score (DDS): 0.0
Top Committers
Name Email Commits
Konrad Rudolph k****h@g****m 1,025
mschubert m****v@g****m 10
Kamil Zyla 7****a@g****m 1
Konrad Rudolph k****h@m****t 1
Pedro Rafael p****o@g****m 1
Jim Hester j****r@g****m 1
Committer Domains (Top 20 + Academic)

Issues and Pull Requests

Last synced: 6 months ago

All Time
  • Total issues: 124
  • Total pull requests: 20
  • Average time to close issues: 6 months
  • Average time to close pull requests: 3 days
  • Total issue authors: 59
  • Total pull request authors: 6
  • Average comments per issue: 2.4
  • Average comments per pull request: 1.4
  • Merged pull requests: 11
  • Bot issues: 0
  • Bot pull requests: 0
Past Year
  • Issues: 7
  • Pull requests: 4
  • Average time to close issues: about 6 hours
  • Average time to close pull requests: 9 days
  • Issue authors: 6
  • Pull request authors: 1
  • Average comments per issue: 0.71
  • Average comments per pull request: 0.0
  • Merged pull requests: 1
  • Bot issues: 0
  • Bot pull requests: 0
Top Authors
Issue Authors
  • klmr (36)
  • dereckdemezquita (5)
  • grst (4)
  • kamilzyla (4)
  • GregYannes (3)
  • mmuurr (3)
  • mrismailt (3)
  • mbojan (3)
  • ggrothendieck (3)
  • jtlandis (2)
  • dereckmezquita (2)
  • GitHunter0 (2)
  • ArcadeAntics (2)
  • mlell (2)
  • thothal (2)
Pull Request Authors
  • klmr (12)
  • tommarshall2 (4)
  • radbasa (2)
  • jtlandis (1)
  • owenjonesuob (1)
  • ArcadeAntics (1)
Top Labels
Issue Labels
⚠️ bug (52) ✨ new feature (41) ⚙️ enhancement (17) 💬 discussion (16) 🧹 maintenance (6) 👥 duplicate (5) 🛠 work in progress (3) 🙅 invalid (3) 📚 documentation (2) 🚨 breaking (2) 🙋 FAQ (2) 📜 needs-reprex (1) 🎯 by design (1) 💔 won’t fix (1) 🚀 ready (1)
Pull Request Labels
🧹 maintenance (1) 🛠 work in progress (1) ✨ new feature (1)

Packages

  • Total packages: 2
  • Total downloads:
    • cran 1,947 last-month
  • Total docker downloads: 289
  • Total dependent packages: 6
    (may contain duplicates)
  • Total dependent repositories: 3
    (may contain duplicates)
  • Total versions: 16
  • Total maintainers: 1
proxy.golang.org: github.com/klmr/box
  • Versions: 8
  • Dependent Packages: 0
  • Dependent Repositories: 0
Rankings
Dependent packages count: 6.5%
Average: 6.7%
Dependent repos count: 7.0%
Last synced: 6 months ago
cran.r-project.org: box

Write Reusable, Composable and Modular R Code

  • Versions: 8
  • Dependent Packages: 6
  • Dependent Repositories: 3
  • Downloads: 1,947 Last month
  • Docker Downloads: 289
Rankings
Stargazers count: 0.4%
Forks count: 1.8%
Dependent packages count: 8.2%
Downloads: 9.7%
Average: 10.6%
Dependent repos count: 16.5%
Docker downloads count: 27.3%
Maintainers (1)
Last synced: 6 months ago

Dependencies

DESCRIPTION cran
  • R >= 3.5.0 depends
  • rstudioapi * enhances
  • tools * imports
  • R6 * suggests
  • devtools * suggests
  • knitr * suggests
  • rlang * suggests
  • rmarkdown * suggests
  • roxygen2 >= 7.0.2 suggests
  • shiny * suggests
  • stringr * suggests
  • testthat >= 3.0.3 suggests
.github/workflows/build.yaml actions
  • actions/cache v2 composite
  • actions/checkout v2 composite
  • r-lib/actions/setup-r v1 composite
.github/workflows/check.yaml actions
  • actions/checkout v3 composite
  • r-lib/actions/check-r-package v2 composite
  • r-lib/actions/setup-pandoc v2 composite
  • r-lib/actions/setup-r v2 composite
  • r-lib/actions/setup-r-dependencies v2 composite