functionals

Functional programming in R with built-in support for parallelism and progress bars

https://github.com/ielbadisy/functionals

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
  • Host: GitHub
  • Owner: ielbadisy
  • License: other
  • Language: R
  • Default Branch: main
  • Homepage:
  • Size: 3.27 MB
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

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

  • Versions: 1
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Downloads: 186 Last month
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