magrittr icon indicating copy to clipboard operation
magrittr copied to clipboard

%>>% pipe for piping last argument

Open vspinu opened this issue 6 years ago • 5 comments

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>.

vspinu avatar Mar 01 '19 03:03 vspinu

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?

mattmalin avatar Jul 01 '19 21:07 mattmalin

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.

vspinu avatar Jul 02 '19 07:07 vspinu

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!

mattmalin avatar Jul 02 '19 07:07 mattmalin

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

moodymudskipper avatar Nov 08 '19 08:11 moodymudskipper

%>>% 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.

D3SL avatar May 19 '20 09:05 D3SL