Glue vs stringr::str_interp

stringr
glue
tidyverse

#1

Both glue and stringr are tidyverse citizens, so when to use each and which do folks prefer?
Do the RStudio folks recommend one vs the other, or is one on track to supplant the other?


#2

I use glue if there’s no other reason I have for using stringr in a given script/analysis.


#3

Give me paste0, or give me death! If my long string is readable, I’m not trying hard enough.

It’s also possible that I don’t do enough string interpolation to need other options in most cases. That aside, glue makes for slightly more readable strings in most situations that I see, and it is reasonably lightweight. The biggest problem is the lack of a hex logo.


#4

I prefer str_interp()'s syntax as it’s similar to how shell or Perl would handle it. I use wrapper around str_interp() that does things like put a space between list elements:

This could probably be made cleaner, but I use:
si <- function(..., frame=parent.frame()) { str_interp(str_c(Map(function(x) {str_c(x, collapse= " ")}, list(...)), collapse=""), env=frame) }


#5

True. But in fairness, it powers all of your other hex logo stickers.


#6

No love for sprintf??!!? Perhaps a microbenchmark showdown in the near future is in order.


#7

I am a big fan of glue. For programming and use in a package, I’d prefer it over stringr because it’s a much lighter dependency. For interactive / script-y use, I think either one is great.

The big advantage of template-based solutions over sprintf() is future readability and debuggability. When things aren’t right, it’s really tedious to sort out big sprintf() calls and match the format strings up with the data.

The advantage of glue and stringr over paste0() is syntactic noise, although at least paste0() doesn’t suffer from the the matching problem! But I find all the commas and quotes become quite a drag once there’s more than a couple of inputs.


#8

But we’d need to benchmark runtime and development/debugging time :stuck_out_tongue_winking_eye:


#9

Very true, and I will consider using glue more in the future. My sprintf usage rarely is that complex, but I ran into some older code of mine today and it was a veritable nightmare trying to remember which element was printing where.


#10

glue is just so easy to remember! It’s especially nice in a quick markdown post/analysis you want to keep breezy.


#11

I pretty much strictly use paste and sprintf because no need to load another library… But my co-workers have complained about my giant SQL queries having the parameters to pass saved all the way for the end. Glue looks like a really nice option to replace that scenario and making it easier for my team to work on my scripts when I’m out.


#12

You can use the syntax used in str_interp / perl / shell with glue if you like.

sh <- function(..., .envir = parent.frame()) {
  glue::glue(..., .envir = .envir, .open = "${")
}
foo = "bar"
sh("foo = ${foo}")
#> foo = bar

You can also create custom transformers to do things like collapse lists / vectors in whatever way you wish. See http://glue.tidyverse.org/articles/transformers.html for more information.

library(glue)
collapse_transformer <- function(regex = "[*]$", ...) {
  function(code, envir, data) {
    if (grepl(regex, code)) {
        code <- sub(regex, "", code)
    }
    res <- evaluate(code, envir, data)
    collapse(res, ...)
  }
}
glue("{1:5*}\n{letters[1:5]*}", .transformer = collapse_transformer(sep = ", ", last = " and "))
#> 1, 2, 3, 4 and 5
#> a, b, c, d and e

#13

If you are using paste and sprintf to construct SQL queries you might be interested in the glue_sql() function just introduced into the development version.


#14

Yes, yes I am. Thanks! Specifically using them in shiny apps to allow interactions with the database, which sounds like a pretty great use case.


#15

One place where many of the methods differ is the interpolation of NA values. The majority of the time I happen to prefer glue’s treatment (along with stringr::str_c): the entire string is coerced to NA if any interpolated values are missing. Most other methods coerce the NA values to “NA” and then perform the full string contsruction.

 > foo <- NA
 > glue::glue("foo = {foo}")
 NA
 > sprintf("foo = %s", foo)
 [1] "foo = NA"
 > paste("foo =", foo)
 [1] "foo = NA"
 > stringr::str_interp("foo = ${foo}")
 [1] "foo = NA"
 > stringr::str_c("foo =", foo)
 [1] NA

This can be a big ‘gotcha’ when switching between the various packages/methods … be warned!


#16

This is great. Have you posted this anywhere? I really like when package others include similar packages and/or point out differences in their README (e.g. @jimhester has a nice little one as Other Implementations at the end of the glue one), and it’s super helpful to see this type of comparison (especially before running into it by chance).


#17

@mara I haven’t posted this anywhere (other than here) yet, but I’ve been considering starting a series of blog posts with exactly this sort of topic in mind: n common ways to do x, with comparisons of each (e.g. edge cases, return values, speed).

I think this thread has inspired me to start, I’ll give it a stab this weekend starting with exactly this string interpolation example!


#18

Awesome! Feel free to hit me up directly when you do so, and I’ll be sure to :bird: it to the tweeple! :wink:


#19

Just wanted to follow up on this recommendation to try glue_sql. I did something today I thought was pretty gnarly and made my code much more concise and reusable. I used glue to build three subqueries that pass in parameters from a UI. Then glue_sql binds then together as CTEs (with query_name as (...) and runs a single query against the database. This may not sound that cool, but it allowed me to break up the query logic into smaller components that are easier to iterate on. Instead of injecting only a few parameter into multiple sections of a long SQL query (~80 lines), I just have 4 small SQL queries off roughly 5-10 lines each.

For example:

WITH 
cool AS {sub_query_1},
story AS {sub_query_2},
bro AS {sub_query_3}
SELECT 
  crazy, hard, math
FROM cool
JOIN story ON cool.id.= strory.cool_id
LEFT JOIN bro ON bro.id = story.bro_id
WHERE bro.name IN ({bro_names})

I’m thrilled. Thanks for making such an awesome package!


#20

Alright, I’m sold. Time to play with glue and run with scissors.