functionals
Functional programming in R with built-in support for parallelism and progress bars
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
-
○Academic email domains
-
○Institutional organization owner
-
○JOSS paper metadata
-
○Scientific vocabulary similarity
Low similarity (8.5%) to scientific vocabulary
Keywords
functional-programming
Last synced: 6 months ago
·
JSON representation
Repository
Functional programming in R with built-in support for parallelism and progress bars
Basic Info
Statistics
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
- Releases: 1
Topics
functional-programming
Created 9 months ago
· Last pushed 7 months ago
Metadata Files
Readme
License
README.Rmd
---
output: github_document
---
```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
fig.path = "man/figures/README-",
out.width = "100%"
)
```
# functionals: Functional mapping with parallelism and progress bars
## Overview
`functionals` is a lightweight toolkit for functional programming in R with built-in support for parallelism and progress bars. It extends base R's functional tools with a consistent, minimal API for mapping, walking, reducing, cross-validating, and repeating computations across lists, data frames, and grouped data.
## Function Reference Table
| Function | Main arguments | Output type | Description |
|--------------|----------------------------------------|-------------|-----------------------------------------------------------------|
| `fmap()` | `.x`, `.f`, `ncores`, `pb` | list | Map `.f` over elements of `.x` |
| `fmapn()` | `.l`, `.f`, `ncores`, `pb` | list | Map `.f` over multiple aligned lists |
| `fmapr()` | `.df`, `.f`, `ncores`, `pb` | list | Map `.f` over each row of a data frame (as named list) |
| `fmapc()` | `.df`, `.f`, `ncores`, `pb` | list | Map `.f(column, name)` over each column |
| `fmapg()` | `.df`, `.f`, `by`, `ncores`, `pb` | list | Map `.f(group_df)` over groups defined by a column |
| `floop()` | `.x`, `.f`, `...`, `ncores`, `pb` | list | General-purpose functional loop with side-effects |
| `fwalk()` | `.x`, `.f`, `ncores`, `pb` | NULL | Map `.f` over `.x` for side-effects only (invisible return) |
| `frepeat()` | `times`, `expr`, `.x`, `ncores`, `pb` | list/vector | Repeat a call/expression multiple times |
| `fcv()` | `.splits`, `.f`, `ncores`, `pb` | list | Map `.f` over resampling splits from `rsample::vfold_cv()` |
| `freduce()` | `.x`, `.f`, `...` | scalar/list | Reduce `.x` using a binary function `.f` |
| `fcompose()` | any number of functions `f1, f2, ...` | function | Compose multiple functions: `f1(f2(...(x)))` |
| `fapply()` | `.x`, `.f`, `ncores`, `pb`, `...` | list | Core internal utility for applying a function over `.x` |
## Syntax Equivalence
| Task | `functionals` Example | `purrr` Example | Base R |
|--------------------------|------------------------------------------------------------|----------------------------------------------------|--------------------------------------------|
| Map square | `fmap(1:5, function(x) x^2)` | `map(1:5, function(x) x^2)` | `lapply(1:5, function(x) x^2)` |
| Map over N arguments | `fmapn(list(1:3, 4:6, 7:9), function(x, y, z) x + y + z)` | `pmap(list(1:3, 4:6, 7:9), function(x, y, z) ...)` | `Map(function(x, y, z) ..., 1:3, 4:6, 7:9)` |
| Map over data frame rows | `fmapr(df, function(row) row$a + row$b)` | `pmap(df[c("a", "b")], function(x, y) x + y)` | `apply(df, 1, function(row) ...)` |
| Map over data frame cols | `fmapc(df, function(x, name) mean(x))` | `imap(df, function(x, name) mean(x))` | `lapply(df, mean)` |
| Grouped map | `fmapg(df, f, by = "group")` | `map(split(df, df$group), f)` | `lapply(split(df, df$group), f)` |
| General-purpose loop | `floop(1:3, function(x) cat(x))` | *(manual recursion)* | `for (x in 1:3) cat(x)` |
| Parallel + progress | `fmap(x, f, ncores = 4, pb = TRUE)` | *(future_map(x, f))* with `progressr` | `parLapply(cl, x, f)` or `mclapply()` |
| Repeat simulation | `frepeat(100, function() rnorm(1))` | *(manual loop)* | `replicate(100, rnorm(1))` |
| Walk with side effects | `fwalk(letters, function(x) cat(x))` | `walk(letters, function(x) cat(x))` | `lapply(letters, cat)` |
| Reduce | ``freduce(1:5, `+`)`` | ``reduce(1:5, `+`)`` | ``Reduce(`+`, 1:5)`` |
| Compose functions | `fcompose(sqrt, abs)(-4)` | `compose(sqrt, abs)(-4)` | `(function(x) sqrt(abs(x)))(-4)` |
## Why no formula interface like `~ .x + .y`?
While `functionals` draws inspiration from `purrr`, it intentionally avoids supporting the formula-based anonymous function syntax (e.g., `~ .x + 1`) for now.
This decision is based on:
- Keeping dependencies minimal (no reliance on `rlang`)
- Avoiding non-standard evaluation that can confuse new users
- Encouraging explicit, readable code using `function(x) { ... }` style
We may consider adding tidy evaluation support (e.g., with quosures or `rlang::as_function`) in a future release. However, the current philosophy favors clarity and simplicity.
## Installation
```{r, eval=FALSE}
# from CRAN
install.packages("functionals")
# from github
remotes::install_github("ielbadisy/functionals")
```
## Examples
```{r}
library(functionals)
library(purrr)
library(furrr)
library(pbapply)
library(dplyr)
library(rsample)
library(bench)
plan(multisession)
# utility to compare results
compare_outputs <- function(label, x, y) {
cat("\n", label, "->", if (identical(x, y)) "dentical\n" else if (isTRUE(all.equal(x, y))) "nearly equal\n" else "different\n")
}
# strip names and convert to plain numeric vector
as_vec <- function(x) as.numeric(unlist(x, use.names = FALSE))
```
### Element-wise map
```{r}
x1 <- fmap(1:5, function(x) x^2)
x2 <- lapply(1:5, function(x) x^2)
x3 <- map(1:5, ~ .x^2)
x4 <- future_map(1:5, ~ .x^2)
x5 <- pblapply(1:5, function(x) x^2)
compare_outputs("Element-wise: base", x1, x2)
compare_outputs("Element-wise: purrr", x1, x3)
compare_outputs("Element-wise: furrr", x1, x4)
compare_outputs("Element-wise: pbapply", x1, x5)
```
## Multi-input map
```{r}
x1 <- fmapn(list(1:3, 4:6), function(x, y) x + y)
x2 <- Map(`+`, 1:3, 4:6)
x3 <- pmap(list(1:3, 4:6), ~ ..1 + ..2)
x4 <- future_pmap(list(1:3, 4:6), ~ ..1 + ..2)
compare_outputs("Multi-input: base", x1, x2)
compare_outputs("Multi-input: purrr", x1, x3)
compare_outputs("Multi-input: furrr", x1, x4)
```
### Row-wise map
```{r}
x1 <- fmapr(mtcars, function(row) row$mpg + row$cyl)
rowlist <- lapply(seq_len(nrow(mtcars)), function(i) as.list(mtcars[i, ]))
x2 <- lapply(rowlist, function(row) row$mpg + row$cyl)
x3 <- map(rowlist, function(row) row$mpg + row$cyl)
compare_outputs("Row-wise: base", as_vec(x1), as_vec(x2))
compare_outputs("Row-wise: purrr", as_vec(x1), as_vec(x3))
```
### Column-wise map
```{r}
x1 <- fmapc(mtcars, function(col, name) mean(col))
x2 <- sapply(mtcars, mean)
x3 <- imap(mtcars, ~ mean(.x))
x4 <- future_imap(mtcars, ~ mean(.x))
compare_outputs("Column-wise: base", x1, as.list(x2))
compare_outputs("Column-wise: purrr", x1, x3)
compare_outputs("Column-wise: furrr", x1, x4)
```
### Group-wise map
```{r}
x1 <- fmapg(iris, function(df) colMeans(df[1:4]), by = "Species")
x2 <- lapply(split(iris, iris$Species), function(df) colMeans(df[1:4]))
x3 <- map(split(iris, iris$Species), ~ colMeans(.x[1:4]))
x4 <- future_map(split(iris, iris$Species), ~ colMeans(.x[1:4]))
compare_outputs("Group-wise: base", x1, x2)
compare_outputs("Group-wise: purrr", x1, x3)
compare_outputs("Group-wise: furrr", x1, x4)
```
### Side-effect map
```{r}
cat("\nSide-effects:\n")
fwalk(1:3, print)
```
### General-purpose loop with return values
```{r}
x1 <- floop(1:5, function(x) x^2, .capture = TRUE)
x2 <- lapply(1:5, function(x) x^2)
x3 <- {
out <- list()
for (i in 1:5) out[[i]] <- i^2
out
}
compare_outputs("floop() vs lapply()", x1, x2)
compare_outputs("floop() vs for()", x1, x3)
```
### General-purpose loop (side-effect only)
```{r}
cat("\nGeneral-purpose loop (side-effects):\n")
floop(1:3, function(x) cat("floop says:", x, "\n"), pb = TRUE, .capture = FALSE)
cat("for-loop equivalent:\n")
for (x in 1:3) cat("for says:", x, "\n")
```
### Cross-validation
```{r}
splits <- vfold_cv(iris, v = 3)$splits
fit_model <- function(split) mean(analysis(split)$Sepal.Length)
x1 <- fcv(splits, fit_model)
x2 <- lapply(splits, fit_model)
compare_outputs("CV map: base", x1, x2)
```
### Repeat simulation
```{r}
x1 <- frepeat(times = 10, expr = rnorm(1))
x2 <- as.list(replicate(10, rnorm(1)))
x3 <- as.list(pbreplicate(10, rnorm(1)))
cat("\nRepeat: Results not comparable (randomized output)\n")
```
### Reduce
```{r}
x1 <- freduce(1:5, `+`)
x2 <- Reduce(`+`, 1:5)
x3 <- reduce(1:5, `+`)
compare_outputs("Reduce: base", x1, x2)
compare_outputs("Reduce: purrr", x1, x3)
```
### Compose
```{r}
x1 <- fcompose(sqrt, abs)(-4)
x2 <- (function(x) sqrt(abs(x)))(-4)
x3 <- compose(sqrt, abs)(-4)
compare_outputs("Compose: base", x1, x2)
compare_outputs("Compose: purrr", x1, x3)
```
Owner
- Name: Imad EL BADISY
- Login: ielbadisy
- Kind: user
- Location: Brest - France
- Company: IMT Atlantique
- Repositories: 3
- Profile: https://github.com/ielbadisy
Research Engineer in Applied Statistics
GitHub Events
Total
- Watch event: 1
- Push event: 9
Last Year
- Watch event: 1
- Push event: 9
Packages
- Total packages: 1
-
Total downloads:
- cran 186 last-month
- Total dependent packages: 0
- Total dependent repositories: 0
- Total versions: 1
- Total maintainers: 1
cran.r-project.org: functionals
Functional Programming with Parallelism and Progress Tracking
- Documentation: http://cran.r-project.org/web/packages/functionals/functionals.pdf
- License: MIT + file LICENSE
-
Latest release: 0.5.0
published 7 months ago
Rankings
Dependent packages count: 25.9%
Dependent repos count: 31.8%
Average: 47.8%
Downloads: 85.6%
Maintainers (1)
Last synced:
6 months ago
Dependencies
DESCRIPTION
cran
- bench * suggests
- furrr * suggests
- future * suggests
- rsample * suggests
- testthat >= 3.0.0 suggests