Coder Social home page Coder Social logo

nofrills's Introduction

Unless you need curry() or curry_fn(), you should use the more versatile gestalt package, which includes fn().

Travis-CI Build Status codecov CRAN_Status_Badge

nofrills

Low-Cost Anonymous Functions

Overview

nofrills is a lightweight R package that provides fn(), a more powerful variation of function() that:

  • costs less — enables tidyverse quasiquotation so you don’t pay the price of functional impurity

  • has the same great taste — supports a superset of function()’s syntax and capabilities

  • is less filling

    fn(x, y = 1 ~ x + y)

    is equivalent to

    function(x, y = 1) x + y

Installation

install.packages("nofrills")

Alternatively, install the development version from GitHub:

# install.packages("devtools")
devtools::install_github("egnha/nofrills")

Usage

Same syntax as function() but shorter

fn(x ~ x + 1)
#> function (x) 
#> x + 1

fn(x, y ~ x + y)
#> function (x, y) 
#> x + y

fn(x, y = 2 ~ x + y)
#> function (x, y = 2) 
#> x + y

fn(x, y = 1, ... ~ log(x + y, ...))
#> function (x, y = 1, ...) 
#> log(x + y, ...)

# the only exception, cf. alist()
fn(x, ... = , y ~ log(x + y, ...))
#> function (x, ..., y) 
#> log(x + y, ...)

fn(~ NA)
#> function () 
#> NA

Supports quasiquotation

Unquote values

z <- 0

fn(x, y = !!z ~ x + y)
#> function (x, y = 0) 
#> x + y

fn(x ~ x > !!z)
#> function (x) 
#> x > 0

Unquote argument names

arg <- "y"

fn(x, !!arg := 0 ~ x + !!as.name(arg))
#> function (x, y = 0) 
#> x + y

Splice in argument lists

args <- alist(x, y = 0)

fn(!!!args, ~ x + y)  # note the one-sided formula
#> function (x, y = 0) 
#> x + y

Literally unquote with QUQ(), QUQS()

library(dplyr)

summariser <- quote(mean)

my_summarise <- fn(df, ... ~ {
  group_by <- quos(...)
  df %>%
    group_by(QUQS(group_by)) %>%
    summarise(a = (!!summariser)(a))
})

my_summarise
#> function (df, ...) 
#> {
#>     group_by <- quos(...)
#>     df %>% group_by(`!!!`(group_by)) %>% summarise(a = mean(a))
#> }

(Source: Programming with dplyr)

Curry functions

Declare a curried function with curry_fn()

The syntax is the same as fn(). Using the literal unquoting operators QUQ(), QUQS(), you can “delay” unquoting to embed argument values in the innermost function:

compare_to <- curry_fn(target, x ~ identical(x, QUQ(target)))
is_this <- compare_to("this")

# The embedded value "this" renders the source comprehensible
is_this
#> function (x) 
#> identical(x, "this")
#> <environment: 0x7fdc55943678>

Curry a function with curry()

curry(function(x, y, z = 0) x + y + z)
#> function (x) 
#> function(y) function(z = 0) x + y + z

double <- curry(`*`)(2)
double(3)
#> [1] 6

Pure functions via quasiquotation

Functions in R are generally impure, i.e., the return value of a function will not in general be determined by the value of its inputs alone. This is because a function may depend on mutable objects in its lexical scope. Normally this isn’t an issue. But if you are working interactively and sourcing files into the global environment, say, or using a notebook interface (like Jupyter or R Notebook), it can be tricky to ensure that you haven’t unwittingly mutated an object that an earlier function depends upon.

  • Consider the following function:

    a <- 1
    foo <- function(x) x + a

    What is the value of foo(1)? It is not necessarily 2 because the value of a may have changed between the creation of foo() and the calling of foo(1):

    foo(1)
    #> [1] 2
    
    a <- 0
    
    foo(1)
    #> [1] 1

    In other words, foo() is impure because the value of foo(x) depends not only on the value of x but also on the externally mutable value of a.

