Magrittr %>% inside a package

I want to use a user defined %operator%, for example %>% from magrittr, inside of a package, for example

			xml2::xml_find_all(document, t$xpath)
		} ) %>% purrr::flatten(.)

This doesn't work unless the code using the package has loaded magrittr.

What is the syntax for doing this so that the package does not depend on magrittr?

Here are some details...

A package cannot depend on a library being loaded, it must just import the library in it's DESCRIPTION file. This isn't a problem for functions because the package name can be used to scope the function as it is for purrr::flatten in the example above.

However the syntax to scope a user defined operator isn't obvious (to me :-()

What is the syntax to scope operators like %>%?

For example this, and a number of variations, does not work

			xml2::xml_find_all(document, t$xpath)
		} ) magrittr::`%>%` purrr::flatten(.)

Thanks,
Dan

3 Likes

Have you tried adding

importFrom(magrittr,"%>%")

to NAMESPACE, or does that not beget the solution you're looking for?

I'm totally just following the suggestion from the SO answer above. The subsequent answers also give :+1: suggestions if you're using devtools!

3 Likes

Alternatively, you could potentially define a new %>% function in your package that just passes arguments along to magrittr::`%>%`. As far as I can tell, there is no way to use a function referenced with the :: operator in an infix manner (and of course, not using %>% as an infix operator defeats the point).

2 Likes

Thanks @mara, @nick. Updating the IMPORTS file to importFrom(magrittr,"%>%") fixes the problem but....

This won't work if I want to use %>% from more than one namespace. I don't need to for the particular package I am working on but I would l like is a more general solution.

But as @nick points out there doesn't seem to be a way to scope the namespace of an infix operator, so importFrom() is the only solution available and I'll have to use @nick 's suggestion of wrapper function if I run into the problem of colliding infix operators.

I knew when I first saw the comment "Everything in R is a function..." there had to be a catch... just kidding I'm really liking R the more I dig into it. And it looks like I will have to dig into user defined operators and namespaces a bit more.

Thanks again,
Dan

1 Like

Note that @nick said that there isn't a way to incorporate the namespace when the operator is used in an infix manner. You should be able to use the same function name (which is what an infix operator is) from more than one namespace, so long is it's qualified by ::.

As in work regardless of whether the user has magrittr loaded, not the formal DESCRIPTION Depends, correct? If the former, @mara's solution—or quite likely the roxygen2 equivalent:

#' @importFrom magrittr "%>%"

—will work. If the latter, magrittr needs to end up in Imports unless you deliberately want to auto-load the whole package for users by putting it in Depends (likely a bad idea due to name clashes). If you want to avoid any dependency, well, you'd have to rewrite the functionality, which would be a waste of effort.

In practice, %>% tends to be re-exported, which can be done in a couple ways, e.g. dplyr/R/utils.r's concise

#' @importFrom magrittr %>%
#' @export
magrittr::`%>%`

or purrr/R/utils.R's more verbose

#' Pipe operator
#'
#' @name %>%
#' @rdname pipe
#' @keywords internal
#' @export
#' @importFrom magrittr %>%
#' @usage lhs \%>\% rhs
NULL

The difference results in different documentation of reexported functions.

Each package only has a single namespace. Hadley's R packages chapter explains the necessities from a package perspective, and his Advanced R chapter on environments offers a deeper dive on how the search path works.

4 Likes

I did look at that but magrittr::%>%(lhs, rhs) isn't as easy to use as just passing in what is on either side of the operator.

I had hoped something as simple as

`%m>%` <- function (lhs, rhs) {
magrittr::`%>%`(lhs, rhs)
}

would work and let me have an alias for the operator but it didn't because the operators in magrittr are doing some clever code interpretation, I think.

I looked at the source for %>% but didn't take the time to fully understand it and figure out what is really needed.

Just using the magrittr::%>%(lhs,rhs) on it's own, even if it did work, would kinda' kill the idea of using fluent programming which is what I was going for. C# LINQ supports supports this and it really makes things a lot more readable (once the syntax sinks in) when you have to pour data into sequence of functions.

But thanks for the suggestion anyhow,
Dan

