lift_xy()
is a composition helper. It helps you compose
functions by lifting their domain from a kind of input to another
kind. The domain can be changed from and to a list (l), a vector
(v) and dots (d). For example, lift_ld(fun)
transforms a
function taking a list to a function taking dots.
lift(..f, ..., .unnamed = FALSE) lift_dl(..f, ..., .unnamed = FALSE) lift_dv(..f, ..., .unnamed = FALSE) lift_vl(..f, ..., .type) lift_vd(..f, ..., .type) lift_ld(..f, ...) lift_lv(..f, ...)
..f  A function to lift. 

...  Default arguments for 
.unnamed  If 
.type  A vector mold or a string describing the type of the
input vectors. The latter can be any of the types returned by

A function.
The most important of those helpers is probably lift_dl()
because it allows you to transform a regular function to one that
takes a list. This is often essential for composition with purrr
functional tools. Since this is such a common function,
lift()
is provided as an alias for that operation.
Here dots should be taken here in a figurative way. The lifted
functions does not need to take dots per se. The function is
simply wrapped a function in do.call()
, so instead
of taking multiple arguments, it takes a single named list or
vector which will be interpreted as its arguments. This is
particularly useful when you want to pass a row of a data frame
or a list to a function and don't want to manually pull it apart
in your function.
These factories allow a function taking a vector to take a list
or dots instead. The lifted function internally transforms its
inputs back to an atomic vector. purrr does not obey the usual R
casting rules (e.g., c(1, "2")
produces a character
vector) and will produce an error if the types are not
compatible. Additionally, you can enforce a particular vector
type by supplying .type
.
lift_ld()
turns a function that takes a list into a
function that takes dots. lift_vd()
does the same with a
function that takes an atomic vector. These factory functions are
the inverse operations of lift_dl()
and lift_dv()
.
lift_vd()
internally coerces the inputs of ..f
to
an atomic vector. The details of this coercion can be controlled
with .type
.
### Lifting from ... to list(...) or c(...) x < list(x = c(1:100, NA, 1000), na.rm = TRUE, trim = 0.9) lift_dl(mean)(x)#> [1] 51#> [1] 51# You can also use the lift() alias for this common operation: lift(mean)(x)#> [1] 51# Default arguments can also be specified directly in lift_dl() list(c(1:100, NA, 1000)) %>% lift_dl(mean, na.rm = TRUE)()#> [1] 59.90099# lift_dl() and lift_ld() are inverse of each other. # Here we transform sum() so that it takes a list fun < sum %>% lift_dl() fun(list(3, NA, 4, na.rm = TRUE))#> [1] 7# Now we transform it back to a variadic function fun2 < fun %>% lift_ld() fun2(3, NA, 4, na.rm = TRUE)#> [1] 7# It can sometimes be useful to make sure the lifted function's # signature has no named parameters, as would be the case for a # function taking only dots. The lifted function will take a list # or vector but will not match its arguments to the names of the # input. For instance, if you give a data frame as input to your # lifted function, the names of the columns are probably not # related to the function signature and should be discarded. lifted_identical < lift_dl(identical, .unnamed = TRUE) mtcars[c(1, 1)] %>% lifted_identical()#> [1] TRUEmtcars[c(1, 2)] %>% lifted_identical()#> [1] FALSE# ### Lifting from c(...) to list(...) or ... # In other situations we need the vectorvalued function to take a # variable number of arguments as with pmap(). This is a job for # lift_vd(): pmap(mtcars, lift_vd(mean))#> [[1]] #> [1] 29.90727 #> #> [[2]] #> [1] 29.98136 #> #> [[3]] #> [1] 23.59818 #> #> [[4]] #> [1] 38.73955 #> #> [[5]] #> [1] 53.66455 #> #> [[6]] #> [1] 35.04909 #> #> [[7]] #> [1] 59.72 #> #> [[8]] #> [1] 24.63455 #> #> [[9]] #> [1] 27.23364 #> #> [[10]] #> [1] 31.86 #> #> [[11]] #> [1] 31.78727 #> #> [[12]] #> [1] 46.43091 #> #> [[13]] #> [1] 46.5 #> #> [[14]] #> [1] 46.35 #> #> [[15]] #> [1] 66.23273 #> #> [[16]] #> [1] 66.05855 #> #> [[17]] #> [1] 65.97227 #> #> [[18]] #> [1] 19.44091 #> #> [[19]] #> [1] 17.74227 #> #> [[20]] #> [1] 18.81409 #> #> [[21]] #> [1] 24.88864 #> #> [[22]] #> [1] 47.24091 #> #> [[23]] #> [1] 46.00773 #> #> [[24]] #> [1] 58.75273 #> #> [[25]] #> [1] 57.37955 #> #> [[26]] #> [1] 18.92864 #> #> [[27]] #> [1] 24.77909 #> #> [[28]] #> [1] 24.88027 #> #> [[29]] #> [1] 60.97182 #> #> [[30]] #> [1] 34.50818 #> #> [[31]] #> [1] 63.15545 #> #> [[32]] #> [1] 26.26273 #># lift_vd() will collect the arguments and concatenate them to a # vector before passing them to ..f. You can add a check to assert # the type of vector you expect: lift_vd(tolower, .type = character(1))("this", "is", "ok")#> [1] "this" "is" "ok"# ### Lifting from list(...) to c(...) or ... # cross() normally takes a list of elements and returns their # cartesian product. By lifting it you can supply the arguments as # if it was a function taking dots: cross_dots < lift_ld(cross) out1 < cross(list(a = 1:2, b = c("a", "b", "c"))) out2 < cross_dots(a = 1:2, b = c("a", "b", "c")) identical(out1, out2)#> [1] TRUE# This kind of lifting is sometimes needed for function # composition. An example would be to use pmap() with a function # that takes a list. In the following, we use some() on each row of # a data frame to check they each contain at least one element # satisfying a condition: mtcars %>% pmap(lift_ld(some, partial(`<`, 200)))#> [[1]] #> [1] FALSE #> #> [[2]] #> [1] FALSE #> #> [[3]] #> [1] FALSE #> #> [[4]] #> [1] TRUE #> #> [[5]] #> [1] TRUE #> #> [[6]] #> [1] TRUE #> #> [[7]] #> [1] TRUE #> #> [[8]] #> [1] FALSE #> #> [[9]] #> [1] FALSE #> #> [[10]] #> [1] FALSE #> #> [[11]] #> [1] FALSE #> #> [[12]] #> [1] TRUE #> #> [[13]] #> [1] TRUE #> #> [[14]] #> [1] TRUE #> #> [[15]] #> [1] TRUE #> #> [[16]] #> [1] TRUE #> #> [[17]] #> [1] TRUE #> #> [[18]] #> [1] FALSE #> #> [[19]] #> [1] FALSE #> #> [[20]] #> [1] FALSE #> #> [[21]] #> [1] FALSE #> #> [[22]] #> [1] TRUE #> #> [[23]] #> [1] TRUE #> #> [[24]] #> [1] TRUE #> #> [[25]] #> [1] TRUE #> #> [[26]] #> [1] FALSE #> #> [[27]] #> [1] FALSE #> #> [[28]] #> [1] FALSE #> #> [[29]] #> [1] TRUE #> #> [[30]] #> [1] FALSE #> #> [[31]] #> [1] TRUE #> #> [[32]] #> [1] FALSE #># Default arguments for ..f can be specified in the call to # lift_ld() lift_ld(cross, .filter = `==`)(1:3, 1:3) %>% str()#> List of 6 #> $ :List of 2 #> ..$ : int 2 #> ..$ : int 1 #> $ :List of 2 #> ..$ : int 3 #> ..$ : int 1 #> $ :List of 2 #> ..$ : int 1 #> ..$ : int 2 #> $ :List of 2 #> ..$ : int 3 #> ..$ : int 2 #> $ :List of 2 #> ..$ : int 1 #> ..$ : int 3 #> $ :List of 2 #> ..$ : int 2 #> ..$ : int 3# Here is another function taking a list and that we can update to # take a vector: glue < function(l) { if (!is.list(l)) stop("not a list") l %>% invoke(paste, .) } not_run({ letters %>% glue() # fails because glue() expects a list }) letters %>% lift_lv(glue)() # succeeds#> [1] "a b c d e f g h i j k l m n o p q r s t u v w x y z"