What and where are commonly useful function operators?

Function operators (FOs) seem like a great way to increase productivity and make the best of R's functional programming chops, perhaps especially in package development.
Keeps functions nice and small, without having to built a gazillion wrappers and passing around arguments like a crazy person.

In @hadley's words:

A function operator is a function that takes one (or more) functions as input and returns a function as output.

(More details here).

Ideally, of course, I don't want to write many FOs myself, but increase my usage of existing FOs from other packages.

My questions are:

  1. What function operators do you recommend and/or find useful?
  2. What good sources (packages?) for function operators are out there?
  3. If this is a stupid question or a bad idea (... "pointless programming"), please set me straight.

For example, I'm looking for an FO which captures file outputs of functions.

3 Likes

I'm aware of some, but was wondering whether there might be more:

  • base R has some, of course, including base::Negate()
  • purrr also has a bunch, of course (called adverbs), such as purrr::possibly()
  • good old plyr also has some, such as plyr::failwith().
  • then there's memoise
  • functools also lists a bunch of FOs, but I'm not familiar with that package.
1 Like

I would like to add to your list

  • the solutions to Hadley Wickham’s book "Advanced R" contributed by Malte Grosser, Henning Bumann, Peter Hurford and Robert Krzyzanowski (see chapters: Behavioural FOs, Output FOs, Input FOs, Combining FOs)
5 Likes

that's a great resource, thanks for pointing me to that!

I skimmed all the functions from base and utils looking for function operators and I only found base::Negate() and base::Vectorize() though I might have missed some.

utils::getS3method() is "almost" a function operator as it takes a function name as a string and returns a function.

Another function that modifies a function (its behavior at least) is trace() but it doesn't return a function.

base::body<- and base::formals<- can also be used to modify a function, but they don't return a function either.

1 Like

I'm not sure if this is what you had in mind in terms of increasing productivity in general programming, but the color palette and interpolation functions are some additional examples of functions that return functions. For example:

library(tidyverse)
library(scales)

# Color palette functions
color_fnc = colorRamp(c("blue", "red", "yellow"))
color_fnc(seq(0,1,0.2))
color_fnc(seq(0,1,0.2)) %>% rgb(., maxColorValue=255) %>% show_col()

color_fnc = colorRampPalette(c("blue", "red", "yellow"))
color_fnc(3)
color_fnc(6) 
show_col(color_fnc(6))

# Linear and spline interpolation
interp = approxfun(1:10, (1:10)^3)
spline.interp = splinefun(1:10, (1:10)^3)

pts = seq(2,3,length=5)
plot(pts, spline.interp(pts), col="blue", type="b")
points(pts, interp(pts), col="red", type="b")

Following Hadley's definitions these are function factories but not function operators, as these don't have a function input.

2 Likes

thanks @joels, I was indeed more looking for function operators; I was simply wondering whether there might be a list of really cool FOs somewhere, or what FOs people are using.

I'm on it but I might need a few more weeks to get it right :

1 Like

Actually body<- and formals<- are legit function operators, I wasn't thinking straight. In their standard form body(f) <- rhs the expression returns the rhs, but that's not relevant.

I found about crayon which has the clever the function operator crayon:::`$.crayon` to grow the _styles attribute of it's input.

crayon:::`$.crayon`
function (crayon, style) 
{
    attr(crayon, "_styles") <- c(attr(crayon, "_styles"), data_env$my_styles[style])
    crayon
}

library(crayon)
cat(blue("hello"))
cat(blue$bold("hello"))

@maxheld83 maybe you'll be interested in this, I updated my package and it's full of new function operators and an API to easily build them or use them. If you are I'll be happy to read your thoughts.


EDIT:

@maxheld83 these packages are now ready to be used, there's more than 50 tags (function operator factories). I made adverb factories from all the purrr and base adverbs, all the with_* functions from withr, progress::progress_bar and much more. See README intro below :

This package consists of a collection of tags built using the functions and
principle designed and described in the package tag. See the read me at :
https://github.com/moodymudskipper/tag.

It allows things like the following :

library(tags)
df_name <- "iris"
# use quasi quotation with any function
using_bang$head(!!rlang::sym(df_name),1)
# use purrr adverbs with tag syntax
using_possibly$log("a", .otherwise = NA_real_)
# use withr functions with tag syntax
setting_options(list(scipen = 100))$print(exp(100))

Installation and setup :

# devtools::install_github("moodymudskipper/tag")
# devtools::install_github("moodymudskipper/tags")
library(tags)
suppressPackageStartupMessages(library(tidyverse,warn.conflicts = FALSE))

The package contains over 50 tags that can be divided into the
following categories.

  • tag counterparts to base and purrr adverbs :
    • vectorizing (wraps base::Vectorize)
    • negating (wraps base::Negate)
    • using_safely (borrows code from purrr::safely)
    • using_quietly (borrows code from purrr::quietly)
    • using_possibly (borrows code from purrr::possibly)
  • tag counterparts to withr functions :
    • all 31 withr::with_* functions have tags::setting_* counterparts
  • tag expanding tidyverse features :
    • using_bang to make any function compatible with quasi-quotation
    • using_lambda to use formula notation in any functional
    • grouping_by to use dplyr::group_by on a single operation
    • using_rowwise to use dplyr::rowwise on a single operation
    • selecting_dots, renaming_dots, mutating_dots, transmuting_dots,
      reversing_dots to edit the dots passed to a call before it's executed,
      using dplyr semantics.
  • tags focused on side effects
    • logging to print call and execution time (wraps Sys.time())
    • suppressing_warnings to selectively suppress warnings based on a regular
      expression.
    • viewing (wraps utils::View)
    • debugging (wraps base::debugonce)
    • progressing to add a progress bar to any functional (wraps progress::progress_bar)
    • beeping to play a sound once the call is over (wraps beepr::beep)
    • popping_up to trigger a message box once the call is over (wraps tcltk::tk_messageBox)
  • other tags
    • composing as an alternative to piping/functional sequences (wraps magrittr's functional sequences)
    • using as the tag counterpart to with, with extra features
    • mapping to loop over argument values (wraps foreach::foreach)
    • self_referencing to avoid variable repetition with functions such as
      base::transform / dplyr::mutate, dplyr::summarize
    • tracing as a tag counterpart to base::trace
    • enclosing to enclose the input function in another function.
    • preserving_attr to make sure some attributes are preserved
    • checking_args to operate checks on arguments before calling the function
2 Likes

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.