The solution from @alistaire basically summarizes how this is handled within tidyverse packages, which often import and re-export the %>% for the user's convenience. They are less likely to actually use the pipe internally.

usethis::use_pipe() does the necessary mechanics (caveat: usethis isn't on CRAN yet, but will be). That's about as close as you'll get to an "official" solution.

2 Likes

magrittr expects pipe calls to use the %>% operator literally:

# Check whether a symbol is a valid magrittr pipe.
is_pipe <- function(pipe) {
  identical(pipe, quote(`%>%`))   ||
  identical(pipe, quote(`%T>%`))  ||
  identical(pipe, quote(`%<>%`))  ||
  identical(pipe, quote(`%$%`))
}

Any different, like magrittr::`%>%`, and the pipe call won't be parsed properly.

If you wanted to make an alias, a workaround could be something like:

`%m>%` <- function(lhs, rhs) {
  `%>%` <- magrittr::`%>%`
  `%>%`(lhs, rhs)
}
1 Like

Maybe I've missed something in the impl but the suggestion for the alias doesn't seem to work, it doesn't process the rhs properly.

And here is some code to impl and try it out... note that in the package I am working on everything prefaced by hd_ is exported. So this example code is inside the package I am working on.

`%m>%` <- function(lhs, rhs) {
 `%>%` <- magrittr::`%>%`
 `%>%`(lhs, rhs)
}
hd_test1 <- function(a, b) {
	3 %>% `+`(2)
}

hd_test2 <- function(a, b) {
	3 %m>% `+`(2)
}

which when executed after my package is loaded produces:

> hd_test1()
[1] 5
> hd_test2()
 Show Traceback
 
 Rerun with Debug
 Error in function_list[[k]](value) : could not find function "rhs" 

hd_test1 works as expected but hd_test2 produces an error.

@mara 's suggestion to importFrom(magrittr,"%>%") in the NAMESPACE file works as expected for what I'm trying to do. Sorry I wasn't clear in my explanation.

I'm using %>% inside my package, in it's impl,. I've no need to export it for the user of the package.

My comment

"This won’t work if I want to use %>% from more than one namespace"

was just a hypothetical about how to disambiguate inline operators from with the same name but from different namespaces. I don't need to somehow export functions from two different namespaces as is possible in, for example, C# or C++.

Thanks for all suggestions, it's helping me understand R much better.

One more edge case, maybe a corner case :-)... reprex doesn't seem to handle %>% properly

this:

> 3 %>% `+`(2)
[1] 5

executes as expected from the console window (assuming magrittr is loaded)

however executing it with reprex, meaning highlight the code line in the editor and use render reprex, produces the following:

3 %>% `+`(2)
#> Error in eval(expr, envir, enclos): could not find function "%>%"

For reprex it needs to be self-contained; i.e. you need to load the libraries you're using in the reprex.

2 Likes

Thanks,

It works now...


library("magrittr")
3 %>% `+`(2)
#> [1] 5

I thought just doing library on the console was enough...

The code I am working on is in a package so I was trying to not load anything
:slightly_frowning_face:

1 Like

No problem. @jennybryan's reprex deck is short, and great.

There's the first triad of reprex rules:

  • Code that actually runs
  • Code I don't have to run
  • Code I can easily run

But it's the sort of sub-rules of that first one that are especially :key:! (OK, it's all key— but I don't think this slide gets all the love it deserves).

n.b. did not realize the whole slide deck would embed— not sure if this is platform-dependent, so I'm leaving the pic here just in case!

1 Like

I was going to confirm that it's platform dependent since I see nothing for your first link, but it turns out that my work just blocks Speaker Deck. :unamused:

I had to do a Privacy Badger override! ¯\_(ツ)_/¯

Ah you're right, I had only tested it with the simplest of cases. This should work better:

`%m>%` <- function(lhs, rhs) {
  parent <- parent.frame()
  env <- new.env(parent = parent)
  env$`%>%` <- magrittr::`%>%`
  expr <- substitute(`%>%`(lhs, rhs))
  eval(expr, env)
}

but there could still be gotchas I'm missing. I think much safer would be to just define the same operator for the package -

`%>%` <- magrittr::`%>%`
1 Like