Skip to content

Instantly share code, notes, and snippets.

@smbache
Last active October 3, 2018 19:11
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save smbache/ae9fb6bb747ac4f23a78 to your computer and use it in GitHub Desktop.
Save smbache/ae9fb6bb747ac4f23a78 to your computer and use it in GitHub Desktop.

To -> or not to ->

In the blog post "A Step to the Right in R Assignments", by @hrbrmstr, and in later twitter discussions (e.g. here), it is argued that <- (and %<>% for that matter) is "illogical" and feels awkward.

Although I can see where the temptation comes from, I consider -> to be bad practice (I do use it often in interactive work). In my view this kind of assignment in pipeline workflows is a great candidate for @hadleywickham's "you're doing it wrong if..." tweet series.

In other words I think that it is -> which is illogical and awkward, and that it is only making code more difficult to read. Let me explain why I think that it is semantically cleaner (or more idiomatic) to use <- by providing an analogy: you don't follow a recipe only to be enlightened after you followed all of the steps; "Oh, It was buns that I was baking!?" A recipe start with the promise, this is what you get, and then you follow the steps. Even next time you need to read about baking buns you may not need to read every detail in the workflow, but is still nice to quicly find the bun recipe:

buns <- # Know up front what you're going to get!
  bowl(rep("flour", 2), "yeast", "water", "warm milk", "oil") %>%
  append("flour", until = "soft") %>%
  beat(duration = "3mins") %>%
  shape(as = "balls", style = "slightly-flat") %>%
  bake(degrees = 200, duration = "15mins") %>%
  cool(buns, duration = "5mins") # -> buns  #rather than when you're done

Another analogy in the left-to-right discussion is that you don't see books where chapters are named on a page after the end of the chapter, although the pages are read from left-to-right (in most languages). You'll see it before the chapter starts; again this is what you get--and then the details. This holds true even if the author came up with the most appropriate name after finishing the chapter.

The <- symbol states what is being defined, and each step in the pipe-line provides the definition. Another benefit is that each step is aligned at the same level of indentation, whereas this is more messy:

some_outset %>% #Uh, what might be getting when I'm done?
    cool_stuff_here %>% # Can't wait...
    cute_stuff_there %>% #...
    and_finally -> CAT # Ahh, yay!

the first step, or the intial value, is not aligned with the rest, and you have to look closer before you see what is produced, even though I capitalized it.

The pipe %>% provides a mechanism for linearizing a workflow, and allows multiple statements to be read from left-to-right. It can be tempting to start using -> and to write long all-in-one pipelines. Some of this "mis-use" can lead to the very thing magrittr set out to minimize: hard-to-read, unstructured all-in-one code.

Of course you may find analogies that support -> too! An example is the latter sentence: only at the end did you see that it was to be emphasized (seing "!"). Wouldn't it be nice to know up front?

? What do you think...

@timelyportfolio
Copy link

Thanks so much for writing this out. I certainly think I have a better understanding of everyone's beliefs now. Still not convinced though :)

@garrettgman
Copy link

I like your explanation! Imagine a dictionary where each definition came before the word being defined.

@jimhester
Copy link

I think most of these readability concerns can be avoided by formatting the right assignment like another %>% and putting the new symbol on its own line

bowl(rep("flour", 2), "yeast", "water", "warm milk", "oil") %>%
  append("flour", until = "soft") %>%
  beat(duration = "3mins") %>%
  shape(as = "balls", style = "slightly-flat") %>%
  bake(degrees = 200, duration = "15mins") %>%
  cool(buns, duration = "5mins") -> 
buns

Then it is easy to see that you start with a bowl with some ingredients, do some actions to it and produce buns. It also gives the expressions a nice symmetry.

@chris-holcomb
Copy link

That is an improvement, but how is that better than:

buns <- 
  bowl(rep("flour", 2), "yeast", "water", "warm milk", "oil") %>%
  append("flour", until = "soft") %>%
  beat(duration = "3mins") %>%
  shape(as = "balls", style = "slightly-flat") %>%
  bake(degrees = 200, duration = "15mins") %>%
  cool(buns, duration = "5mins")

Here you immediately know what is being created, it's first primary source, and you can look on the left side for objects. To me using -> goes against the design of R. E.g., AFAIK, you can't do this:

function(x,exponent){
  return(x^exponent)
  } -> 
pow2

instead of:

pow <- function(x,exponent){
  return(x^exponent)
}

And you wouldn't want to.

@agawronski
Copy link

agawronski commented Aug 12, 2016

No way ... we read left to right, and piping goes left to right.

I agree that generally it should not be used but when piping data it seems so much more natural (and to me, significantly more readable than %<>%). I agree whole heartedly with @hrbrmstr. I predict, with absolutely no data to back this up, that the -> assignment operator will become the standard method when piping.

I know what I think! What do you think?

@Guillawme
Copy link

Guillawme commented Oct 3, 2018

To me using -> goes against the design of R. E.g., AFAIK, you can't do this:

function(x,exponent){
  return(x^exponent)
  } -> 
pow2

Not saying you should use this notation, but it definitely works if you enclose the entire function definition with braces:

{
  function(x, exponent) {
      return(x^exponent)
  } 
} -> 
pow2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment