├── .github ├── .gitignore └── workflows │ └── R-CMD-check.yaml ├── src ├── .gitignore ├── promise.h ├── invoke.cpp ├── capture.cpp ├── promise.cpp ├── default.cpp └── RcppExports.cpp ├── LICENSE ├── .gitignore ├── NEWS.md ├── man-roxygen ├── returns-invoke.R ├── seealso-capture.R ├── param-invoke-call_env.R ├── seealso-promise-methods.R ├── seealso-capture-args.R ├── param-invoke-f_expr.R ├── returns-promise-warning.R └── details-invoke-f_expr.R ├── .Rbuildignore ├── R ├── uneval-package.R ├── util.R ├── RcppExports.R ├── default.R ├── arglist.R ├── capture.R ├── promise.R ├── invoke.R └── autopartial.R ├── README.md ├── tests ├── testthat │ ├── test-promise.R │ ├── test-invoke.R │ ├── test-default.R │ └── test-autopartial.R └── testthat.R ├── README.Rmd ├── uneval.Rproj ├── man ├── promise_expr.Rd ├── is_arglist.Rd ├── promise_env.Rd ├── is_default.Rd ├── arglist.Rd ├── DEFAULT.Rd ├── promises.Rd ├── partial.Rd ├── capture_all.Rd ├── new_promise.Rd ├── promise.Rd ├── capture.Rd ├── as_promise.Rd ├── do_invoke.Rd ├── new_autopartial.Rd ├── invoke.Rd └── autopartial.Rd ├── NAMESPACE ├── DESCRIPTION └── LICENSE.md /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.so 3 | *.dll 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2024 2 | COPYRIGHT HOLDER: uneval authors 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # uneval 0.0.0.9000 2 | 3 | * Initial version based on code from `{ggdist}`. 4 | -------------------------------------------------------------------------------- /man-roxygen/returns-invoke.R: -------------------------------------------------------------------------------- 1 | #' @returns 2 | #' The result of evaluating the function `f`. 3 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^uneval\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^LICENSE\.md$ 4 | ^README\.Rmd$ 5 | ^man-roxygen$ 6 | ^\.github$ 7 | -------------------------------------------------------------------------------- /man-roxygen/seealso-capture.R: -------------------------------------------------------------------------------- 1 | #' @seealso [capture()] for capturing the [`promise`] associated with a single 2 | #' function argument. 3 | -------------------------------------------------------------------------------- /man-roxygen/param-invoke-call_env.R: -------------------------------------------------------------------------------- 1 | #' @param call_env <[`environment`]> environment to call `f` from. This will be 2 | #' available as [parent.frame()] within `f`. 3 | -------------------------------------------------------------------------------- /R/uneval-package.R: -------------------------------------------------------------------------------- 1 | ## usethis namespace: start 2 | #' @importFrom Rcpp sourceCpp 3 | #' @useDynLib uneval, .registration = TRUE 4 | ## usethis namespace: end 5 | NULL 6 | -------------------------------------------------------------------------------- /man-roxygen/seealso-promise-methods.R: -------------------------------------------------------------------------------- 1 | #' @seealso [promise_expr()] and [promise_env()] for extracting the expression 2 | #' or environment associated with a [`promise`]. 3 | -------------------------------------------------------------------------------- /man-roxygen/seealso-capture-args.R: -------------------------------------------------------------------------------- 1 | #' @seealso [capture_all()], [capture_named()], and [capture_dots()] for 2 | #' capturing the [`promise`]s associated with multiple function arguments. 3 | -------------------------------------------------------------------------------- /src/promise.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | SEXP unwrap_promise(SEXP x); 5 | SEXP find_promise(Rcpp::Symbol name, Rcpp::Environment env); 6 | SEXP promise_expr_(Rcpp::Promise promise); 7 | SEXP promise_env_(Rcpp::Promise promise); 8 | -------------------------------------------------------------------------------- /man-roxygen/param-invoke-f_expr.R: -------------------------------------------------------------------------------- 1 | #' @param f_expr <[`language`]> an expression representing `f`. This is primarly 2 | #' cosmetic; it does not affect what function is called, but may be used for 3 | #' printing purposes and will be available as the first element of [sys.call()] 4 | #' within `f`. 5 | -------------------------------------------------------------------------------- /R/util.R: -------------------------------------------------------------------------------- 1 | `%||%` = function (x, y) { 2 | if (is.null(x)) y else x 3 | } 4 | 5 | cat0 = function(...) { 6 | cat(..., sep = "") 7 | } 8 | 9 | # needed instead of deparse1() for R < 4.0 10 | deparse0 = function (expr, collapse = " ", width.cutoff = 500L, ...) { 11 | paste(deparse(expr, width.cutoff, ...), collapse = collapse) 12 | } 13 | -------------------------------------------------------------------------------- /man-roxygen/returns-promise-warning.R: -------------------------------------------------------------------------------- 1 | #' @returns 2 | #' **NOTE:** Due to the fragility of [`promise`]s, it is often best to keep them 3 | #' in [`arglist`]s. If you attempt to assign the results of this function 4 | #' to a variable instead of passing it directly to another function, it will 5 | #' likely be [forced][force] and no longer be a [`promise`]. 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # uneval: Non-standard evaluation and partial function application with promises 5 | 6 | 7 | 8 | [![R-CMD-check](https://github.com/mjskay/uneval/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/mjskay/uneval/actions/workflows/R-CMD-check.yaml) 9 | 10 | 11 | TBD 12 | -------------------------------------------------------------------------------- /src/invoke.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace Rcpp; 3 | 4 | // [[Rcpp::export]] 5 | SEXP apply_closure_(Language call, RObject fun, DottedPair args, Environment env) { 6 | if (TYPEOF(fun) != CLOSXP) { 7 | const char* fmt = "`fun` must be a closure: [type=%s; target=CLOSXP]."; 8 | throw not_compatible(fmt, Rf_type2char(TYPEOF(fun))); 9 | } 10 | return Rf_applyClosure(call, fun, args, env, R_NilValue); 11 | } 12 | -------------------------------------------------------------------------------- /tests/testthat/test-promise.R: -------------------------------------------------------------------------------- 1 | # promises ---------------------------------------------------------------- 2 | 3 | test_that("promise expressions are not retrieved as byte code", { 4 | f = function(...) { 5 | lapply(promises(...), promise_expr_) 6 | } 7 | f = autopartial(f) 8 | g = compiler::cmpfun(function(...) { 9 | gx = 5 10 | f(x = gx, ...) 11 | }) 12 | expect_equal(g(), list(x = quote(gx))) 13 | }) 14 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | # This file is part of the standard setup for testthat. 2 | # It is recommended that you do not modify it. 3 | # 4 | # Where should you do additional test configuration? 5 | # Learn more about the roles of various files in: 6 | # * https://r-pkgs.org/testing-design.html#sec-tests-files-overview 7 | # * https://testthat.r-lib.org/articles/special-files.html 8 | 9 | library(testthat) 10 | library(uneval) 11 | 12 | test_check("uneval") 13 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | # uneval: Non-standard evaluation and partial function application with promises 8 | 9 | 10 | [![R-CMD-check](https://github.com/mjskay/uneval/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/mjskay/uneval/actions/workflows/R-CMD-check.yaml) 11 | 12 | 13 | TBD 14 | 15 | -------------------------------------------------------------------------------- /man-roxygen/details-invoke-f_expr.R: -------------------------------------------------------------------------------- 1 | #' When `f` is a [`closure`], the name of the function in the unevaluated call 2 | #' provided to `f` via [sys.call()] can also be set via `f_expr`, making it 3 | #' possible to avoid potentially-ugly captured function calls created by 4 | #' [match.call()] in `f`. 5 | #' 6 | #' Currently, if `f` is a [`primitive`] function, [invoke()] falls back to using 7 | #' [do.call()], so `f_expr` cannot be used to set the call expression seen by 8 | #' [sys.call()] in `f`. 9 | -------------------------------------------------------------------------------- /uneval.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | LineEndingConversion: Posix 18 | 19 | BuildType: Package 20 | PackageUseDevtools: Yes 21 | PackageInstallArgs: --no-multiarch --with-keep.source 22 | PackageRoxygenize: rd,collate,namespace 23 | -------------------------------------------------------------------------------- /man/promise_expr.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/promise.R 3 | \name{promise_expr} 4 | \alias{promise_expr} 5 | \title{Get a promise expression} 6 | \usage{ 7 | promise_expr(x) 8 | } 9 | \arguments{ 10 | \item{x}{<\code{\link{promise}}>} 11 | } 12 | \value{ 13 | an expression if \code{x} is a \code{\link{promise}}; otherwise \code{x}. 14 | } 15 | \description{ 16 | Return the expression associated with a \code{\link{promise}}. 17 | } 18 | \examples{ 19 | promise_expr(promise(x + 1)) 20 | 21 | } 22 | -------------------------------------------------------------------------------- /man/is_arglist.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/arglist.R 3 | \name{is_arglist} 4 | \alias{is_arglist} 5 | \title{Is \code{x} an argument list?} 6 | \usage{ 7 | is_arglist(x) 8 | } 9 | \arguments{ 10 | \item{x}{an object.} 11 | } 12 | \value{ 13 | scalar \code{\link{logical}}. 14 | } 15 | \description{ 16 | \code{TRUE} if \code{x} is an \code{\link{arglist}}, \code{FALSE} otherwise. 17 | } 18 | \examples{ 19 | is_arglist(arglist()) 20 | 21 | } 22 | \concept{argument list constructors and predicates} 23 | -------------------------------------------------------------------------------- /man/promise_env.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/promise.R 3 | \name{promise_env} 4 | \alias{promise_env} 5 | \title{Get a promise environment} 6 | \usage{ 7 | promise_env(x) 8 | } 9 | \arguments{ 10 | \item{x}{<\code{\link{promise}}>} 11 | } 12 | \value{ 13 | an \code{\link{environment}} if \code{x} is a \code{\link{promise}}; otherwise \code{NULL}. 14 | } 15 | \description{ 16 | Return the \code{\link{environment}} associated with a \code{\link{promise}}. 17 | } 18 | \examples{ 19 | promise_env(promise(x + 1)) 20 | 21 | } 22 | -------------------------------------------------------------------------------- /tests/testthat/test-invoke.R: -------------------------------------------------------------------------------- 1 | test_that("basic invocation works", { 2 | f = function(...) match.call() 3 | 4 | y = 2 5 | z = 3 6 | f(1 + 2, x = y + z) 7 | 8 | expect_equal(invoke(f(promise(1 + 2), x = promise(y + z))), quote(f(1 + 2, x = y + z))) 9 | expect_equal(invoke(f(1 + 2, x = y + z)), quote(f(3, x = 5))) 10 | 11 | g = function(x, ...) invoke(f(1 + 2, x = capture(x), ...)) 12 | expect_equal(g(x = y + z, i = j), quote(f(3, x = y + z, i = j))) 13 | expect_equal( 14 | invoke(f(1 + 2, ... = list(x = y + z, i = promise(j)), m = 8)), 15 | quote(f(3, x = 5, i = j, m = 8)) 16 | ) 17 | }) 18 | -------------------------------------------------------------------------------- /src/capture.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "promise.h" 3 | using namespace Rcpp; 4 | 5 | /** 6 | * Convert a dotted pairlist to a `list()` 7 | * @param dots a dotted pair list as passed to an R function via `...` 8 | * @returns the values of `dots` converted to a list of promises. Nested 9 | * promises are unwrapped so that they are only a single promise. 10 | */ 11 | // [[Rcpp::export]] 12 | List dots_to_list(DottedPair dots) { 13 | int n = dots.size(); 14 | List list(n); 15 | 16 | list.names() = dots.attr("names"); 17 | 18 | for (int i = 0; i < n; i++) { 19 | list[i] = unwrap_promise(dots[0]); 20 | dots.remove(0); 21 | } 22 | 23 | return list; 24 | } 25 | -------------------------------------------------------------------------------- /R/RcppExports.R: -------------------------------------------------------------------------------- 1 | # Generated by using Rcpp::compileAttributes() -> do not edit by hand 2 | # Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 3 | 4 | dots_to_list <- function(dots) { 5 | .Call(`_uneval_dots_to_list`, dots) 6 | } 7 | 8 | is_default_ <- function(x) { 9 | .Call(`_uneval_is_default_`, x) 10 | } 11 | 12 | apply_closure_ <- function(call, fun, args, env) { 13 | .Call(`_uneval_apply_closure_`, call, fun, args, env) 14 | } 15 | 16 | find_promise <- function(name, env) { 17 | .Call(`_uneval_find_promise`, name, env) 18 | } 19 | 20 | promise_expr_ <- function(promise) { 21 | .Call(`_uneval_promise_expr_`, promise) 22 | } 23 | 24 | promise_env_ <- function(promise) { 25 | .Call(`_uneval_promise_env_`, promise) 26 | } 27 | 28 | -------------------------------------------------------------------------------- /man/is_default.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/default.R 3 | \name{is_default} 4 | \alias{is_default} 5 | \title{Is \code{x} a DEFAULT?} 6 | \usage{ 7 | is_default(x) 8 | } 9 | \arguments{ 10 | \item{x}{an object.} 11 | } 12 | \value{ 13 | scalar \code{\link{logical}}. 14 | } 15 | \description{ 16 | \code{TRUE} if \code{x} is a \code{\link[=DEFAULT]{DEFAULT()}} or a promise to a \code{\link[=DEFAULT]{DEFAULT()}}, \code{FALSE} 17 | otherwise. Designed to detect \code{\link[=DEFAULT]{DEFAULT()}} in \code{\link{arglist}}s without evaluating 18 | arguments in many cases. 19 | } 20 | \examples{ 21 | is_default(DEFAULT()) 22 | is_default(promise(DEFAULT())) 23 | x = DEFAULT() 24 | is_default(promise(x)) 25 | 26 | } 27 | \seealso{ 28 | Other DEFAULT constructors and predicates: 29 | \code{\link{DEFAULT}()} 30 | } 31 | \concept{DEFAULT constructors and predicates} 32 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method("[",uneval_arglist) 4 | S3method(as_promise,default) 5 | S3method(as_promise,formula) 6 | S3method(c,uneval_arglist) 7 | S3method(format,uneval_arglist) 8 | S3method(format,uneval_default) 9 | S3method(print,uneval_arglist) 10 | S3method(print,uneval_autopartial) 11 | S3method(print,uneval_default) 12 | S3method(print,uneval_formatted_promise) 13 | export(DEFAULT) 14 | export(arglist) 15 | export(as_promise) 16 | export(autopartial) 17 | export(capture) 18 | export(capture_all) 19 | export(capture_dots) 20 | export(capture_named) 21 | export(do_invoke) 22 | export(invoke) 23 | export(is_arglist) 24 | export(is_default) 25 | export(new_autopartial) 26 | export(new_promise) 27 | export(partial) 28 | export(promise) 29 | export(promise_env) 30 | export(promise_expr) 31 | export(promises) 32 | importFrom(Rcpp,sourceCpp) 33 | useDynLib(uneval, .registration = TRUE) 34 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: uneval 2 | Title: Non-standard Evaluation and Partial Function Application with Promises 3 | Version: 0.0.0.9000 4 | Authors@R: c( 5 | person("Matthew", "Kay", role = c("aut", "cre"), email = "mjskay@northwestern.edu") 6 | ) 7 | Description: Provides an approach to non-standard evaluation and partial function 8 | application (including automatic partial function application, as known as 9 | currying) based on R's built-in promises. Non-standard evaluation with promises 10 | allows both code and execution environment to be captured and forwarded to 11 | function calls in a manner broadly compatible with existing code that uses 12 | non-standard evaluation techniques already built in to the language. 13 | License: MIT + file LICENSE 14 | Encoding: UTF-8 15 | Roxygen: list(markdown = TRUE) 16 | RoxygenNote: 7.3.1 17 | Depends: 18 | R (>= 3.6.0) 19 | Imports: 20 | Rcpp 21 | LinkingTo: 22 | Rcpp 23 | Suggests: 24 | testthat (>= 3.0.0) 25 | Config/testthat/edition: 3 26 | -------------------------------------------------------------------------------- /man/arglist.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/arglist.R 3 | \name{arglist} 4 | \alias{arglist} 5 | \title{An argument list} 6 | \usage{ 7 | arglist(...) 8 | } 9 | \arguments{ 10 | \item{...}{objects, including \code{\link{promise}}s, possibly with names.} 11 | } 12 | \value{ 13 | a \code{\link{list}} with class \code{c("uneval_arglist", "list")}. 14 | } 15 | \description{ 16 | A lightweight wrapper around \code{\link{list}} designed to store values to be passed 17 | to function arguments, including \code{\link{promise}}s. 18 | } 19 | \details{ 20 | \code{arglist}s are \code{\link{list}}s that have more useful output for any \code{\link{promise}}s 21 | they contains when \code{\link{print}}ed or \code{\link{format}}ed, as \code{\link{promise}}s by themselves 22 | do not print well. They otherwise act as normal \code{\link{list}}s. 23 | } 24 | \examples{ 25 | # promises in normal lists don't print well 26 | list(promise(x + 1)) 27 | 28 | # arglists show more useful information 29 | arglist(promise(x + 1)) 30 | 31 | } 32 | \concept{promise list constructors and predicates} 33 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2024 uneval authors 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 | -------------------------------------------------------------------------------- /man/DEFAULT.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/default.R 3 | \name{DEFAULT} 4 | \alias{DEFAULT} 5 | \title{A default argument} 6 | \usage{ 7 | DEFAULT() 8 | } 9 | \description{ 10 | A flag indicating that the default value of an argument should be used. 11 | } 12 | \details{ 13 | \code{\link[=DEFAULT]{DEFAULT()}} is a flag passed to an argument of an \link{autopartial} function 14 | that indicates the argument should keep its default value (or the most 15 | recently partially-applied value of that argument). It is styled in all caps, 16 | like \code{NULL} or \code{TRUE}, as a reminder that it is a special flag. 17 | } 18 | \examples{ 19 | f = autopartial(function(x, y = "b") { 20 | c(x = x, y = y) 21 | }) 22 | 23 | f("a") 24 | 25 | # uses the default value of `y` ("b") 26 | f("a", y = DEFAULT()) 27 | 28 | # partially apply `f` 29 | g = f(y = "c") 30 | g 31 | 32 | # uses the last partially-applied value of `y` ("c") 33 | g("a", y = DEFAULT()) 34 | } 35 | \seealso{ 36 | \code{\link[=autopartial]{autopartial()}} 37 | 38 | Other DEFAULT constructors and predicates: 39 | \code{\link{is_default}()} 40 | } 41 | \concept{DEFAULT constructors and predicates} 42 | -------------------------------------------------------------------------------- /man/promises.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/promise.R 3 | \name{promises} 4 | \alias{promises} 5 | \title{Construct a list of promises} 6 | \usage{ 7 | promises(...) 8 | } 9 | \arguments{ 10 | \item{...}{bare expressions to wrap in promises.} 11 | } 12 | \value{ 13 | an \code{\link{arglist}} of \code{\link{promise}}s. 14 | } 15 | \description{ 16 | Create an \code{\link{arglist}} of promises from bare expressions. 17 | } 18 | \examples{ 19 | promises(1, x + 1, y) 20 | 21 | } 22 | \seealso{ 23 | \code{\link[=promise_expr]{promise_expr()}} and \code{\link[=promise_env]{promise_env()}} for extracting the expression 24 | or environment associated with a \code{\link{promise}}. 25 | 26 | \code{\link[=capture]{capture()}} for capturing the \code{\link{promise}} associated with a single 27 | function argument. 28 | 29 | \code{\link[=capture_all]{capture_all()}}, \code{\link[=capture_named]{capture_named()}}, and \code{\link[=capture_dots]{capture_dots()}} for 30 | capturing the \code{\link{promise}}s associated with multiple function arguments. 31 | 32 | Other promise constructors: 33 | \code{\link{as_promise}()}, 34 | \code{\link{new_promise}()}, 35 | \code{\link{promise}()} 36 | } 37 | \concept{promise constructors} 38 | -------------------------------------------------------------------------------- /src/promise.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "promise.h" 3 | using namespace Rcpp; 4 | 5 | /** 6 | * Unwrap an SEXP that may be a promise to a promise to a promise (etc) into a 7 | * single promise. Non-promises are left as-is. 8 | * @param x an SEXP that might be a promise 9 | * @returns 10 | * - If `x` is a promise: a promise whose code is not a promise 11 | * - If `x` is not a promise: `x` 12 | */ 13 | SEXP unwrap_promise(SEXP x) { 14 | RObject expr = x; 15 | while (TYPEOF(expr) == PROMSXP) { 16 | x = expr; 17 | expr = PREXPR(x); 18 | } 19 | return x; 20 | } 21 | 22 | /** 23 | * Find a promise by name 24 | * @param name name of a variable 25 | * @param env environment to search 26 | * @returns an unwrapped promise (if `name` refers to a promise) or an object 27 | * (if `name` does not refer to a promise). 28 | */ 29 | // [[Rcpp::export]] 30 | SEXP find_promise(Symbol name, Environment env) { 31 | RObject var = Rf_findVar(name, env); 32 | return unwrap_promise(var); 33 | } 34 | 35 | // [[Rcpp::export]] 36 | SEXP promise_expr_(Promise promise) { 37 | promise = unwrap_promise(promise); 38 | return PREXPR(promise); 39 | } 40 | 41 | // [[Rcpp::export]] 42 | SEXP promise_env_(Promise promise) { 43 | promise = unwrap_promise(promise); 44 | return PRENV(promise); 45 | } 46 | -------------------------------------------------------------------------------- /src/default.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "promise.h" 3 | using namespace Rcpp; 4 | 5 | // identical(x, quote(DEFAULT())) || identical(x, quote(uneval::DEFAULT())) 6 | bool is_default_call(SEXP x) { 7 | if (TYPEOF(x) == LANGSXP) { 8 | Language call = x; 9 | if (call.size() == 1) { 10 | if (TYPEOF(call[0]) == SYMSXP) { 11 | Symbol symbol = call[0]; 12 | return symbol == "DEFAULT"; 13 | } else if (TYPEOF(call[0]) == LANGSXP) { 14 | Language f = call[0]; 15 | if (f.size() == 3 && TYPEOF(f[0]) == SYMSXP && TYPEOF(f[1]) == SYMSXP && TYPEOF(f[2]) == SYMSXP) { 16 | Symbol f_call = f[0]; 17 | Symbol f_pkg = f[1]; 18 | Symbol f_fun = f[2]; 19 | return f_call == "::" && f_pkg == "uneval" && f_fun == "DEFAULT"; 20 | } 21 | } 22 | } 23 | } 24 | 25 | return false; 26 | } 27 | 28 | // [[Rcpp::export]] 29 | bool is_default_(RObject x) { 30 | if (TYPEOF(x) == PROMSXP) { 31 | x = unwrap_promise(x); 32 | RObject expr = PREXPR(x); 33 | 34 | if (TYPEOF(expr) == SYMSXP) { 35 | // TODO: should this be PRVALUE? 36 | Environment env = PRENV(x); 37 | x = Rcpp_eval(expr, env); 38 | } else { 39 | x = expr; 40 | } 41 | } 42 | 43 | return is_default_call(x) || Rf_inherits(x, "uneval_default"); 44 | } 45 | -------------------------------------------------------------------------------- /man/partial.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/autopartial.R 3 | \name{partial} 4 | \alias{partial} 5 | \title{Partial function application} 6 | \usage{ 7 | partial(.f, ...) 8 | } 9 | \arguments{ 10 | \item{.f}{<\code{\link{closure}} | \code{\link{primitive}}> function to be partially applied.} 11 | 12 | \item{...}{arguments to be partially applied to \code{.f}.} 13 | } 14 | \value{ 15 | A modified version of \code{.f} with the arguments in \code{...} applied to it. 16 | } 17 | \description{ 18 | Partially apply a function. 19 | } 20 | \details{ 21 | Partially applies the provided arguments to the function \code{.f}. Acts like 22 | \code{\link[=autopartial]{autopartial()}}, except that the next invocation will evaluate the function 23 | rather than waiting for all required arguments to be supplied. 24 | 25 | Arguments may also be passed the special value \code{\link[=DEFAULT]{DEFAULT()}}. If \code{\link[=DEFAULT]{DEFAULT()}} is 26 | passed to an argument, its default value is used instead. 27 | 28 | Great pains are taken to ensure that the resulting function acts as much as 29 | possible like the original function. See the \strong{Implementation details} 30 | section of \code{\link[=autopartial]{autopartial()}} for more information. 31 | } 32 | \examples{ 33 | f = function(x, y, z = 3) c(x, y, z) 34 | fp = partial(f, 1, z = 4) 35 | fp 36 | fp(2) 37 | 38 | } 39 | \seealso{ 40 | Other partial function constructors: 41 | \code{\link{autopartial}()}, 42 | \code{\link{new_autopartial}()} 43 | } 44 | \concept{partial function constructors} 45 | -------------------------------------------------------------------------------- /man/capture_all.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/capture.R 3 | \name{capture_all} 4 | \alias{capture_all} 5 | \alias{capture_named} 6 | \alias{capture_dots} 7 | \title{Capture promises for arguments to a function call} 8 | \usage{ 9 | capture_all(env = parent.frame()) 10 | 11 | capture_named(env = parent.frame()) 12 | 13 | capture_dots(env = parent.frame()) 14 | } 15 | \arguments{ 16 | \item{env}{<\code{\link{environment}}> the frame to capture arguments from. 17 | The default looks at the arguments passed to the function that called this one.} 18 | } 19 | \value{ 20 | An \code{\link{arglist}} where names are names of arguments to the function 21 | in the given frame and values are the promises corresponding to those 22 | arguments. 23 | } 24 | \description{ 25 | Capture a list of \code{\link{promise}}s representing arguments to the surrounding 26 | function call (or another function call specified by its frame, \code{env}). 27 | } 28 | \details{ 29 | \code{capture_all()} captures all arguments to the function call. 30 | 31 | \code{capture_named()} captures named arguments (i.e. those explicitly 32 | listed in the function definition). 33 | 34 | \code{capture_dots()} captures arguments passed via \code{...} 35 | } 36 | \examples{ 37 | # captures x, y, z 38 | f = function(x, y, ...) capture_all() 39 | f(1, y = 2, z = 3) 40 | 41 | # captures x, y 42 | f = function(x, y, ...) capture_named() 43 | f(1, y = 2, z = 3) 44 | 45 | # captures z 46 | f = function(x, y, ...) capture_dots() 47 | f(1, y = 2, z = 3) 48 | 49 | } 50 | \seealso{ 51 | \code{\link[=capture]{capture()}} for capturing the \code{\link{promise}} associated with a single 52 | function argument. 53 | } 54 | -------------------------------------------------------------------------------- /man/new_promise.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/promise.R 3 | \name{new_promise} 4 | \alias{new_promise} 5 | \title{Low-level promise constructor} 6 | \usage{ 7 | new_promise(expr, env = parent.frame()) 8 | } 9 | \arguments{ 10 | \item{expr}{<\code{\link{language}}> expression to wrap in a promise.} 11 | 12 | \item{env}{<\code{\link{environment}}> environment \code{expr} is promised to be executed in.} 13 | } 14 | \value{ 15 | A \code{\link{promise}}. 16 | 17 | \strong{NOTE:} Due to the fragility of \code{\link{promise}}s, it is often best to keep them 18 | in \code{\link{arglist}}s. If you attempt to assign the results of this function 19 | to a variable instead of passing it directly to another function, it will 20 | likely be \link[=force]{forced} and no longer be a \code{\link{promise}}. 21 | } 22 | \description{ 23 | Manually construct a \code{\link{promise}}. 24 | } 25 | \examples{ 26 | arglist(new_promise(quote(x + 1))) 27 | 28 | } 29 | \seealso{ 30 | \code{\link[=promise_expr]{promise_expr()}} and \code{\link[=promise_env]{promise_env()}} for extracting the expression 31 | or environment associated with a \code{\link{promise}}. 32 | 33 | \code{\link[=capture]{capture()}} for capturing the \code{\link{promise}} associated with a single 34 | function argument. 35 | 36 | \code{\link[=capture_all]{capture_all()}}, \code{\link[=capture_named]{capture_named()}}, and \code{\link[=capture_dots]{capture_dots()}} for 37 | capturing the \code{\link{promise}}s associated with multiple function arguments. 38 | 39 | Other promise constructors: 40 | \code{\link{as_promise}()}, 41 | \code{\link{promise}()}, 42 | \code{\link{promises}()} 43 | } 44 | \concept{promise constructors} 45 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | 9 | name: R-CMD-check 10 | 11 | jobs: 12 | R-CMD-check: 13 | runs-on: ${{ matrix.config.os }} 14 | 15 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | config: 21 | - {os: macos-latest, r: 'release'} 22 | - {os: windows-latest, r: 'release'} 23 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 24 | - {os: ubuntu-latest, r: 'release'} 25 | - {os: ubuntu-latest, r: 'oldrel-1'} 26 | - {os: ubuntu-latest, r: 'oldrel-2'} 27 | - {os: ubuntu-latest, r: 'oldrel-3'} 28 | - {os: ubuntu-latest, r: 'oldrel-4'} 29 | 30 | env: 31 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 32 | R_KEEP_PKG_SOURCE: yes 33 | 34 | steps: 35 | - uses: actions/checkout@v4 36 | 37 | - uses: r-lib/actions/setup-pandoc@v2 38 | 39 | - uses: r-lib/actions/setup-r@v2 40 | with: 41 | r-version: ${{ matrix.config.r }} 42 | http-user-agent: ${{ matrix.config.http-user-agent }} 43 | use-public-rspm: true 44 | 45 | - uses: r-lib/actions/setup-r-dependencies@v2 46 | with: 47 | extra-packages: any::rcmdcheck 48 | needs: check 49 | 50 | - uses: r-lib/actions/check-r-package@v2 51 | with: 52 | upload-snapshots: true 53 | build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' 54 | -------------------------------------------------------------------------------- /man/promise.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/promise.R 3 | \name{promise} 4 | \alias{promise} 5 | \title{A promise} 6 | \usage{ 7 | promise(expr) 8 | } 9 | \arguments{ 10 | \item{expr}{ expression to wrap in a promise.} 11 | } 12 | \value{ 13 | An R \emph{promise}: an object with \code{typeof(.) == "promise"}. The 14 | \code{\link{environment}} associated with the promise will be that of the function that 15 | called \code{promise()}. 16 | 17 | \strong{NOTE:} Due to the fragility of \code{\link{promise}}s, it is often best to keep them 18 | in \code{\link{arglist}}s. If you attempt to assign the results of this function 19 | to a variable instead of passing it directly to another function, it will 20 | likely be \link[=force]{forced} and no longer be a \code{\link{promise}}. 21 | } 22 | \description{ 23 | Create a promise from a bare expression. Promises are what power R's lazy 24 | evaluation: they are objects that contain an expression and an 25 | \code{\link{environment}} in which that expression should be executed. 26 | } 27 | \examples{ 28 | arglist(promise(x + 1)) 29 | 30 | } 31 | \seealso{ 32 | \code{\link[=promise_expr]{promise_expr()}} and \code{\link[=promise_env]{promise_env()}} for extracting the expression 33 | or environment associated with a \code{\link{promise}}. 34 | 35 | \code{\link[=capture]{capture()}} for capturing the \code{\link{promise}} associated with a single 36 | function argument. 37 | 38 | \code{\link[=capture_all]{capture_all()}}, \code{\link[=capture_named]{capture_named()}}, and \code{\link[=capture_dots]{capture_dots()}} for 39 | capturing the \code{\link{promise}}s associated with multiple function arguments. 40 | 41 | Other promise constructors: 42 | \code{\link{as_promise}()}, 43 | \code{\link{new_promise}()}, 44 | \code{\link{promises}()} 45 | } 46 | \concept{promise constructors} 47 | -------------------------------------------------------------------------------- /man/capture.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/capture.R 3 | \name{capture} 4 | \alias{capture} 5 | \title{Capture promise for one argument to a function call} 6 | \usage{ 7 | capture(x, env = parent.frame()) 8 | } 9 | \arguments{ 10 | \item{x}{ the name of the argument.} 11 | 12 | \item{env}{<\code{\link{environment}}> the environment to search for the promise. The 13 | default looks at the arguments passed to the function that called this one.} 14 | } 15 | \value{ 16 | One of: 17 | \itemize{ 18 | \item If \code{x} refers to a \code{\link{promise}}, the promise with the given name from the 19 | given environment. Promises whose code is itself a promise (possibly 20 | recursively) are unwrapped so that the code referred to by the returned 21 | promise is not also a promise. 22 | \item If \code{x} does not refer to a promise, it is returned as-is. 23 | } 24 | 25 | \strong{NOTE:} Due to the fragility of \code{\link{promise}}s, it is often best to keep them 26 | in \code{\link{arglist}}s. If you attempt to assign the results of this function 27 | to a variable instead of passing it directly to another function, it will 28 | likely be \link[=force]{forced} and no longer be a \code{\link{promise}}. 29 | } 30 | \description{ 31 | Capture a \code{\link{promise}} representing one argument to the surrounding 32 | function (or another function specified by \code{env}). 33 | } 34 | \examples{ 35 | f = function(x) capture(x) 36 | 37 | # if we capture a raw promise, its output is not 38 | # very informative... 39 | f(1 + 2) 40 | 41 | # ... we can wrap it in an arglist to get 42 | # more information 43 | arglist(f(1 + 2)) 44 | 45 | } 46 | \seealso{ 47 | \code{\link[=capture_all]{capture_all()}}, \code{\link[=capture_named]{capture_named()}}, and \code{\link[=capture_dots]{capture_dots()}} for 48 | capturing the \code{\link{promise}}s associated with multiple function arguments. 49 | } 50 | -------------------------------------------------------------------------------- /man/as_promise.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/promise.R 3 | \name{as_promise} 4 | \alias{as_promise} 5 | \alias{as_promise.default} 6 | \alias{as_promise.formula} 7 | \title{Convert an object to a promise} 8 | \usage{ 9 | as_promise(x, env = parent.frame()) 10 | 11 | \method{as_promise}{default}(x, env = parent.frame()) 12 | 13 | \method{as_promise}{formula}(x, env = parent.frame()) 14 | } 15 | \arguments{ 16 | \item{x}{object to convert to a promise: may be a quoted expression, or 17 | may be an object that wraps an expression and an environment, such as 18 | a \code{\link{formula}}.} 19 | 20 | \item{env}{<\code{\link{environment}}> environment \code{expr} is promised to be executed in. 21 | When applied to a \code{\link{formula}}, the environment associated with the 22 | \code{\link{formula}} will be used.} 23 | } 24 | \value{ 25 | A \code{\link{promise}}. 26 | 27 | \strong{NOTE:} Due to the fragility of \code{\link{promise}}s, it is often best to keep them 28 | in \code{\link{arglist}}s. If you attempt to assign the results of this function 29 | to a variable instead of passing it directly to another function, it will 30 | likely be \link[=force]{forced} and no longer be a \code{\link{promise}}. 31 | } 32 | \description{ 33 | Convert an object to a \code{\link{promise}}. 34 | } 35 | \examples{ 36 | arglist(as_promise(quote(x + 1))) 37 | 38 | arglist(as_promise( ~ x + 1)) 39 | 40 | } 41 | \seealso{ 42 | \code{\link[=promise_expr]{promise_expr()}} and \code{\link[=promise_env]{promise_env()}} for extracting the expression 43 | or environment associated with a \code{\link{promise}}. 44 | 45 | \code{\link[=capture]{capture()}} for capturing the \code{\link{promise}} associated with a single 46 | function argument. 47 | 48 | \code{\link[=capture_all]{capture_all()}}, \code{\link[=capture_named]{capture_named()}}, and \code{\link[=capture_dots]{capture_dots()}} for 49 | capturing the \code{\link{promise}}s associated with multiple function arguments. 50 | 51 | Other promise constructors: 52 | \code{\link{new_promise}()}, 53 | \code{\link{promise}()}, 54 | \code{\link{promises}()} 55 | } 56 | \concept{promise constructors} 57 | -------------------------------------------------------------------------------- /tests/testthat/test-default.R: -------------------------------------------------------------------------------- 1 | # default arguments ---------------------------------------------------------- 2 | 3 | test_that("is_default works", { 4 | x = DEFAULT() 5 | 6 | expect_true(is_default(x)) 7 | expect_true(is_default(DEFAULT())) 8 | 9 | expect_true(is_default(promise(x))) 10 | expect_true(is_default(promise(DEFAULT()))) 11 | expect_true(is_default(promise(uneval::DEFAULT()))) 12 | 13 | f = function(x) capture(x) 14 | g = function(y) f(y) 15 | h = compiler::cmpfun(function(z) g(z)) 16 | expect_true(is_default(h(x))) 17 | expect_true(is_default(h(DEFAULT()))) 18 | expect_true(is_default(h(uneval::DEFAULT()))) 19 | }) 20 | 21 | test_that("DEFAULT arguments are detected correctly", { 22 | f = function(x = 1, y = 2, z = 3) list(x, y, z) 23 | f = autopartial(f) 24 | g = function(...) { 25 | gz = DEFAULT() 26 | f(z = gz, ...) 27 | } 28 | h = function(...) { 29 | g(y = DEFAULT(), ...) 30 | } 31 | 32 | expect_equal(g(), list(1, 2, 3)) 33 | expect_equal(h(x = DEFAULT()), list(1, 2, 3)) 34 | }) 35 | 36 | test_that("DEFAULT works on arguments with default values", { 37 | foo = autopartial(function(x, a = 2) c(x, a)) 38 | 39 | expect_equal(foo(a = DEFAULT())(1), c(1, 2)) 40 | expect_equal(foo(1, a = DEFAULT()), c(1, 2)) 41 | expect_equal(foo(a = DEFAULT())(x = 1), c(1, 2)) 42 | expect_equal(foo(a = DEFAULT())(a = 4)(x = 1), c(1, 4)) 43 | expect_equal(foo(a = 4)(a = DEFAULT())(x = 1), c(1, 4)) 44 | 45 | foo = autopartial(function(x, y, a = 3, b = 4) c(x, y, a, b)) 46 | 47 | expect_equal(foo(a = DEFAULT(), b = 5)(1)(y = -2, b = DEFAULT()), c(1, -2, 3, 5)) 48 | }) 49 | 50 | test_that("DEFAULT works on positional arguments", { 51 | foo = autopartial(function(x, y) c(x, y)) 52 | 53 | expect_equal(foo(DEFAULT()), foo()) 54 | expect_equal(foo(x = DEFAULT())(1), foo(1)) 55 | expect_equal(foo(1, y = DEFAULT()), foo(1)) 56 | expect_equal(foo(DEFAULT())(x = 1), foo(1)) 57 | expect_equal(foo(x = DEFAULT())(x = 4)(y = 1), c(4, 1)) 58 | expect_equal(foo(4)(x = DEFAULT())(y = 1), c(4, 1)) 59 | }) 60 | 61 | test_that("DEFAULT works on initial application", { 62 | f = function(x = 5) x 63 | expect_equal(autopartial(f, DEFAULT())(), 5) 64 | expect_equal(partial(f, DEFAULT())(), 5) 65 | }) 66 | -------------------------------------------------------------------------------- /man/do_invoke.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/invoke.R 3 | \name{do_invoke} 4 | \alias{do_invoke} 5 | \title{Invoke a function using an argument list that may contain promises} 6 | \usage{ 7 | do_invoke(f, args, call_env = parent.frame(), f_expr = substitute(f)) 8 | } 9 | \arguments{ 10 | \item{f}{<\code{\link{closure}} | \code{\link{primitive}}> function to call} 11 | 12 | \item{args}{<\code{\link{arglist}} | \code{\link{list}} | \code{\link{pairlist}}> list of arguments 13 | to call the function with.} 14 | 15 | \item{call_env}{<\code{\link{environment}}> environment to call \code{f} from. This will be 16 | available as \code{\link[=parent.frame]{parent.frame()}} within \code{f}.} 17 | 18 | \item{f_expr}{<\code{\link{language}}> an expression representing \code{f}. This is primarly 19 | cosmetic; it does not affect what function is called, but may be used for 20 | printing purposes and will be available as the first element of \code{\link[=sys.call]{sys.call()}} 21 | within \code{f}.} 22 | } 23 | \value{ 24 | The result of evaluating the function \code{f}. 25 | } 26 | \description{ 27 | Call a function using an explicit argument list that may contain \link{promise}s. 28 | This is a low-level interface intended to mimic \code{\link[=do.call]{do.call()}}; for a 29 | higher-level interface, see \code{\link[=invoke]{invoke()}}. 30 | 31 | Currently, if \code{f} is a \code{\link{primitive}} function, \code{\link[=invoke]{invoke()}} falls back to using 32 | \code{\link[=do.call]{do.call()}}, so \code{f_expr} cannot be used to set the call expression seen by 33 | \code{\link[=sys.call]{sys.call()}} in \code{f}. 34 | } 35 | \details{ 36 | This is intended as an alternative to \code{\link[=do.call]{do.call()}} that provides 37 | better support for promises when calling \code{\link{closure}}s. In particular, 38 | promises in \code{args} will be left as-is, allowing precise manipulation of the 39 | expressions and environments of the arguments to \code{f} by using functions like 40 | \code{\link[=promise]{promise()}}, \code{\link[=promises]{promises()}}, \code{\link[=capture]{capture()}}, \code{\link[=capture_all]{capture_all()}}, etc. 41 | } 42 | \examples{ 43 | # TODO 44 | 45 | } 46 | \seealso{ 47 | \code{\link[=invoke]{invoke()}}, the higher-level interface to \code{\link[=do_invoke]{do_invoke()}} intended 48 | for normal use. 49 | } 50 | -------------------------------------------------------------------------------- /man/new_autopartial.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/autopartial.R 3 | \name{new_autopartial} 4 | \alias{new_autopartial} 5 | \title{Low-level constructor for automatically partially-applied functions} 6 | \usage{ 7 | new_autopartial( 8 | f, 9 | args = arglist(), 10 | required_arg_names = find_required_arg_names(f), 11 | allow_defaults = TRUE, 12 | f_expr = substitute(f) 13 | ) 14 | } 15 | \arguments{ 16 | \item{f}{<\code{\link{closure}} | \code{\link{primitive}}> function to automatically partially-apply.} 17 | 18 | \item{args}{<\code{\link{list}} | \code{\link{pairlist}} | \code{\link{arglist}}> arguments to apply now.} 19 | 20 | \item{required_arg_names}{<\code{\link{character}}> names of required arguments 21 | in \code{f}. When all of these have been supplied, the function will be evaluated. 22 | The default, \code{find_required_arg_names(f)}, considers all arguments without a 23 | default value in the function definition to be required. Pass \code{NULL} or 24 | \code{character()} to get traditional (non-automatic) partial application.} 25 | 26 | \item{allow_defaults}{<\code{\link{logical}}> if \code{TRUE}, if you pass \code{DEFAULT()} to an 27 | argument to this function, whatever value that argument already has will be 28 | used instead.} 29 | 30 | \item{f_expr}{<\code{\link{language}}> an expression representing \code{f}. This is primarly 31 | cosmetic; it does not affect what function is called, but may be used for 32 | printing purposes and will be available as the first element of \code{\link[=sys.call]{sys.call()}} 33 | within \code{f}.} 34 | } 35 | \value{ 36 | a \code{\link{function}} that when called will be partially applied until all 37 | of the arguments in \code{required_arg_names} have been supplied. 38 | } 39 | \description{ 40 | Construct a version of the function \code{f} that is partially applied when called 41 | unless all required arguments have been supplied. This is a low-level 42 | constructor that should be used only if you need to manually adjust 43 | \code{required_arg_names}, \code{allow_defaults}, or \code{f_expr}. In most cases, you should use 44 | the higher-level interfaces \code{\link[=autopartial]{autopartial()}} or \code{\link[=partial]{partial()}}. 45 | } 46 | \examples{ 47 | # TODO 48 | 49 | } 50 | \seealso{ 51 | Other partial function constructors: 52 | \code{\link{autopartial}()}, 53 | \code{\link{partial}()} 54 | } 55 | \concept{partial function constructors} 56 | -------------------------------------------------------------------------------- /R/default.R: -------------------------------------------------------------------------------- 1 | # DEFAULT ------------------------------------------------------------------ 2 | 3 | #' A default argument 4 | #' 5 | #' @description 6 | #' A flag indicating that the default value of an argument should be used. 7 | #' 8 | #' @details 9 | #' [DEFAULT()] is a flag passed to an argument of an [autopartial] function 10 | #' that indicates the argument should keep its default value (or the most 11 | #' recently partially-applied value of that argument). It is styled in all caps, 12 | #' like `NULL` or `TRUE`, as a reminder that it is a special flag. 13 | #' 14 | #' @seealso [autopartial()] 15 | #' @family DEFAULT constructors and predicates 16 | #' 17 | #' @examples 18 | #' f = autopartial(function(x, y = "b") { 19 | #' c(x = x, y = y) 20 | #' }) 21 | #' 22 | #' f("a") 23 | #' 24 | #' # uses the default value of `y` ("b") 25 | #' f("a", y = DEFAULT()) 26 | #' 27 | #' # partially apply `f` 28 | #' g = f(y = "c") 29 | #' g 30 | #' 31 | #' # uses the last partially-applied value of `y` ("c") 32 | #' g("a", y = DEFAULT()) 33 | #' @export 34 | DEFAULT = function() { 35 | structure(list(), class = "uneval_default") 36 | } 37 | 38 | 39 | # type predicates --------------------------------------------------------- 40 | 41 | #' Is `x` a DEFAULT? 42 | #' 43 | #' @description 44 | #' `TRUE` if `x` is a [DEFAULT()] or a promise to a [DEFAULT()], `FALSE` 45 | #' otherwise. Designed to detect [DEFAULT()] in [`arglist`]s without evaluating 46 | #' arguments in many cases. 47 | #' 48 | #' @param x an object. 49 | #' 50 | #' @returns scalar [`logical`]. 51 | #' 52 | #' @family DEFAULT constructors and predicates 53 | #' 54 | #' @examples 55 | #' is_default(DEFAULT()) 56 | #' is_default(promise(DEFAULT())) 57 | #' x = DEFAULT() 58 | #' is_default(promise(x)) 59 | #' 60 | #' @export 61 | is_default = is_default_ 62 | 63 | 64 | # printing and formating -------------------------------------------------- 65 | 66 | #' @export 67 | print.uneval_default = function(x, ...) { 68 | cat("DEFAULT()\n") 69 | } 70 | 71 | #' @export 72 | format.uneval_default = function(x, ...) { 73 | "DEFAULT()" 74 | } 75 | 76 | 77 | # helpers ----------------------------------------------------------------- 78 | 79 | #' Remove DEFAULTs from an argument list 80 | #' @param args a named list of promises 81 | #' @returns A modified version of `args` with any default arguments 82 | #' (arguments for which [is_default()] returns `TRUE`) removed 83 | #' @noRd 84 | remove_defaults = function(args) { 85 | default = vapply(args, is_default, logical(1)) 86 | args[!default] 87 | } 88 | 89 | -------------------------------------------------------------------------------- /R/arglist.R: -------------------------------------------------------------------------------- 1 | # argument lists ----------------------------------------------------------------- 2 | 3 | #' Low-level argument list constructor 4 | #' @param x <[`list`]> a list of arguments 5 | #' @returns a [`list`] with class `c("uneval_arglist", "list")`. 6 | #' @noRd 7 | new_arglist = function(x = list()) { 8 | stopifnot(is.list(x)) 9 | class(x) = c("uneval_arglist", "list") 10 | x 11 | } 12 | 13 | #' An argument list 14 | #' 15 | #' @description 16 | #' A lightweight wrapper around [`list`] designed to store values to be passed 17 | #' to function arguments, including [`promise`]s. 18 | #' 19 | #' @param ... objects, including [`promise`]s, possibly with names. 20 | #' 21 | #' @details 22 | #' `arglist`s are [`list`]s that have more useful output for any [`promise`]s 23 | #' they contains when [`print`]ed or [`format`]ed, as [`promise`]s by themselves 24 | #' do not print well. They otherwise act as normal [`list`]s. 25 | #' 26 | #' @returns a [`list`] with class `c("uneval_arglist", "list")`. 27 | #' 28 | #' @examples 29 | #' # promises in normal lists don't print well 30 | #' list(promise(x + 1)) 31 | #' 32 | #' # arglists show more useful information 33 | #' arglist(promise(x + 1)) 34 | #' 35 | #' @family promise list constructors and predicates 36 | #' @export 37 | arglist = function(...) { 38 | new_arglist(list(...)) 39 | } 40 | 41 | 42 | # predicates -------------------------------------------------------------- 43 | 44 | #' Is `x` an argument list? 45 | #' 46 | #' @description 47 | #' `TRUE` if `x` is an [`arglist`], `FALSE` otherwise. 48 | #' 49 | #' @param x an object. 50 | #' 51 | #' @returns scalar [`logical`]. 52 | #' 53 | #' @family argument list constructors and predicates 54 | #' 55 | #' @examples 56 | #' is_arglist(arglist()) 57 | #' 58 | #' @export 59 | is_arglist = function(x) { 60 | inherits(x, "uneval_arglist") 61 | } 62 | 63 | 64 | # indexing ---------------------------------------------------------------- 65 | 66 | #' @export 67 | `[.uneval_arglist` = function(x, ...) { 68 | new_arglist(NextMethod()) 69 | } 70 | 71 | 72 | # concatenation ----------------------------------------------------------- 73 | 74 | #' @export 75 | c.uneval_arglist = function(...) { 76 | out = NextMethod() 77 | if (is.list(out)) out = new_arglist(out) 78 | out 79 | } 80 | 81 | 82 | # printing and formatting ------------------------------------------------- 83 | 84 | #' @export 85 | print.uneval_arglist = function(x, ...) { 86 | cat0(":\n") 87 | print(lapply(x, make_printable)) 88 | invisible(x) 89 | } 90 | 91 | #' @export 92 | format.uneval_arglist = function(x, ...) { 93 | format(lapply(x, make_printable), ...) 94 | } 95 | 96 | #' @export 97 | print.uneval_formatted_promise = function(x, ...) { 98 | cat0(x, "\n") 99 | invisible(x) 100 | } 101 | 102 | make_printable = function(x) { 103 | if (typeof(x) == "promise") { 104 | expr = promise_expr(x) 105 | env = promise_env(x) 106 | structure( 107 | paste0(""), 108 | class = c("uneval_formatted_promise", "character") 109 | ) 110 | } else { 111 | x 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/RcppExports.cpp: -------------------------------------------------------------------------------- 1 | // Generated by using Rcpp::compileAttributes() -> do not edit by hand 2 | // Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 3 | 4 | #include 5 | 6 | using namespace Rcpp; 7 | 8 | #ifdef RCPP_USE_GLOBAL_ROSTREAM 9 | Rcpp::Rostream& Rcpp::Rcout = Rcpp::Rcpp_cout_get(); 10 | Rcpp::Rostream& Rcpp::Rcerr = Rcpp::Rcpp_cerr_get(); 11 | #endif 12 | 13 | // dots_to_list 14 | List dots_to_list(DottedPair dots); 15 | RcppExport SEXP _uneval_dots_to_list(SEXP dotsSEXP) { 16 | BEGIN_RCPP 17 | Rcpp::RObject rcpp_result_gen; 18 | Rcpp::RNGScope rcpp_rngScope_gen; 19 | Rcpp::traits::input_parameter< DottedPair >::type dots(dotsSEXP); 20 | rcpp_result_gen = Rcpp::wrap(dots_to_list(dots)); 21 | return rcpp_result_gen; 22 | END_RCPP 23 | } 24 | // is_default_ 25 | bool is_default_(RObject x); 26 | RcppExport SEXP _uneval_is_default_(SEXP xSEXP) { 27 | BEGIN_RCPP 28 | Rcpp::RObject rcpp_result_gen; 29 | Rcpp::RNGScope rcpp_rngScope_gen; 30 | Rcpp::traits::input_parameter< RObject >::type x(xSEXP); 31 | rcpp_result_gen = Rcpp::wrap(is_default_(x)); 32 | return rcpp_result_gen; 33 | END_RCPP 34 | } 35 | // apply_closure_ 36 | SEXP apply_closure_(Language call, RObject fun, DottedPair args, Environment env); 37 | RcppExport SEXP _uneval_apply_closure_(SEXP callSEXP, SEXP funSEXP, SEXP argsSEXP, SEXP envSEXP) { 38 | BEGIN_RCPP 39 | Rcpp::RObject rcpp_result_gen; 40 | Rcpp::RNGScope rcpp_rngScope_gen; 41 | Rcpp::traits::input_parameter< Language >::type call(callSEXP); 42 | Rcpp::traits::input_parameter< RObject >::type fun(funSEXP); 43 | Rcpp::traits::input_parameter< DottedPair >::type args(argsSEXP); 44 | Rcpp::traits::input_parameter< Environment >::type env(envSEXP); 45 | rcpp_result_gen = Rcpp::wrap(apply_closure_(call, fun, args, env)); 46 | return rcpp_result_gen; 47 | END_RCPP 48 | } 49 | // find_promise 50 | SEXP find_promise(Symbol name, Environment env); 51 | RcppExport SEXP _uneval_find_promise(SEXP nameSEXP, SEXP envSEXP) { 52 | BEGIN_RCPP 53 | Rcpp::RObject rcpp_result_gen; 54 | Rcpp::RNGScope rcpp_rngScope_gen; 55 | Rcpp::traits::input_parameter< Symbol >::type name(nameSEXP); 56 | Rcpp::traits::input_parameter< Environment >::type env(envSEXP); 57 | rcpp_result_gen = Rcpp::wrap(find_promise(name, env)); 58 | return rcpp_result_gen; 59 | END_RCPP 60 | } 61 | // promise_expr_ 62 | SEXP promise_expr_(Promise promise); 63 | RcppExport SEXP _uneval_promise_expr_(SEXP promiseSEXP) { 64 | BEGIN_RCPP 65 | Rcpp::RObject rcpp_result_gen; 66 | Rcpp::RNGScope rcpp_rngScope_gen; 67 | Rcpp::traits::input_parameter< Promise >::type promise(promiseSEXP); 68 | rcpp_result_gen = Rcpp::wrap(promise_expr_(promise)); 69 | return rcpp_result_gen; 70 | END_RCPP 71 | } 72 | // promise_env_ 73 | SEXP promise_env_(Promise promise); 74 | RcppExport SEXP _uneval_promise_env_(SEXP promiseSEXP) { 75 | BEGIN_RCPP 76 | Rcpp::RObject rcpp_result_gen; 77 | Rcpp::RNGScope rcpp_rngScope_gen; 78 | Rcpp::traits::input_parameter< Promise >::type promise(promiseSEXP); 79 | rcpp_result_gen = Rcpp::wrap(promise_env_(promise)); 80 | return rcpp_result_gen; 81 | END_RCPP 82 | } 83 | 84 | static const R_CallMethodDef CallEntries[] = { 85 | {"_uneval_dots_to_list", (DL_FUNC) &_uneval_dots_to_list, 1}, 86 | {"_uneval_is_default_", (DL_FUNC) &_uneval_is_default_, 1}, 87 | {"_uneval_apply_closure_", (DL_FUNC) &_uneval_apply_closure_, 4}, 88 | {"_uneval_find_promise", (DL_FUNC) &_uneval_find_promise, 2}, 89 | {"_uneval_promise_expr_", (DL_FUNC) &_uneval_promise_expr_, 1}, 90 | {"_uneval_promise_env_", (DL_FUNC) &_uneval_promise_env_, 1}, 91 | {NULL, NULL, 0} 92 | }; 93 | 94 | RcppExport void R_init_uneval(DllInfo *dll) { 95 | R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); 96 | R_useDynamicSymbols(dll, FALSE); 97 | } 98 | -------------------------------------------------------------------------------- /R/capture.R: -------------------------------------------------------------------------------- 1 | # capture many args ------------------------------------------------------- 2 | 3 | #' Capture promises for arguments to a function call 4 | #' 5 | #' @description 6 | #' Capture a list of [`promise`]s representing arguments to the surrounding 7 | #' function call (or another function call specified by its frame, `env`). 8 | #' 9 | #' @param env <[`environment`]> the frame to capture arguments from. 10 | #' The default looks at the arguments passed to the function that called this one. 11 | #' 12 | #' @details 13 | #' `capture_all()` captures all arguments to the function call. 14 | #' 15 | #' `capture_named()` captures named arguments (i.e. those explicitly 16 | #' listed in the function definition). 17 | #' 18 | #' `capture_dots()` captures arguments passed via `...` 19 | #' 20 | #' @returns An [`arglist`] where names are names of arguments to the function 21 | #' in the given frame and values are the promises corresponding to those 22 | #' arguments. 23 | #' 24 | #' @template seealso-capture 25 | #' 26 | #' @examples 27 | #' # captures x, y, z 28 | #' f = function(x, y, ...) capture_all() 29 | #' f(1, y = 2, z = 3) 30 | #' 31 | #' # captures x, y 32 | #' f = function(x, y, ...) capture_named() 33 | #' f(1, y = 2, z = 3) 34 | #' 35 | #' # captures z 36 | #' f = function(x, y, ...) capture_dots() 37 | #' f(1, y = 2, z = 3) 38 | #' 39 | #' @name capture_all 40 | #' @export 41 | capture_all = function(env = parent.frame()) { 42 | named_args = capture_named(env) 43 | dots_args = capture_dots(env) 44 | all_args = c(named_args, dots_args) 45 | # TODO: this next two lines might be redundant / can maybe just return all_args 46 | f = do.call(sys.function, list(), envir = env) 47 | new_arglist(match_function_args(f, all_args)) 48 | } 49 | 50 | #' @rdname capture_all 51 | #' @export 52 | capture_named = function(env = parent.frame()) { 53 | f = do.call(sys.function, list(), envir = env) 54 | call = do.call(match.call, list(), envir = env) 55 | arg_names = intersect(names(call[-1]), names(formals(args(f)))) 56 | promises = lapply(arg_names, find_promise, env) 57 | names(promises) = arg_names 58 | new_arglist(promises) 59 | } 60 | 61 | #' @rdname capture_all 62 | #' @export 63 | capture_dots = function(env = parent.frame()) { 64 | dots = env$... 65 | if (missing(dots)) { 66 | new_arglist() 67 | } else { 68 | new_arglist(dots_to_list(dots)) 69 | } 70 | } 71 | 72 | 73 | # capture one arg --------------------------------------------------------- 74 | 75 | #' Capture promise for one argument to a function call 76 | #' 77 | #' @description 78 | #' Capture a [`promise`] representing one argument to the surrounding 79 | #' function (or another function specified by `env`). 80 | #' 81 | #' @param x the name of the argument. 82 | #' @param env <[`environment`]> the environment to search for the promise. The 83 | #' default looks at the arguments passed to the function that called this one. 84 | #' 85 | #' @returns One of: 86 | #' - If `x` refers to a [`promise`], the promise with the given name from the 87 | #' given environment. Promises whose code is itself a promise (possibly 88 | #' recursively) are unwrapped so that the code referred to by the returned 89 | #' promise is not also a promise. 90 | #' - If `x` does not refer to a promise, it is returned as-is. 91 | #' 92 | #' @template returns-promise-warning 93 | #' 94 | #' @template seealso-capture-args 95 | #' 96 | #' @examples 97 | #' f = function(x) capture(x) 98 | #' 99 | #' # if we capture a raw promise, its output is not 100 | #' # very informative... 101 | #' f(1 + 2) 102 | #' 103 | #' # ... we can wrap it in an arglist to get 104 | #' # more information 105 | #' arglist(f(1 + 2)) 106 | #' 107 | #' @export 108 | capture = function(x, env = parent.frame()) { 109 | # seem to need to do wrap the call to find_promise() in a list rather than 110 | # returning directly to avoid evaluating the promise... 111 | list(find_promise(substitute(x), env))[[1]] 112 | } 113 | -------------------------------------------------------------------------------- /tests/testthat/test-autopartial.R: -------------------------------------------------------------------------------- 1 | # autopartial ------------------------------------------------------------- 2 | 3 | test_that("partial function printing works", { 4 | add1 = function(x, ...) { 5 | x + 1 6 | } 7 | 8 | add1_auto = autopartial(add1) 9 | 10 | expect_output(print(add1_auto()), " an expression giving the function and 16 | arguments to be called. Unlike normal function invocation, arguments will 17 | not be automatically turned into promises, so must be wrapped in \code{\link[=promise]{promise()}} 18 | if you wish them to be evaluated lazily. Dots can be passed using \code{...}, and 19 | lists of arguments can be spliced in using \verb{... = }.} 20 | 21 | \item{env}{<\code{\link{environment}}> the environment to evaluate the function 22 | definition and arguments extracted from \code{expr} in.} 23 | 24 | \item{call_env}{<\code{\link{environment}}> environment to call \code{f} from. This will be 25 | available as \code{\link[=parent.frame]{parent.frame()}} within \code{f}.} 26 | 27 | \item{f_expr}{<\code{\link{language}}> an expression representing \code{f}. This is primarly 28 | cosmetic; it does not affect what function is called, but may be used for 29 | printing purposes and will be available as the first element of \code{\link[=sys.call]{sys.call()}} 30 | within \code{f}.} 31 | } 32 | \value{ 33 | The result of evaluating the function \code{f}. 34 | } 35 | \description{ 36 | Call a function using arguments that may be \link{promise}s. 37 | \code{\link[=invoke]{invoke()}} is syntactic sugar for \code{\link[=do_invoke]{do_invoke()}} that is designed to look 38 | more like a function call and which allows splicing-in of argument lists 39 | via assignment to \code{...}. 40 | 41 | Currently, if \code{f} is a \code{\link{primitive}} function, \code{\link[=invoke]{invoke()}} falls back to using 42 | \code{\link[=do.call]{do.call()}}, so \code{f_expr} cannot be used to set the call expression seen by 43 | \code{\link[=sys.call]{sys.call()}} in \code{f}. 44 | } 45 | \details{ 46 | This function allows you to call another function while explicitly giving 47 | each argument as either an already-evaluated object or as a \code{\link{promise}}. 48 | 49 | Consider a function like this: 50 | 51 | \if{html}{\out{
}}\preformatted{f = function(...) match.call() 52 | }\if{html}{\out{
}} 53 | 54 | We can call it as follows: 55 | 56 | \if{html}{\out{
}}\preformatted{y = 2 57 | z = 3 58 | f(1 + 2, x = y + z) 59 | #> f(1 + 2, x = y + z) 60 | }\if{html}{\out{
}} 61 | 62 | The standard function invocation \code{f(1, x = y + z)} creates a \code{\link{promise}} for 63 | each argument, ensuring they are lazily evaluated by the underlying call. 64 | Because they are not evaluated, \code{match.call()} shows the unevaluated 65 | expressions when called. 66 | 67 | \code{invoke()} makes the creation of argument \code{\link{promise}}e explicit, requiring 68 | you to wrap an argument's expression in \code{\link[=promise]{promise()}} if you wish it to be 69 | evaluated lazily. Thus, the equivalent of the above call with \code{invoke()} is: 70 | 71 | \if{html}{\out{
}}\preformatted{invoke(f(promise(1 + 2), x = promise(y + z))) 72 | #> f(1 + 2, x = y + z) 73 | }\if{html}{\out{
}} 74 | 75 | By making construction of argument \code{\link{promise}}s explicit, we can more easily 76 | manipulate when and how arguments are evaluated. For example, we can evaluate 77 | arguments at call time by not wrapping them in \code{\link[=promise]{promise()}}: 78 | 79 | \if{html}{\out{
}}\preformatted{invoke(f(1 + 2, x = y + z)) 80 | #> f(3, x = 5) 81 | }\if{html}{\out{
}} 82 | 83 | Or, we can pass down an argument's \code{\link{promise}} captured via \code{\link{capture}}: 84 | 85 | \if{html}{\out{
}}\preformatted{g = function(z, ...) invoke(f(1 + 2, x = capture(z), ...)) 86 | g(z = a + b, i = j) 87 | #> f(3, x = a + b, i = j) 88 | }\if{html}{\out{
}} 89 | 90 | Notice how \code{...} is also forwarded above, allowing the \code{i} argument to be 91 | forwarded. Lists of arguments can also be spliced in using \verb{... = }: 92 | 93 | \if{html}{\out{
}}\preformatted{invoke(f(1 + 2, ... = list(x = y + z, i = promise(j)), m = 8)) 94 | #> f(3, x = 5, i = j, m = 8) 95 | }\if{html}{\out{
}} 96 | } 97 | \examples{ 98 | # TODO 99 | 100 | } 101 | \seealso{ 102 | \code{\link[=do_invoke]{do_invoke()}}, the low-level function-calling interface used by 103 | \code{\link[=invoke]{invoke()}}. 104 | } 105 | -------------------------------------------------------------------------------- /R/promise.R: -------------------------------------------------------------------------------- 1 | # promise ----------------------------------------------------------------- 2 | 3 | #' Low-level promise constructor 4 | #' 5 | #' @description 6 | #' Manually construct a [`promise`]. 7 | #' 8 | #' @param expr <[`language`]> expression to wrap in a promise. 9 | #' @param env <[`environment`]> environment `expr` is promised to be executed in. 10 | #' 11 | #' @returns A [`promise`]. 12 | #' @template returns-promise-warning 13 | #' 14 | #' @template seealso-promise-methods 15 | #' @template seealso-capture 16 | #' @template seealso-capture-args 17 | #' @family promise constructors 18 | #' 19 | #' @examples 20 | #' arglist(new_promise(quote(x + 1))) 21 | #' 22 | #' @export 23 | new_promise = function(expr, env = parent.frame()) { 24 | do.call(promises, list(expr), envir = env)[[1]] 25 | } 26 | 27 | #' A promise 28 | #' 29 | #' @description 30 | #' Create a promise from a bare expression. Promises are what power R's lazy 31 | #' evaluation: they are objects that contain an expression and an 32 | #' [`environment`] in which that expression should be executed. 33 | #' 34 | #' @param expr expression to wrap in a promise. 35 | #' 36 | #' @returns An R *promise*: an object with `typeof(.) == "promise"`. The 37 | #' [`environment`] associated with the promise will be that of the function that 38 | #' called `promise()`. 39 | #' @template returns-promise-warning 40 | #' 41 | #' @template seealso-promise-methods 42 | #' @template seealso-capture 43 | #' @template seealso-capture-args 44 | #' @family promise constructors 45 | #' 46 | #' @examples 47 | #' arglist(promise(x + 1)) 48 | #' 49 | #' @export 50 | promise = function(expr) { 51 | capture(expr) 52 | } 53 | 54 | 55 | # promises ---------------------------------------------------------------- 56 | 57 | #' Construct a list of promises 58 | #' 59 | #' @description 60 | #' Create an [`arglist`] of promises from bare expressions. 61 | #' 62 | #' @param ... bare expressions to wrap in promises. 63 | #' 64 | #' @returns an [`arglist`] of [`promise`]s. 65 | #' 66 | #' @template seealso-promise-methods 67 | #' @template seealso-capture 68 | #' @template seealso-capture-args 69 | #' @family promise constructors 70 | #' 71 | #' @examples 72 | #' promises(1, x + 1, y) 73 | #' 74 | #' @export 75 | promises = function(...) { 76 | capture_dots() 77 | } 78 | 79 | 80 | # type conversion --------------------------------------------------------- 81 | 82 | #' Convert an object to a promise 83 | #' 84 | #' @description 85 | #' Convert an object to a [`promise`]. 86 | #' 87 | #' @param x object to convert to a promise: may be a quoted expression, or 88 | #' may be an object that wraps an expression and an environment, such as 89 | #' a [`formula`]. 90 | #' @param env <[`environment`]> environment `expr` is promised to be executed in. 91 | #' When applied to a [`formula`], the environment associated with the 92 | #' [`formula`] will be used. 93 | #' 94 | #' @returns A [`promise`]. 95 | #' @template returns-promise-warning 96 | #' 97 | #' @template seealso-promise-methods 98 | #' @template seealso-capture 99 | #' @template seealso-capture-args 100 | #' @family promise constructors 101 | #' 102 | #' @examples 103 | #' arglist(as_promise(quote(x + 1))) 104 | #' 105 | #' arglist(as_promise( ~ x + 1)) 106 | #' 107 | #' @export 108 | as_promise = function(x, env = parent.frame()) { 109 | UseMethod("as_promise") 110 | } 111 | 112 | #' @rdname as_promise 113 | #' @export 114 | as_promise.default = function(x, env = parent.frame()) { 115 | new_promise(x, env) 116 | } 117 | 118 | #' @rdname as_promise 119 | #' @export 120 | as_promise.formula = function(x, env = parent.frame()) { 121 | new_promise(x[[length(x)]], environment(x)) 122 | } 123 | 124 | # methods ----------------------------------------------------------------- 125 | 126 | #' Get a promise expression 127 | #' 128 | #' @description 129 | #' Return the expression associated with a [`promise`]. 130 | #' 131 | #' @param x <[`promise`]> 132 | #' 133 | #' @returns an expression if `x` is a [`promise`]; otherwise `x`. 134 | #' 135 | #' @examples 136 | #' promise_expr(promise(x + 1)) 137 | #' 138 | #' @export 139 | promise_expr = function(x) { 140 | if (typeof(x) == "promise") { 141 | promise_expr_(x) 142 | } else { 143 | x 144 | } 145 | } 146 | 147 | #' Get a promise environment 148 | #' 149 | #' @description 150 | #' Return the [`environment`] associated with a [`promise`]. 151 | #' 152 | #' @param x <[`promise`]> 153 | #' 154 | #' @returns an [`environment`] if `x` is a [`promise`]; otherwise `NULL`. 155 | #' 156 | #' @examples 157 | #' promise_env(promise(x + 1)) 158 | #' 159 | #' @export 160 | promise_env = function(x) { 161 | if (typeof(x) == "promise") { 162 | promise_env_(x) 163 | } else { 164 | NULL 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /man/autopartial.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/autopartial.R 3 | \name{autopartial} 4 | \alias{autopartial} 5 | \title{Automatic partial function application} 6 | \usage{ 7 | autopartial(.f, ...) 8 | } 9 | \arguments{ 10 | \item{.f}{<\code{\link{closure}} | \code{\link{primitive}}> function to turn into an automatically 11 | partially-applied function.} 12 | 13 | \item{...}{optional arguments to be partially applied to \code{.f}.} 14 | } 15 | \value{ 16 | A modified version of \code{.f} that will automatically be partially 17 | applied until all of its required arguments have been given. 18 | } 19 | \description{ 20 | Construct an \code{autopartial} function; that is, a function that supports 21 | \emph{automatic partial application}. 22 | 23 | When a function created by \code{autopartial()} is called, 24 | if all of its required arguments have not been provided, it returns a 25 | modified version of itself that uses the arguments passed to it so far as 26 | defaults. Once all required arguments have been supplied, the function is 27 | evaluated. 28 | 29 | \code{autopartial()} can be considered a form of 30 | \href{https://en.wikipedia.org/wiki/Currying}{currying} with respect to 31 | a function's required arguments, but I think \emph{automatic partial application} 32 | gets the idea across more clearly. \code{autopartial()} also has a number of 33 | features, like named arguments and defaults, which are not typically 34 | considered part of the classical definition of currying. See \strong{Details}. 35 | } 36 | \details{ 37 | Create an \emph{automatically partially-applied} function by passing it to 38 | \code{autopartial()}. The resulting function can be called repeatedly until 39 | all required arguments (i.e. those that do not have default values 40 | provided in the function definition) have been supplied, then the function is 41 | evaluated and the result returned. 42 | 43 | For example, consider the function \code{f}: 44 | 45 | \if{html}{\out{
}}\preformatted{f = function(x, y, z, a = 4) c(x, y, z, a) 46 | f = autopartial(f) 47 | f 48 | #> : 49 | #> f = function(x, y, z, a = 4) c(x, y, z, a) 50 | #> 51 | #> f() 52 | }\if{html}{\out{
}} 53 | 54 | It can be called as normal: 55 | 56 | \if{html}{\out{
}}\preformatted{f(1, 2, 3) 57 | #> [1] 1 2 3 4 58 | }\if{html}{\out{
}} 59 | 60 | If all required arguments haven't been supplied yet, the result is a 61 | partially-applied function: 62 | 63 | \if{html}{\out{
}}\preformatted{f(1) 64 | #> : 65 | #> f = function(x, y, z, a = 4) c(x, y, z, a) 66 | #> 67 | #> f(x = 1) 68 | }\if{html}{\out{
}} 69 | 70 | These invocations can be chained in arbitrary combinations: 71 | 72 | \if{html}{\out{
}}\preformatted{f(1)(2)(3) 73 | #> [1] 1 2 3 4 74 | f(z = 3)(1, 2) 75 | #> [1] 1 2 3 4 76 | f(y = 2, z = 3)(1) 77 | #> [1] 1 2 3 4 78 | f(y = 2)(1)(3) 79 | #> [1] 1 2 3 4 80 | # etc ... 81 | }\if{html}{\out{
}} 82 | 83 | Positional arguments can be supplied by position or by name. Named arguments 84 | can be supplied anywhere in the sequence; e.g. \code{f(a = 7)(1, 2, 3)} is 85 | equivalent to \code{f(1, 2, 3, a = 7)}. 86 | 87 | Arguments supplied in one partial call can be overridden later in the sequence. 88 | For example, \code{f(1)(x = 2)(x = 3)} is equivalent to just \code{f(x = 3)}. 89 | 90 | Arguments may also be passed the special value \code{\link[=DEFAULT]{DEFAULT()}}. If \code{\link[=DEFAULT]{DEFAULT()}} is 91 | passed to an argument, its default value (or the most recently-partially-applied 92 | non-\code{\link{DEFAULT}} value) is used instead. For example, \code{f(a = DEFAULT())} is 93 | equivalent to \code{f(a = 4)} (since the default value of \code{a} is \code{4} in the 94 | definition of \code{f}), and \code{f(x = 1)(x = DEFAULT())} is equivalent to \code{f(x = 1)}. 95 | } 96 | \section{Implementation details}{ 97 | 98 | Great pains are taken to ensure that \code{autopartial} functions act as much as 99 | possible like normal R functions. 100 | 101 | The initial definition of an \code{autopartial} function has the same 102 | \code{\link[=formals]{formals()}} as the function it wraps. When it is further partially applied, 103 | the \code{\link[=formals]{formals()}} are updated to reflect the expressions of the modified 104 | arguments. To allow for sequences of applications of positional arguments 105 | (e.g. \code{f(1)(2)(3)}) to be equivalent to a single application (e.g. 106 | \code{f(1, 2, 3)}), positional arguments are moved to the back of the \code{\link[=formals]{formals()}} 107 | list when they are applied. 108 | 109 | During partial application, arguments are stored as \code{\link{promise}}s and will not 110 | be evaluated until the underlying function is ultimately called (and even 111 | then, an argument may not be evaluated if that function does not evaluate 112 | the argument in question---just like normal R functions). Thus, non-standard 113 | evaluation constructs like \code{\link[=substitute]{substitute()}} should work correctly within the 114 | underlying function. 115 | 116 | \code{\link[=DEFAULT]{DEFAULT()}} values are detected, as much as possible, without evaluating 117 | arguments: a \code{\link[=DEFAULT]{DEFAULT()}} is valid only if it is stored in a symbol passed to 118 | an argument or if \code{DEFAULT()} is passed directly to the argument. 119 | 120 | The final function evaluation acts as much as possible like a normal function 121 | evaluation. The underlying function is called from the same environment that 122 | the final \code{autopartial} function is called from, so functions that inspect 123 | their context (e.g. using \code{\link[=parent.frame]{parent.frame()}}) should work correctly. The call 124 | expression associated with the function invocation is also constructed 125 | to reflect all of the partially-applied arguments and the original function 126 | name, so functions that use \code{\link[=sys.call]{sys.call()}} or \code{\link[=match.call]{match.call()}} should also give 127 | nice-looking output. 128 | 129 | For example, the \code{\link[=density]{density()}} function, which uses both \code{\link[=substitute]{substitute()}} and 130 | \code{\link[=match.call]{match.call()}} to construct labels for its output, gives identical output 131 | if invoked directly (e.g. \code{density(rnorm(10))}) or via \code{autopartial()} 132 | (e.g. \code{autopartial(density)(rnorm(10))}). 133 | 134 | Under the hood, \code{autopartial()} uses \code{\link[=new_autopartial]{new_autopartial()}} to create the 135 | \emph{automatically partially-applied} function, captures intermediate arguments 136 | as \code{\link{promise}}s using \code{\link[=capture_all]{capture_all()}}, and ultimately uses \code{\link[=invoke]{invoke()}} 137 | to call the underlying function. 138 | } 139 | 140 | \examples{ 141 | # create a custom automatically partially-applied function 142 | f = autopartial(function(x, y, z = 3) (x + y) * z) 143 | f() 144 | f(1) 145 | g = f(y = 2)(z = 4) 146 | g 147 | g(1) 148 | 149 | # pass DEFAULT() to optional arguments to use existing values 150 | f(z = DEFAULT())(1, 2) # uses default z = 3 151 | f(z = 4)(z = DEFAULT())(1, 2) # uses z = 4 152 | 153 | } 154 | \seealso{ 155 | Other partial function constructors: 156 | \code{\link{new_autopartial}()}, 157 | \code{\link{partial}()} 158 | } 159 | \concept{partial function constructors} 160 | -------------------------------------------------------------------------------- /R/invoke.R: -------------------------------------------------------------------------------- 1 | # invoke ------------------------------------------------------------------ 2 | 3 | #' Invoke a function using promises 4 | #' 5 | #' @description 6 | #' Call a function using arguments that may be [promise]s. 7 | #' [invoke()] is syntactic sugar for [do_invoke()] that is designed to look 8 | #' more like a function call and which allows splicing-in of argument lists 9 | #' via assignment to `...`. 10 | #' 11 | #' @param expr an expression giving the function and 12 | #' arguments to be called. Unlike normal function invocation, arguments will 13 | #' not be automatically turned into promises, so must be wrapped in [promise()] 14 | #' if you wish them to be evaluated lazily. Dots can be passed using `...`, and 15 | #' lists of arguments can be spliced in using `... = `. 16 | #' @param env <[`environment`]> the environment to evaluate the function 17 | #' definition and arguments extracted from `expr` in. 18 | #' @template param-invoke-call_env 19 | #' @template param-invoke-f_expr 20 | #' 21 | #' @details 22 | #' This function allows you to call another function while explicitly giving 23 | #' each argument as either an already-evaluated object or as a [`promise`]. 24 | #' 25 | #' Consider a function like this: 26 | #' 27 | #' ```{r} 28 | #' f = function(...) match.call() 29 | #' ``` 30 | #' 31 | #' We can call it as follows: 32 | #' 33 | #' ```{r} 34 | #' y = 2 35 | #' z = 3 36 | #' f(1 + 2, x = y + z) 37 | #' ``` 38 | #' 39 | #' The standard function invocation `f(1, x = y + z)` creates a [`promise`] for 40 | #' each argument, ensuring they are lazily evaluated by the underlying call. 41 | #' Because they are not evaluated, `match.call()` shows the unevaluated 42 | #' expressions when called. 43 | #' 44 | #' `invoke()` makes the creation of argument [`promise`]e explicit, requiring 45 | #' you to wrap an argument's expression in [promise()] if you wish it to be 46 | #' evaluated lazily. Thus, the equivalent of the above call with `invoke()` is: 47 | #' 48 | #' ```{r} 49 | #' invoke(f(promise(1 + 2), x = promise(y + z))) 50 | #' ``` 51 | #' 52 | #' By making construction of argument [`promise`]s explicit, we can more easily 53 | #' manipulate when and how arguments are evaluated. For example, we can evaluate 54 | #' arguments at call time by not wrapping them in [promise()]: 55 | #' 56 | #' ```{r} 57 | #' invoke(f(1 + 2, x = y + z)) 58 | #' ``` 59 | #' 60 | #' Or, we can pass down an argument's [`promise`] captured via [`capture`]: 61 | #' 62 | #' ```{r} 63 | #' g = function(z, ...) invoke(f(1 + 2, x = capture(z), ...)) 64 | #' g(z = a + b, i = j) 65 | #' ``` 66 | #' 67 | #' Notice how `...` is also forwarded above, allowing the `i` argument to be 68 | #' forwarded. Lists of arguments can also be spliced in using `... = `: 69 | #' 70 | #' ```{r} 71 | #' invoke(f(1 + 2, ... = list(x = y + z, i = promise(j)), m = 8)) 72 | #' ``` 73 | #' 74 | #' @template details-invoke-f_expr 75 | #' 76 | #' @template returns-invoke 77 | #' 78 | #' @seealso [do_invoke()], the low-level function-calling interface used by 79 | #' [invoke()]. 80 | #' 81 | #' @examples 82 | #' # TODO 83 | #' 84 | #' @export 85 | invoke = function(expr, env = parent.frame(), call_env = env, f_expr = substitute(expr)[[1]]) { 86 | call = substitute(expr) 87 | stopifnot(is.call(call)) 88 | 89 | f = eval(call[[1]], env) 90 | args = eval_args(call[-1], env) 91 | 92 | do_invoke(f, args, call_env = call_env, f_expr = f_expr) 93 | } 94 | 95 | #' Invoke a function using an argument list that may contain promises 96 | #' 97 | #' @description 98 | #' Call a function using an explicit argument list that may contain [promise]s. 99 | #' This is a low-level interface intended to mimic [do.call()]; for a 100 | #' higher-level interface, see [invoke()]. 101 | #' 102 | #' @param f <[`closure`] | [`primitive`]> function to call 103 | #' @param args <[`arglist`] | [`list`] | [`pairlist`]> list of arguments 104 | #' to call the function with. 105 | #' @template param-invoke-call_env 106 | #' @template param-invoke-f_expr 107 | #' 108 | #' @details 109 | #' This is intended as an alternative to [do.call()] that provides 110 | #' better support for promises when calling [`closure`]s. In particular, 111 | #' promises in `args` will be left as-is, allowing precise manipulation of the 112 | #' expressions and environments of the arguments to `f` by using functions like 113 | #' [promise()], [promises()], [capture()], [capture_all()], etc. 114 | #' 115 | #' @template details-invoke-f_expr 116 | #' 117 | #' @template returns-invoke 118 | #' 119 | #' @seealso [invoke()], the higher-level interface to [do_invoke()] intended 120 | #' for normal use. 121 | #' 122 | #' @examples 123 | #' # TODO 124 | #' 125 | #' @export 126 | do_invoke = function(f, args, call_env = parent.frame(), f_expr = substitute(f)) { 127 | # a simpler version of the below would be something like: 128 | # > do.call(f, args, envir = env) 129 | # however this would lead to the function call appearing as the 130 | # full function body in things like match.call(). 131 | switch(typeof(f), 132 | closure = { 133 | # for closures, we can manually construct the call such that the 134 | # following functions work when used in the closure: 135 | # (1) sys.call() / match.call() will pick up a nice-looking call 136 | # (2) substitute() gives nice-looking expressions 137 | # (3) parent.frame() gives the the same frame the user called us from 138 | arg_exprs = lapply(args, promise_expr) 139 | call = as.call(c(list(f_expr), arg_exprs)) 140 | apply_closure(call, f, args, call_env) 141 | }, 142 | special = { 143 | # for primitives / builtins we can at least ensure the calling frame 144 | # is the same frame the user called us from 145 | do.call(f, args, envir = call_env) 146 | }, 147 | stop("`f` must be a function, not a ", typeof(f)) 148 | ) 149 | } 150 | 151 | 152 | # arg list helpers -------------------------------------------------------- 153 | 154 | #' Evaluate a list of argument expressions in an environment 155 | #' @param arg_exprs <[`list`]> argument expressions. May contain `...`, which 156 | #' will be turned into a list of arguments passed down from the surrounding 157 | #' context. May also contain arguments with the name `...` pointing at lists, 158 | #' which will be spliced into the argument list 159 | #' @param env <[`environment`]> frame to evaluate arguments in 160 | #' @noRd 161 | eval_args = function(arg_exprs, env) { 162 | args = as.list(arg_exprs) 163 | 164 | # convert f(...) into f(... = ) so it can be 165 | # spliced in by splice_dots() 166 | is_dots = vapply(args, identical, quote(...), FUN.VALUE = logical(1)) 167 | if (any(is_dots)) { 168 | if (is.null(names(args))) names(args) = rep("", length(args)) 169 | dots_i = which(is_dots) 170 | stopifnot(length(dots_i) == 1) 171 | args[[dots_i]] = capture_dots(env) 172 | names(args)[[dots_i]] = "..." 173 | } 174 | 175 | args[!is_dots] = lapply(args[!is_dots], eval, envir = env) 176 | 177 | splice_dots(args) 178 | } 179 | 180 | #' Splice `...` arguments into an argument list 181 | #' @param args <[`list`]> argument list that may include any number of arguments 182 | #' named `...` pointing to list-like objects. 183 | #' @returns list of arguments where elements named `...` have had their contents 184 | #' spliced into the list 185 | #' @noRd 186 | splice_dots = function(args) { 187 | if (any(names(args) == "...")) { 188 | arg_lists = lapply(seq_along(args), function(i) { 189 | name = names(args)[[i]] 190 | if (identical(name, "...")) { 191 | as.list(args[[i]]) 192 | } else { 193 | setNames(list(args[[i]]), name) 194 | } 195 | }) 196 | args = do.call(c, arg_lists) 197 | } 198 | 199 | args 200 | } 201 | 202 | 203 | # apply_closure ----------------------------------------------------------- 204 | 205 | #' Call a closure directly without wrapping arguments in [`promise`]s. 206 | #' @param call <[`call`]> expression of the function call with its arguments. 207 | #' This is not evaluated, but will be available in the closure as [sys.call()]. 208 | #' @param fun <[`closure`]> the function to call. 209 | #' @param args list of arguments. 210 | #' @param env <[`environment`]> environment to call the function from. 211 | #' @noRd 212 | apply_closure = function(call, fun, args, env) { 213 | apply_closure_(call, fun, as.pairlist(args), env) 214 | } 215 | -------------------------------------------------------------------------------- /R/autopartial.R: -------------------------------------------------------------------------------- 1 | # constructors ------------------------------------------------------------ 2 | 3 | #' Automatic partial function application 4 | #' 5 | #' @description 6 | #' Construct an `autopartial` function; that is, a function that supports 7 | #' *automatic partial application*. 8 | #' 9 | #' When a function created by `autopartial()` is called, 10 | #' if all of its required arguments have not been provided, it returns a 11 | #' modified version of itself that uses the arguments passed to it so far as 12 | #' defaults. Once all required arguments have been supplied, the function is 13 | #' evaluated. 14 | #' 15 | #' `autopartial()` can be considered a form of 16 | #' [currying](https://en.wikipedia.org/wiki/Currying) with respect to 17 | #' a function's required arguments, but I think *automatic partial application* 18 | #' gets the idea across more clearly. `autopartial()` also has a number of 19 | #' features, like named arguments and defaults, which are not typically 20 | #' considered part of the classical definition of currying. See **Details**. 21 | #' 22 | #' @param .f <[`closure`] | [`primitive`]> function to turn into an automatically 23 | #' partially-applied function. 24 | #' @param ... optional arguments to be partially applied to `.f`. 25 | #' 26 | #' @details 27 | #' Create an *automatically partially-applied* function by passing it to 28 | #' `autopartial()`. The resulting function can be called repeatedly until 29 | #' all required arguments (i.e. those that do not have default values 30 | #' provided in the function definition) have been supplied, then the function is 31 | #' evaluated and the result returned. 32 | #' 33 | #' For example, consider the function `f`: 34 | #' 35 | #' ```{r} 36 | #' f = function(x, y, z, a = 4) c(x, y, z, a) 37 | #' f = autopartial(f) 38 | #' f 39 | #' ``` 40 | #' 41 | #' It can be called as normal: 42 | #' 43 | #' ```{r} 44 | #' f(1, 2, 3) 45 | #' ``` 46 | #' 47 | #' If all required arguments haven't been supplied yet, the result is a 48 | #' partially-applied function: 49 | #' 50 | #' ```{r} 51 | #' f(1) 52 | #' ``` 53 | #' 54 | #' These invocations can be chained in arbitrary combinations: 55 | #' 56 | #' ```{r} 57 | #' f(1)(2)(3) 58 | #' f(z = 3)(1, 2) 59 | #' f(y = 2, z = 3)(1) 60 | #' f(y = 2)(1)(3) 61 | #' # etc ... 62 | #' ``` 63 | #' 64 | #' Positional arguments can be supplied by position or by name. Named arguments 65 | #' can be supplied anywhere in the sequence; e.g. `f(a = 7)(1, 2, 3)` is 66 | #' equivalent to `f(1, 2, 3, a = 7)`. 67 | #' 68 | #' Arguments supplied in one partial call can be overridden later in the sequence. 69 | #' For example, `f(1)(x = 2)(x = 3)` is equivalent to just `f(x = 3)`. 70 | #' 71 | #' Arguments may also be passed the special value [DEFAULT()]. If [DEFAULT()] is 72 | #' passed to an argument, its default value (or the most recently-partially-applied 73 | #' non-[`DEFAULT`] value) is used instead. For example, `f(a = DEFAULT())` is 74 | #' equivalent to `f(a = 4)` (since the default value of `a` is `4` in the 75 | #' definition of `f`), and `f(x = 1)(x = DEFAULT())` is equivalent to `f(x = 1)`. 76 | #' 77 | #' @section Implementation details: 78 | #' Great pains are taken to ensure that `autopartial` functions act as much as 79 | #' possible like normal R functions. 80 | #' 81 | #' The initial definition of an `autopartial` function has the same 82 | #' [formals()] as the function it wraps. When it is further partially applied, 83 | #' the [formals()] are updated to reflect the expressions of the modified 84 | #' arguments. To allow for sequences of applications of positional arguments 85 | #' (e.g. `f(1)(2)(3)`) to be equivalent to a single application (e.g. 86 | #' `f(1, 2, 3)`), positional arguments are moved to the back of the [formals()] 87 | #' list when they are applied. 88 | #' 89 | #' During partial application, arguments are stored as [`promise`]s and will not 90 | #' be evaluated until the underlying function is ultimately called (and even 91 | #' then, an argument may not be evaluated if that function does not evaluate 92 | #' the argument in question---just like normal R functions). Thus, non-standard 93 | #' evaluation constructs like [substitute()] should work correctly within the 94 | #' underlying function. 95 | #' 96 | #' [DEFAULT()] values are detected, as much as possible, without evaluating 97 | #' arguments: a [DEFAULT()] is valid only if it is stored in a symbol passed to 98 | #' an argument or if `DEFAULT()` is passed directly to the argument. 99 | #' 100 | #' The final function evaluation acts as much as possible like a normal function 101 | #' evaluation. The underlying function is called from the same environment that 102 | #' the final `autopartial` function is called from, so functions that inspect 103 | #' their context (e.g. using [parent.frame()]) should work correctly. The call 104 | #' expression associated with the function invocation is also constructed 105 | #' to reflect all of the partially-applied arguments and the original function 106 | #' name, so functions that use [sys.call()] or [match.call()] should also give 107 | #' nice-looking output. 108 | #' 109 | #' For example, the [density()] function, which uses both [substitute()] and 110 | #' [match.call()] to construct labels for its output, gives identical output 111 | #' if invoked directly (e.g. `density(rnorm(10))`) or via `autopartial()` 112 | #' (e.g. `autopartial(density)(rnorm(10))`). 113 | #' 114 | #' Under the hood, `autopartial()` uses [new_autopartial()] to create the 115 | #' *automatically partially-applied* function, captures intermediate arguments 116 | #' as [`promise`]s using [capture_all()], and ultimately uses [invoke()] 117 | #' to call the underlying function. 118 | #' 119 | #' @returns 120 | #' A modified version of `.f` that will automatically be partially 121 | #' applied until all of its required arguments have been given. 122 | #' 123 | #' @family partial function constructors 124 | #' 125 | #' @examples 126 | #' # create a custom automatically partially-applied function 127 | #' f = autopartial(function(x, y, z = 3) (x + y) * z) 128 | #' f() 129 | #' f(1) 130 | #' g = f(y = 2)(z = 4) 131 | #' g 132 | #' g(1) 133 | #' 134 | #' # pass DEFAULT() to optional arguments to use existing values 135 | #' f(z = DEFAULT())(1, 2) # uses default z = 3 136 | #' f(z = 4)(z = DEFAULT())(1, 2) # uses z = 4 137 | #' 138 | #' @export 139 | autopartial = function(.f, ...) { 140 | args = remove_defaults(match_function_args(.f, capture_dots())) 141 | new_autopartial(.f, args, f_expr = substitute(.f)) 142 | } 143 | 144 | #' Partial function application 145 | #' 146 | #' @description 147 | #' Partially apply a function. 148 | #' 149 | #' @param .f <[`closure`] | [`primitive`]> function to be partially applied. 150 | #' @param ... arguments to be partially applied to `.f`. 151 | #' 152 | #' @details 153 | #' Partially applies the provided arguments to the function `.f`. Acts like 154 | #' [autopartial()], except that the next invocation will evaluate the function 155 | #' rather than waiting for all required arguments to be supplied. 156 | #' 157 | #' Arguments may also be passed the special value [DEFAULT()]. If [DEFAULT()] is 158 | #' passed to an argument, its default value is used instead. 159 | #' 160 | #' Great pains are taken to ensure that the resulting function acts as much as 161 | #' possible like the original function. See the **Implementation details** 162 | #' section of [autopartial()] for more information. 163 | #' 164 | #' @returns 165 | #' A modified version of `.f` with the arguments in `...` applied to it. 166 | #' 167 | #' @family partial function constructors 168 | #' 169 | #' @examples 170 | #' f = function(x, y, z = 3) c(x, y, z) 171 | #' fp = partial(f, 1, z = 4) 172 | #' fp 173 | #' fp(2) 174 | #' 175 | #' @export 176 | partial = function(.f, ...) { 177 | args = remove_defaults(match_function_args(.f, capture_dots())) 178 | new_autopartial(.f, args, required_arg_names = character(), f_expr = substitute(.f)) 179 | } 180 | 181 | #' Low-level constructor for automatically partially-applied functions 182 | #' 183 | #' @description 184 | #' Construct a version of the function `f` that is partially applied when called 185 | #' unless all required arguments have been supplied. This is a low-level 186 | #' constructor that should be used only if you need to manually adjust 187 | #' `required_arg_names`, `allow_defaults`, or `f_expr`. In most cases, you should use 188 | #' the higher-level interfaces [autopartial()] or [partial()]. 189 | #' 190 | #' @param f <[`closure`] | [`primitive`]> function to automatically partially-apply. 191 | #' @param args <[`list`] | [`pairlist`] | [`arglist`]> arguments to apply now. 192 | #' @param required_arg_names <[`character`]> names of required arguments 193 | #' in `f`. When all of these have been supplied, the function will be evaluated. 194 | #' The default, `find_required_arg_names(f)`, considers all arguments without a 195 | #' default value in the function definition to be required. Pass `NULL` or 196 | #' `character()` to get traditional (non-automatic) partial application. 197 | #' @param allow_defaults <[`logical`]> if `TRUE`, if you pass `DEFAULT()` to an 198 | #' argument to this function, whatever value that argument already has will be 199 | #' used instead. 200 | #' @template param-invoke-f_expr 201 | #' 202 | #' @returns a [`function`] that when called will be partially applied until all 203 | #' of the arguments in `required_arg_names` have been supplied. 204 | #' 205 | #' @family partial function constructors 206 | #' 207 | #' @examples 208 | #' # TODO 209 | #' 210 | #' @export 211 | new_autopartial = function( 212 | f, 213 | args = arglist(), 214 | required_arg_names = find_required_arg_names(f), 215 | allow_defaults = TRUE, 216 | f_expr = substitute(f) 217 | ) { 218 | stopifnot( 219 | "`f_expr` must be a language object" = is.language(f_expr), 220 | "`f` must be a function" = is.function(f), 221 | "`f` cannot be a primitive function without an argument list, like `if`" = !is.null(args(f)), 222 | "`args` must be a list" = is.list(args) || is.pairlist(args), 223 | "`required_arg_names` must be a character vector" = is.character(required_arg_names) || is.null(required_arg_names), 224 | "`allow_defaults` must be a scalar logical" = is.logical(allow_defaults) && length(allow_defaults) == 1 225 | ) 226 | 227 | # we use these weird names to avoid clashing with argument names in f, 228 | # because partial_f will have a signature containing the same formals as f, 229 | # so if those formals include the names f, args, etc, things would break 230 | `>f` = f 231 | `>args` = args 232 | `>required_arg_names` = required_arg_names 233 | `>allow_defaults` = allow_defaults 234 | `>f_expr` = f_expr 235 | 236 | partial_f = function() { 237 | new_args = capture_all() 238 | if (`>allow_defaults`) new_args = remove_defaults(new_args) 239 | args = update_args(`>args`, new_args) 240 | 241 | if (all(`>required_arg_names` %in% names(args))) { 242 | do_invoke(`>f`, args, call_env = parent.frame(), f_expr = `>f_expr`) 243 | } else { 244 | new_autopartial(`>f`, args, `>required_arg_names`, `>allow_defaults`, `>f_expr`) 245 | } 246 | } 247 | partial_formals = formals(args(f)) 248 | # update expressions in formals to match provided args 249 | updated_formal_names = intersect(names(partial_formals), names(args)) 250 | partial_formals[updated_formal_names] = lapply(args[updated_formal_names], promise_expr) 251 | # move any positional args that have been applied to the end. this allows 252 | # f(1)(2)(3)... to be equivalent to f(1, 2, 3, ...) if positions 1, 2, 3, ... 253 | # correspond to required arguments. 254 | positional_arg_names = find_required_arg_names(f) 255 | is_updated_required = names(partial_formals) %in% intersect(updated_formal_names, positional_arg_names) 256 | partial_formals = c(partial_formals[!is_updated_required], partial_formals[is_updated_required]) 257 | formals(partial_f) = partial_formals 258 | 259 | attr(partial_f, "f") = f 260 | attr(partial_f, "args") = args 261 | attr(partial_f, "allow_defaults") = allow_defaults 262 | attr(partial_f, "f_expr") = f_expr 263 | class(partial_f) = c("uneval_autopartial", "function") 264 | partial_f 265 | } 266 | 267 | 268 | # printing ---------------------------------------------------------------- 269 | 270 | #' @export 271 | print.uneval_autopartial = function(x, ..., width = getOption("width")) { 272 | cat0(":\n") 273 | 274 | f_expr = attr(x, "f_expr") 275 | if (!is.symbol(f_expr)) f_expr = "." 276 | cat0(f_expr, " = ") 277 | 278 | f = attr(x, "f") 279 | f_string = utils::capture.output(print(f, width = width - 2, ...)) 280 | cat(f_string, sep = "\n ") 281 | 282 | cat0(format(as.call(c( 283 | list(f_expr), 284 | lapply(attr(x, "args"), promise_expr) 285 | )))) 286 | 287 | invisible(x) 288 | } 289 | 290 | 291 | # helpers ----------------------------------------------------------------- 292 | 293 | #' Given a function and a list of arguments, return a modified version of the 294 | #' argument list where named arguments have been matched according to R's 295 | #' argument-matching rules. 296 | #' @param f a function 297 | #' @param args a list of arguments, such as returned by [arglist()]. 298 | #' Should not contain a `...` argument. 299 | #' @returns a standardized list of arguments (i.e. where all arguments with 300 | #' names are named and in order) that can be supplied to `f`, or an error 301 | #' if `args` is not a valid argument list for `f`. 302 | #' @noRd 303 | match_function_args = function(f, args) { 304 | # use match.call to figure out the names of arguments and the 305 | # argument order 306 | args_i = seq_along(args) 307 | names(args_i) = names(args) 308 | call = as.call(c(list(quote(f)), args_i)) 309 | args_i_call = match.call(args(f), call)[-1] 310 | args_i = as.integer(as.list(args_i_call)) 311 | 312 | # fill in names and re-order the args 313 | names(args)[args_i] = names(args_i_call) 314 | args[args_i] 315 | } 316 | 317 | #' Update a list of arguments, overwriting named arguments in `old_args` 318 | #' with values that appear in `new_args`. Positional arguments from both 319 | #' argument lists are kept in the same order as they appear. 320 | #' @param old_args a list of arguments 321 | #' @param new_args a list of arguments 322 | #' @returns a list of arguments 323 | #' @noRd 324 | update_args = function(old_args, new_args) { 325 | if (is.null(names(old_args))) names(old_args) = rep("", length(old_args)) 326 | if (is.null(names(new_args))) names(new_args) = rep("", length(new_args)) 327 | 328 | old_names = names(old_args) 329 | old_names = old_names[nzchar(old_names)] 330 | new_names = names(new_args) 331 | updated_names = intersect(old_names, new_names) 332 | old_args[updated_names] = new_args[updated_names] 333 | 334 | c(old_args, new_args[!names(new_args) %in% updated_names]) 335 | } 336 | 337 | #' Return the names of required arguments for function `f` 338 | #' @param f A function 339 | #' @returns character vector of argument names 340 | #' @noRd 341 | find_required_arg_names = function(f) { 342 | args = formals(args(f)) 343 | is_missing = vapply(args, is_missing_arg, logical(1)) 344 | setdiff(names(args)[is_missing], "...") 345 | } 346 | 347 | #' Is `x` a missing argument? 348 | #' @param an argument 349 | #' @returns `TRUE` if `x` represents a missing argument 350 | #' @noRd 351 | is_missing_arg = function(x) { 352 | missing(x) || identical(x, quote(expr = )) 353 | } 354 | --------------------------------------------------------------------------------