| Title: | A Framework for Writing and Applying Function Decorators |
|---|---|
| Description: | A framework for creating function decorators in R. Decorators can be applied to functions and to S3, S4, S7 and R6 methods. |
| Authors: | Dyfan Jones [aut, cre] |
| Maintainer: | Dyfan Jones <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.0.0.9000 |
| Built: | 2026-10-26 19:54:54 UTC |
| Source: | https://github.com/DyfanJones/adorn |
%@% reads left to right: in a %@% b %@% f, a is the outermost
decorator and f the innermost function. A plain function(f) that returns a function also works as a
decorator on the left-hand side.
lhs %@% rhslhs %@% rhs
lhs |
A decorator. |
rhs |
A function, or another decorator to compose with. |
The decorated function, or a composed decorator if rhs is
itself a decorator.
loud <- decorator(function(f, ...) toupper(f(...))) %@% function() "hi" loud()loud <- decorator(function(f, ...) toupper(f(...))) %@% function() "hi" loud()
decorate() applies a decorator to a target: a function, or a method that
already exists on an S3, S4, S7 or R6 class. Use %@% to decorate a
function you are defining, and decorate() for code you did not write.
decorate(target, decorator, ...) decorate(target, signature) <- valuedecorate(target, decorator, ...) decorate(target, signature) <- value
target |
What to decorate; see the table above. |
decorator |
A decorator, or a stack of them built with |
... |
Arguments for specific kinds of target:
|
signature |
For the replacement form, the S7 class, S4 signature, S3 class name, or R6 method name(s), as above. |
value |
For the replacement form, the decorator to apply. |
target |
Example | Extra argument |
| a function | decorate(fn, d), the same as d %@% fn |
|
| the name of an S3 generic | decorate("print", d, class = "money") |
class |
| an S4 generic | decorate(area, d, signature = "Square") |
signature |
| an S7 generic | decorate(speak, d, class = Dog) |
class |
| the name of an R6 method | decorate("add", d, class = Counter) |
class
|
For S3 and R6 the method is named by a string, and class gives the class or
R6 generator. For S4 and S7 the generic is the target. The replacement form
(see below) works for all four systems.
For a function, the decorated function. For R6, the class generator, invisibly. For the others, the decorated method, invisibly; the method is replaced in place.
decorate("name", decorator, class = Generator) replaces the method in the
generator, so objects created afterwards use it. Objects that already exist
keep the old method, because R6 copies methods into each object when it is
created. An inherited method is copied into Generator and decorated there;
the parent class is unchanged. Give several names to decorate several methods.
decorate(target, class) <- decorator mirrors S7's method(generic, class) <- fn
and works for all four systems:
decorate(speak, Dog) <- shout # S7 decorate(area, "Square") <- shout # S4 decorate(print, "money") <- shout # S3, with the generic function decorate(Counter, "add") <- shout # R6, public methods
R does not allow a string as the target of a replacement call, so for S3 pass
the generic function. Its name is read from its UseMethod() call, so the
generic must call UseMethod() directly. For R6 the form reaches public
methods only; use the explicit form for where.
# A function shout <- decorator(function(f, ...) toupper(f(...))) greet <- decorate(function(who) paste("hello", who), shout) greet("world") # An existing S3 method area <- function(x, ...) UseMethod("area") area.square <- function(x, ...) x$side^2 decorate("area", shout, class = "square") area(structure(list(side = 3), class = "square")) # An R6 method if (requireNamespace("R6", quietly = TRUE)) { Greeter <- R6::R6Class("Greeter", public = list( hi = function(who) paste("hello", who) )) decorate("hi", shout, class = Greeter) Greeter$new()$hi("world") }# A function shout <- decorator(function(f, ...) toupper(f(...))) greet <- decorate(function(who) paste("hello", who), shout) greet("world") # An existing S3 method area <- function(x, ...) UseMethod("area") area.square <- function(x, ...) x$side^2 decorate("area", shout, class = "square") area(structure(list(side = 3), class = "square")) # An R6 method if (requireNamespace("R6", quietly = TRUE)) { Greeter <- R6::R6Class("Greeter", public = list( hi = function(who) paste("hello", who) )) decorate("hi", shout, class = Greeter) Greeter$new()$hi("world") }
decorator() turns an ordinary function into a decorator. The first
argument of wrapper receives the function being decorated. There are two
styles, chosen by whether wrapper has a ... argument:
decorator(wrapper, name = NULL)decorator(wrapper, name = NULL)
wrapper |
A function, as described above. |
name |
Name shown when printing the decorator and in
|
Call style, function(f, ..., <params>): the body runs on every call.
... holds the arguments of that call; forward them with f(...).
Factory style, function(f, <params>): the body runs once, when the
function is decorated, and returns the replacement function. Use this
when the decorator needs state, such as a cache.
Arguments after ... (call style) or after f (factory style) are
decorator parameters. Set them by calling the decorator:
retry(times = 5), for a decorator named retry.
If your decorator stores the result of f(...) before returning it, use
withVisible() to keep functions that return invisible() quiet.
Whatever the style, the decorated function keeps the formal arguments of
the original, so argument matching, missing(), defaults and
introspection all behave as before.
A decorator: a function that decorates a function when called with one, and returns a configured decorator when called with parameters.
# Call style: runs on every call shout <- decorator(function(f, ...) toupper(f(...))) # Call style with a parameter repeated <- decorator(function(f, ..., times = 2) { rep(f(...), times) }) # Factory style: keeps state between calls counted <- decorator(function(f) { n <- 0 function(...) { n <<- n + 1 message("call ", n) f(...) } }) greet <- shout %@% function(who) paste("hello", who) greet("world") ones <- repeated(times = 3) %@% function() 1 ones()# Call style: runs on every call shout <- decorator(function(f, ...) toupper(f(...))) # Call style with a parameter repeated <- decorator(function(f, ..., times = 2) { rep(f(...), times) }) # Factory style: keeps state between calls counted <- decorator(function(f) { n <- 0 function(...) { n <<- n + 1 message("call ", n) f(...) } }) greet <- shout %@% function(who) paste("hello", who) greet("world") ones <- repeated(times = 3) %@% function() 1 ones()
Call this from inside a decorator to find out which function it is currently wrapping.
decorator_context()decorator_context()
A list with name (the name the function is bound to, or
"<anonymous>"), decorator (the decorator's name, see decorator()) and fn (the
function being wrapped).
who <- decorator(function(f, ...) { message("calling ", decorator_context()$name) f(...) }) hello <- who %@% function() "hi" hello()who <- decorator(function(f, ...) { message("calling ", decorator_context()$name) f(...) }) hello <- who %@% function() "hi" hello()
layers() is an alias for decorators(), and strip() for
undecorate().
undecorate(f) decorators(f)undecorate(f) decorators(f)
f |
A function. |
undecorate() returns the original, undecorated function (or f
itself if it is not decorated). decorators() returns the names of the
decorators applied, outermost first.