fn() enables you to write pure(r) functions by using quasiquotation to eliminate such indeterminacy.

  • With fn(), you can unquote a to capture its value at the point of creation:

    a <- 1
    foo <- fn(x ~ x + !!a)

    Now foo() is a pure function, unaffected by changes in its lexical scope:

    foo(1)
    #> [1] 2
    
    a <- 0
    
    foo(1)
    #> [1] 2

Alternatives to nofrills

Alternative anonymous-function constructors (which don’t support quasiquotation) include:

Acknowledgement

The rlang package by Lionel Henry and Hadley Wickham makes nofrills possible. Crucially, rlang provides the engine for quasiquotation and expression capture.

License

MIT Copyright © 2017–22 Eugene Ha

nofrills's People

Contributors

egnha avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

nofrills's Issues

Curry via successive unquoting

For instance

curry(fn(x, y, z ~ x + y + z))

should yield

fn(x ~ !! fn(y ~ !! fn(z ~ x + y + z)))

(This can be accomplished with reduction.)

fn_interp.logical() should be strict

The selection boolean vector—the filter—should be exactly as long as the vector to be filtered. By default, R recycles filters. Since this is typically unintended, interpret a length mismatch as a bug. Disallow it at runtime.

fn_()

Variation of fn() that does not support quasiquotation.

Make partial() more efficiently applicable

Meaning, partial() should be operationally idempotent.

For instance, partial(partial(f, x = 1), y = 2) and partial(f, x = 1, y = 2) should have the same run-time characteristics. Ideally, they should be implemented as the same function.

Should printing be tested?

For instance, the printing of partial application is mildly complicated. Perhaps it should be tested.

magrittr semantics in composition

Implicit partialization and “dot” functions à la magrittr:

readLines %>>>% gsub(" ", "", .)
abs %>>>% {. + 1} %>>>% log

Unquote to preempt the default behavior:

inc <- 1
abs %>>>% {. + !!inc} %>>>% log
(!!safely(f)) %>>>% {paste(.$result, collapse = "")}
sprint("%s", .) %>>>% paste(collapse = "")

JavaScript-like anonymous function

Hey Eugene, I was wondering (out of sheer curiosity) if it is possible to implement Javascript's syntax for anonymous function in R and stumbled upon your package. It seems to be very cool!

Do you think it is viable to implement something like this in R?

(x, y) => {
    x + y
}

Avoid grepl() as predicate

It is slow, because base R has no (public) facility for reusing compiled regular expressions. (Relying on a package like ore is infeasible, since I want to avoid additional dependencies.) In particular, is_bare_dot_names() should be subsumed by a much cheaper predicate, e.g., a pre-computed, logical-vector attribute.

Vignette

Would be nice to include illustrative "case studies" (e.g., serializers, k-means, ad hoc analyses).

Deprecate curry()

Given a good-enough partial(), curry() seems to add little value, given how tricky it is to implement it with the ”right” semantics, quasiquotation support and acceptable performance:

  • In practice, partial application is the more useful operation
  • Currying, when genuinely desired, can be achieved by multiple partial applications
  • Multiple partial applications are practical because the original function is always the one that is partially applied—partial applications don't “pile up”
  • Calling semantics are complicated by the need to:
    1. Maintain conventionally calling semantics (because the semantics for true currying are not idiomatic in R)
    2. Accommodate quasiquotation (because partial() does)
    3. Maintain call associativity—e.g., equivalence of foo(a)(b) and foo(a, b)—in view of arguments with default values and the first two points

Type-consistent composition

Composition should produce an “optional type”: it should always be either NULL or a function of class CompositeFunction, even for a single function. This is required in order for generic methods to work predictably, e.g., as.list().

"Type" of dots, in practice

With regards to partial application and currying, there are two views on ...: 1) as a special kind of indecomposible type—how the interpreter sees it; 2) as an infinite product type (of mode any)—how the user sees it.

Choose the one that is compatible with typical use cases.

Derive formals from args()

Because this is what print.default() displays, which may diverge from rlang::as_closure(). Affects partial().

partial()

Unlike purrr::partial(), this should enable unquoting and splicing of arguments.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.