├── .Rbuildignore ├── LICENSE ├── .gitignore ├── NEWS.md ├── NAMESPACE ├── man ├── filtering.Rd ├── grouping.Rd ├── mutating.Rd ├── renaming.Rd ├── arranging.Rd ├── selecting.Rd ├── ungrouping.Rd ├── summarizing.Rd ├── currify_verb.Rd ├── grapes-.-grapes.Rd └── grapes-semi-colon-grapes.Rd ├── DESCRIPTION ├── LICENSE.md ├── README.md └── R └── currr.R /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^LICENSE\.md$ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2023 2 | COPYRIGHT HOLDER: Michael DeCrescenzo 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .Rdata 4 | .httr-oauth 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # currr 0.0.0.9000 2 | 3 | * Added a `NEWS.md` file to track changes to the package. 4 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export("%.%") 4 | export("%;%") 5 | export(arranging) 6 | export(currify_verb) 7 | export(filtering) 8 | export(grouping) 9 | export(mutating) 10 | export(renaming) 11 | export(selecting) 12 | export(summarizing) 13 | export(ungrouping) 14 | -------------------------------------------------------------------------------- /man/filtering.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/currr.R 3 | \name{filtering} 4 | \alias{filtering} 5 | \title{Curryable dplyr::filter} 6 | \usage{ 7 | filtering(...) 8 | } 9 | \description{ 10 | Pass all arguments except .data to create a new function of .data. 11 | } 12 | -------------------------------------------------------------------------------- /man/grouping.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/currr.R 3 | \name{grouping} 4 | \alias{grouping} 5 | \title{Curryable dplyr::group_by} 6 | \usage{ 7 | grouping(...) 8 | } 9 | \description{ 10 | Pass all arguments except .data to create a new function of .data. 11 | } 12 | -------------------------------------------------------------------------------- /man/mutating.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/currr.R 3 | \name{mutating} 4 | \alias{mutating} 5 | \title{Curryable dplyr::mutate} 6 | \usage{ 7 | mutating(...) 8 | } 9 | \description{ 10 | Pass all arguments except .data to create a new function of .data. 11 | } 12 | -------------------------------------------------------------------------------- /man/renaming.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/currr.R 3 | \name{renaming} 4 | \alias{renaming} 5 | \title{Curryable dplyr::rename} 6 | \usage{ 7 | renaming(...) 8 | } 9 | \description{ 10 | Pass all arguments except .data to create a new function of .data. 11 | } 12 | -------------------------------------------------------------------------------- /man/arranging.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/currr.R 3 | \name{arranging} 4 | \alias{arranging} 5 | \title{Curryable dplyr::arrange} 6 | \usage{ 7 | arranging(...) 8 | } 9 | \description{ 10 | Pass all arguments except .data to create a new function of .data. 11 | } 12 | -------------------------------------------------------------------------------- /man/selecting.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/currr.R 3 | \name{selecting} 4 | \alias{selecting} 5 | \title{Curryable dplyr::select.} 6 | \usage{ 7 | selecting(...) 8 | } 9 | \description{ 10 | Pass all arguments except .data to create a new function of .data. 11 | } 12 | -------------------------------------------------------------------------------- /man/ungrouping.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/currr.R 3 | \name{ungrouping} 4 | \alias{ungrouping} 5 | \title{Curryable dplyr::ungroup} 6 | \usage{ 7 | ungrouping(...) 8 | } 9 | \description{ 10 | Pass all arguments except .data to create a new function of .data. 11 | } 12 | -------------------------------------------------------------------------------- /man/summarizing.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/currr.R 3 | \name{summarizing} 4 | \alias{summarizing} 5 | \title{Curryable dplyr::summarize} 6 | \usage{ 7 | summarizing(...) 8 | } 9 | \description{ 10 | Pass all arguments except .data to create a new function of .data. 11 | } 12 | -------------------------------------------------------------------------------- /man/currify_verb.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/currr.R 3 | \name{currify_verb} 4 | \alias{currify_verb} 5 | \title{Create a curryable verb.} 6 | \usage{ 7 | currify_verb(verb) 8 | } 9 | \arguments{ 10 | \item{verb}{A function that takes .data and args (...)} 11 | } 12 | \description{ 13 | currify_verb(foo) creates a function of dots (...). 14 | Passing arguments to this function in turn creates a new function foo(.data, ...) 15 | } 16 | \examples{ 17 | filtering = currify_verb(dplyr::filter) 18 | } 19 | -------------------------------------------------------------------------------- /man/grapes-.-grapes.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/currr.R 3 | \name{\%.\%} 4 | \alias{\%.\%} 5 | \title{Function composition operator.} 6 | \usage{ 7 | g \%.\% f 8 | } 9 | \arguments{ 10 | \item{g}{A function} 11 | 12 | \item{f}{A function} 13 | } 14 | \description{ 15 | \code{g \%.\% f} creates a function equivalent to g(f(x)) for some input \code{x}. 16 | Meant to emulate infix notation for function composition, $f . g$. 17 | } 18 | \examples{ 19 | (length \%.\% unique)(c(1, 1, 2, 2, 3)) 20 | } 21 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: currr 2 | Title: Curried Functional Interfaces Atop `dplyr` 3 | Version: 0.0.0.9000 4 | Authors@R: 5 | person("Michael", "DeCrescenzo", , "mgdecrescenzo@gmail.com", role = c("aut", "cre")) 6 | Description: 7 | `currr` provides a deferred-evaluation interface for `dplyr` operations. 8 | `currr` functions create curried versions of `dplyr` functions that specify arguments but omit data. 9 | This allows data-manipulation operations to be composed as functions, without the need to provide data immediately. 10 | License: MIT + file LICENSE 11 | Encoding: UTF-8 12 | Roxygen: list(markdown = TRUE) 13 | RoxygenNote: 7.2.3 14 | Imports: 15 | dplyr, 16 | memoise, 17 | purrr 18 | -------------------------------------------------------------------------------- /man/grapes-semi-colon-grapes.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/currr.R 3 | \name{\%;\%} 4 | \alias{\%;\%} 5 | \title{Postfix function composition operator.} 6 | \usage{ 7 | f \%;\% g 8 | } 9 | \arguments{ 10 | \item{f}{A function} 11 | 12 | \item{g}{A function} 13 | } 14 | \description{ 15 | \code{f \%;\% g} creates a function equivalent to g(f(x)) (or \code{x |> f() |> g()}) for some input \code{x}. 16 | Unlike conventional function composition, which evaluates right to left, postfix notation operates left to right. 17 | This is similar to the direction of the pipe operator \verb{|>}, except the postfix operator creates a function (without evaluating it). 18 | } 19 | \examples{ 20 | (unique \%;\% length)(c(1, 1, 2, 2, 3)) 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2023 Michael DeCrescenzo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # currr 3 | 4 | 5 | 6 | 7 | `currr` provides a deferred-evaluation interface for `dplyr` operations. 8 | `currr` functions create curried versions of `dplyr` verbs; the user provides all arguments except for the data frame. 9 | This allows data-manipulation operations to be composed _as functions_, without the need to provide data up front. 10 | 11 | 12 | ## Installation 13 | 14 | ``` r 15 | devtools::install_github("mikedecr/currr") 16 | ``` 17 | 18 | ## Example 19 | 20 | A demonstration of `currr` by contrasting with `dplyr`. 21 | 22 | ```r 23 | library(currr) 24 | library(dplyr) 25 | ``` 26 | 27 | How to filter with `dplyr`: we need the data (`mtcars`) and the arguments (`mpg == max(mpg)`) when we call `filter`. 28 | 29 | ```{r} 30 | # filter mpg == max(mpg) 31 | # with dplyr, data and function are combined 32 | mtcars |> filter(mpg == max(mpg)) 33 | ``` 34 | 35 | How to filter with `currr`: `filtering` only requires the arguments (`mpg == max(mpg)`). 36 | This creates a _curried_ `filter` function that fixes the arguments. 37 | We can then call the curried function any time later by passing the data (`mtcars`). 38 | 39 | ```{r} 40 | # with currr, function is separated from the data 41 | flt_max_mpg = filtering(mpg == max(mpg)) 42 | flt_max_mpg(mtcars) 43 | ``` 44 | 45 | 46 | The benefit of `currr` becomes more apparent when functions are composed. 47 | 48 | With `dplyr`, if I want to apply the same filter step on a _grouped_ data frame, I have to type all of my `filter` code again. 49 | 50 | ```{r} 51 | # filter max mpg, by am 52 | # with dplyr: must rewrite the filter step 53 | mtcars |> group_by(am) |> filter(mpg == max(mpg)) 54 | ``` 55 | 56 | But with `currr`, I already wrote a filter function. 57 | I can recycle that function by composing it with another function (`grouping`). 58 | 59 | ```{r} 60 | # with currr, pre-defined functions are reusable and composable 61 | by_am = grouping(am) 62 | (by_am %;% flt_max_mpg)(mtcars) 63 | ``` 64 | 65 | As my data analysis code grows more complex, the value of pre-defining small functions pays greater rewards. 66 | 67 | **`currr` functions produces _memoized_ functions of data frames**, which cache values according to their inputs. 68 | If a curried function has already computed its result on a data frame, passing the same data frame returns the cached value instead of recomputing it. 69 | This lets us achieve the same efficiency of storing "intermediate data frames" without actually needing to manage those intermediate objects. 70 | 71 | -------------------------------------------------------------------------------- /R/currr.R: -------------------------------------------------------------------------------- 1 | 2 | #' Create a curryable verb. 3 | #' 4 | #' currify_verb(foo) creates a function of dots (...). 5 | #' Passing arguments to this function in turn creates a new function foo(.data, ...) 6 | #' @param verb A function that takes .data and args (...) 7 | #' @examples 8 | #' filtering = currify_verb(dplyr::filter) 9 | #' @export 10 | currify_verb <- function(verb) { 11 | function(...) { 12 | intention <- function(.data) { 13 | return(purrr::partial(verb)(.data, ...)) 14 | } 15 | memoise::memoise(intention) 16 | } 17 | } 18 | 19 | #' Function composition operator. 20 | #' 21 | #' `g %.% f` creates a function equivalent to g(f(x)) for some input `x`. 22 | #' Meant to emulate infix notation for function composition, $f . g$. 23 | #' @param g A function 24 | #' @param f A function 25 | #' @examples 26 | #' (length %.% unique)(c(1, 1, 2, 2, 3)) 27 | #' @export 28 | `%.%` <- function(g, f) function(...) g(f(...)) 29 | 30 | #' Postfix function composition operator. 31 | #' 32 | #' `f %;% g` creates a function equivalent to g(f(x)) (or `x |> f() |> g()`) for some input `x`. 33 | #' Unlike conventional function composition, which evaluates right to left, postfix notation operates left to right. 34 | #' This is similar to the direction of the pipe operator `|>`, except the postfix operator creates a function (without evaluating it). 35 | #' @param f A function 36 | #' @param g A function 37 | #' @examples 38 | #' (unique %;% length)(c(1, 1, 2, 2, 3)) 39 | #' @export 40 | `%;%` <- function(f, g) g %.% f 41 | 42 | #' Curryable dplyr::select. 43 | #' 44 | #' Pass all arguments except .data to create a new function of .data. 45 | #' @export 46 | selecting <- currify_verb(dplyr::select) 47 | 48 | #' Curryable dplyr::filter 49 | #' 50 | #' Pass all arguments except .data to create a new function of .data. 51 | #' @export 52 | filtering <- currify_verb(dplyr::filter) 53 | 54 | #' Curryable dplyr::group_by 55 | #' 56 | #' Pass all arguments except .data to create a new function of .data. 57 | #' @export 58 | grouping <- currify_verb(dplyr::group_by) 59 | 60 | #' Curryable dplyr::ungroup 61 | #' 62 | #' Pass all arguments except .data to create a new function of .data. 63 | #' @export 64 | ungrouping <- currify_verb(dplyr::ungroup) 65 | 66 | #' Curryable dplyr::mutate 67 | #' 68 | #' Pass all arguments except .data to create a new function of .data. 69 | #' @export 70 | mutating <- currify_verb(dplyr::mutate) 71 | 72 | #' Curryable dplyr::summarize 73 | #' 74 | #' Pass all arguments except .data to create a new function of .data. 75 | #' @export 76 | summarizing <- currify_verb(dplyr::summarize) 77 | 78 | #' Curryable dplyr::arrange 79 | #' 80 | #' Pass all arguments except .data to create a new function of .data. 81 | #' @export 82 | arranging <- currify_verb(dplyr::arrange) 83 | 84 | #' Curryable dplyr::rename 85 | #' 86 | #' Pass all arguments except .data to create a new function of .data. 87 | #' @export 88 | renaming <- currify_verb(dplyr::rename) 89 | 90 | --------------------------------------------------------------------------------