knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.path = "README-" ) options(width = 108)
library(dplyr) library(magrittr) library(purrr) library(rlang) library(lambdass)
The purpose of this package is to provide you with an easy syntax for making an anonymous function.
rlang::new_function()
, rlang::as_closure()
https://github.com/tidyverse/rlangpryr::f()
, pryr::make_function()
https://github.com/hadley/pryrlambda.r::"%as%"
https://github.com/zatonovo/lambda.rlambdaR::lambda()
https://github.com/hoxo-m/lambdaR Family of tidyverse functions such as dplyr::mutate_if
and purrr::map
whose formal arguments include .predicate
, .f
, and/or .funs
for a closure object can accept formula
notation with ..1
or .x
which is converted to a closure by rlang::as_closure()
.
See the comparison of usage below.
if ("package:lambdass" %in% search()) detach("package:lambdass") library(purrr) microbenchmark::microbenchmark( 1 %>% map(~ ..1 + 1), # purrr::map with rlang:::as_closure 1 %>% map(~ .x + 1), # purrr::map with rlang:::as_closure times = 1e3 ) suppressPackageStartupMessages(library("lambdass")) microbenchmark::microbenchmark( 1 %>% Map(~~ .. + 1, .), # base::Map with lamdass's double-tilde 1 %>% map(~~ .. + 1), # purrr::map with lambdass's double-tilde 1 %>% map(~~ ..1 + 1), # purrr::map with lambdass's double-tilde times = 1e3 )
# install.packages("devtools") devtools::install_github("tobcap/lambdass") library("lambdass")
can be written in three ways.
~~ .. + 1 f.(x, x + 1) x %->% {x + 1} # all means `add one` function(x) x + 1
Double-tilde with dotted symbol placeholder makes an anonymous function
like the usage of %
in Closure's lambda, _
in Scala's lambda, or #
in Mathematica's Pure Function. See details in the documents below.
A bounded variable can be specified by ..
which is a synonym for ..1
.
Arguments can be designated by ..1
, ..2
, up to ..5
.
~~ .. + 1 ~~ ..2 / ..1 Map(~~ .. ^ 2, 1:10) Reduce(~~ ..1 + ..2, 1:10)
The placeholder must be in order.
~~ ..2 + 1
Double-tilde cannot create curried-function such as function(x) function(y) x + y
.
The "De Bruijn index" is suitable for a notation of lambda calculas,
but double-tilde is designed to cooperate with normal use of R's functions,
not curried-function.
See https://en.wikipedia.org/wiki/De_Bruijn_index for "De Bruijn index".
A function name f()
is used in many situations, so I avoid using it and f.()
is adopted.
# f.(...arguments, body) f.(x, x + 1) f.(x, y, x + y) # can define curried-function f.(x, f.(y, x + y)) Map(f.(x, x ^ 2), 1:10) Reduce(f.(x, y, x + y), 1:10)
f.()
is now implemented by C.
The conceptual R code is defined by f.r()
.
identical(f.(x, y, x + y), f.r(x, y, x + y)) ## about ten times speed-up microbenchmark::microbenchmark( f.(x, y, x + y), f.r(x, y, x + y) )
right-hand side always needs {
because R cannot control the
strength of associativity for %infix-function%
.
x %->% {x + 1} # left-hand side is written in two ways: {x; y} %->% {x + y} `any-valid-varname`(x, y) %->% {x + y} # but recommend to use just `f`, for readability and simplicity f(x, y) %->% {x + y} Map(x %->% {x ^ 2}, 1:10) Reduce({x; y} %->% {x + y}, 1:10)
Arguments can be checked when using :
with the result of
tyepof(actual_argument)
as symbol
{x:integer} %->% {x * 2} f(x:character) %->% {paste(x, "test")}
When the default value is set to a parameter, its type-checking syntax is automatically added at head of function-body.
{x = 1L} %->% {x * 2} f(x:character, y = "") %->% {paste0(x, y)}
Note that it's nanoseconds
microbenchmark::microbenchmark( "f." = f.(x, x), "~~" = ~~ .., "%->%" = {x} %->% {x}, "as.function" = as.function(alist(x=, x)), "as.function.default" = as.function.default(alist(x=, x)), "function(x) x" = function(x) x ) # Unit: nanoseconds # expr min lq mean median uq max neval # f. 5350 6687.5 8073.96 8025.0 8916 34326 100 # ~~ 10699 12037.0 13704.19 13374.0 14712 28977 100 # %->% 138194 141983.5 160380.99 145772.5 170514 324978 100 # as.function 32543 33881.0 41360.74 36110.0 40567 188122 100 # as.function.default 22736 24295.5 27447.78 26302.0 29423 66869 100 # function(x) x 0 446.0 910.25 447.0 892 38338 100 microbenchmark::microbenchmark( "f." = f.(x, y, x + y), "~~" = ~~ ..1 + ..2, "%->%" = f(x, y) %->% {x + y}, "as.function" = as.function(alist(x=, y=, x + y)), "as.function.default" = as.function.default(alist(x=, y=, x + y)), "function(x, y) x+y" = function(x, y) x + y ) # Unit: nanoseconds # expr min lq mean median uq max neval # f. 6241 7134 8947.70 8471.0 9362.5 20507 100 # ~~ 13374 14712 19263.05 18724.0 20506.0 98965 100 # %->% 113676 119916 131948.24 122814.5 128163.0 327652 100 # as.function 33434 35663 41761.75 39006.5 44133.0 74001 100 # as.function.default 23627 25411 30964.91 27639.0 32766.0 73109 100 # function(x, y) x+y 0 446 602.64 447.0 892.0 4904 100
Comparison with rlang::as_closure()
microbenchmark::microbenchmark( ~~ .., ~~ ..1, as_closure(~ .x), as_closure(~ ..1), identity )
Apply closure
microbenchmark::microbenchmark( (~~ ..)(1) , (~~ ..1)(1) , as_closure(~ .x)(1), as_closure(~ ..1)(1), identity(1) )
By using f.(), R notation is almost as-is with the article written in Wikipedia.
https://github.com/TobCap/lambdass/blob/master/vignettes/ChurchEncoding.md
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.