tidyverse / magrittr

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

Feature request: add prefix operator #132

Closed burgerga closed 6 years ago

burgerga commented 7 years ago

Having used Mathematica I love their flexible style of input notation, especially the infix operator // (example: x//f -> f[x]) and prefix operator @ (example: f@x -> f[x]). I am happy that magrittr provides the infix operator, but is it also possible to add the prefix operator? (I really dislike typing/matching parentheses ;) )

Possible implementation: since purr has already claimed %@% as infix attribute accesor (which makes sense), I think %<% might work?

smbache commented 7 years ago

Can you provide a realistic use case?

alexDeCastroAtGit commented 7 years ago

My use case (hopefully I'm not being a duffus here): how to pipe a vector of coordinates to a tensor? I'm trying to pipe say c(1,2) to a matrix G to fetch that specific entry.

smbache commented 7 years ago

@alexDeCastroAtGit That's really doing more than "piping", as indexing a matrix like that requires a matrix in itself; you can do it with an intermediate step:

# some matrix
G <- matrix(1:4, 2, 2)
# some coordinate
v <- c(1, 2)

# Pipe index
v %>% rbind %>% G[.]
# => 3

You can also get creative with %$% which wraps with; this small example is pretty dull, and not justified in any way, but there are more complex examples where this approach can be useful:

# Supporse you had a custom class
coord <- structure(c(1, 2), class = "coord")

# Implements its 'with' method; here is a very simple implementation; expr could be an expression 
# which needs special treatment.
with.coord <- function(data, expr, ...)
{
    expr[rbind(coord)]
}

# Can now be used with %$%
coord %$% G
# => 3
alexDeCastroAtGit commented 7 years ago

Thanks!

burgerga commented 7 years ago

Maybe the use case isn't as strong because you are able to write everything as postfix as well, but let's say you want "to print the 10 cars with the highest mpg from mtcars" then normal form

print(head(arrange(mtcars, mpg), 10))

follows your train of thought most naturally I think. Downsides are that you need to count parentheses carefully, because the 10 is far away from head. Prefix notation keeps this train of thought but gets rid of the parentheses and separation of arguments:

print %<% head(10) %<% arrange(mpg) %<% mtcars

Of course you get a similar thing by using postfix, but I think by being able to use both prefix and postfix you can create more readable code that can also convey intent better. A very nice explanation of this is in the Mathematica Cookbook in 2.7: Using Prefix and Postfix Notation to Produce More Readable Code

ctbrown commented 7 years ago

If I am following your specific example correctly, the backpipe package provides a %<% operator that does just this. It can also be found on github.

Full disclosure ... I am the author/maintainer of backpipe. It is heavily used in our company for the use case described in the README.md and I would love to hear other use cases if you have them.

burgerga commented 7 years ago

@ctbrown It seems I didn't look hard enough, it is exactly what I'm looking for! Still, it would be nice if it was one package but given the level of skepticism in #26 I'm not too hopeful.

ctbrown commented 7 years ago

@burgerga, I agree and tried to argue for the inclusion of backpipe/prefix inclusion in #26. I think there are concrete and good use cases for the backpipe/prefix operator. I laid out one strong use case in backpipe's README.md.

Most of the discussion was with @piccolbo who is a good friend. I gave him a little bit of hell for first making light of the issue and then arguing against it. I think he sees some of the utility now.

I can also understand the some of the reluctance to including it in the main magrittr core. As much as possible, the backpipe package has followed magrittr style so if and when magrittr decides a backpipe operator is useful, I am happy for it to be folded in magrittr (or piper) so that the backpipe package can go away.

piccolbo commented 7 years ago

Maybe my comment was a little tongue-in-cheek, particularly the sonification part, but I like the idea of experimenting with other notations inspired by DAGs of expressions, like a notation for functions like join that instead of extending one pipeline can merge two into one.

A %()% B
%________%
join

In fact it shouldn't be too difficult to define %()% as an infix form of list and %_____% as a do.call operator with arguments in the opposite order. It doesn't have to stop at two arguments but then it's not realistic to have them on the same line. It could look like

C--------------------\
B-----------------\  |
A--------------\  |  |
three_way_join(*, *, *)

But these are just tree-like expressions. DAGs of computations are all the rage these days in Hadoop circles and more. Imagine we have a partial result C that we want to reuse in expressions f(A,C) and g(B,C), and those would be then used together in another function h in a diamond pattern. That could look like:

C------\
|      |
| f(A, *)----\
\------\     |
       |     |
  g(B, *)-\  |
          |  |
        h(*, *) 

And why stop at DAGs, if I could have it my way. We could express fixed point computations as

x ---> f(x) --\
|             |
\-------------/

where the semantics could be that the expression evaluates to the supremum of the Kleene chain for f. Some of these ideas will be pushing the limits of what the parser can do, I am willing to admit. But why not dream? In R 4.0 maybe together with a fix for sample(n)? Sorry, I am afraid all my contributions to R for the foreseeable future will be of the comedic kind, having switched to the evil side for my work. Merry Christmas to all!