tidyverse / magrittr

Improve the readability of R code with the pipe
https://magrittr.tidyverse.org
Other
957 stars 157 forks source link

%>>% pipe for piping last argument #192

Open vspinu opened 5 years ago

vspinu commented 5 years ago

Though not as frequently as piping first argument it's often needed to pipe the last argument.

Would you mind adding the %>>% operator?

x %>>% append(y) # is equivalent to
append(y, x)

>> because in clojure these operators are -> and ->> respectively. Racket uses f> and l>.

mattmalin commented 5 years ago

That looks like a useful function, but is there an equivalent to ... where it's used in other languages to handle variable arguments which pass through? I can imagine this potentially causing a less defined set of behaviours to capture.

For example, if there's a function foo <- function(x = 1, y = 2, ...) {<function contents>} would z %>>% foo(a) mean foo(a, z) or foo(a, y=2, z) where y=2 comes from missing argument taking default?

vspinu commented 5 years ago

That's ok. Just like with R where %>% manipulates the code, ->> in lisps is a macro which transforms the code at compile time. So z %>>% foo(a) should be exactly equivalent to foo(a,z).

There is no ability to pass a named argument. Would be great if y = z %>% foo(a) would be transformed into foo(a, y=z). Not sure if it's possible though.

mattmalin commented 5 years ago

You can pass to named arguments in positions other than first already in %>% using the . operator in place of the argument you need to replace (see the %>% help for more details).

Very useful when needing to pass to functions in positions other than first argument, pretty much anything before tidyverse approaches became widespread!

moodymudskipper commented 4 years ago

You could do :

foo <- function(a=2, b) {
  c(a,b)
}

`%>>%` <-
  function (lhs, rhs) {
    rhs_call <- insert_dot_last(substitute(rhs))
    eval(rhs_call, envir = list(. = lhs), enclos = parent.frame())
  }

insert_dot_last <-
  function(expr, special_cases = TRUE) {
    if(is.symbol(expr) || expr[[1]] == quote(`(`)) {
      # if a symbol or an expression inside parentheses, make it a call with 
      # a dot arg
      expr <- as.call(c(expr, quote(`.`)))
    } else if(length(expr) ==1) {
      # if a call without arg, same thing
      expr <- as.call(c(expr[[1]], quote(`.`)))
    } else if (expr[[1]] != quote(`{`) &&
               all(sapply(expr[-1], `!=`, quote(`.`)))) {
      # if a call with args but no dot in arg, insert dot in last place
      expr <- as.call(c(as.list(expr), quote(`.`)))
    }
    expr
  }
1 %>>% foo(2)
#> [1] 2 1
1 %>>% foo(2, .)
#> [1] 2 1
1 %>>% foo(., 2)
#> [1] 1 2

See : https://stackoverflow.com/a/58301142/2270475

D3SL commented 4 years ago

%>>% is already used by pipeR as the primary pipe operator. Plus as mattmalin stated . already allows you to sned input wherever you want. I think rather than another operator it's probably better practice to encourage users to explicitly define where their input is being piped when not to the first unnamed argument.

Apologies for necroposting.