loading...

Notes on porting Ruby to R

sckott profile image Scott Chamberlain Originally published at recology.info on ・4 min read

In doing a number of ports of Ruby gems to R (vcr, webmockr), I’ve noticed a few differences between the languages that are fun to dive into, at least for me.

monkey patching

Ruby has a nice thing where you can “monkey patch” classes/methods/etc. in other Ruby libraries. For example, lets say you have Ruby gems foo and bar. If foo has a method hello, you can override the hello method in foo with one from bar. AFAICT this is acceptable in gems on Rubygems.org and in general in the community.

Monkey patching is technically possible in R, but is not allowed in packages on CRAN (see ?assignInNamespace help for the warnings), even though there is some usage in CRAN packages. We can do this using utils::assignInNamespace. Let’s say you have an R package foo and another R package bar. Here, we can assign a new hello method to the one already defined in foo:

# the foo::hello method looks like
hello <- function() return("world!")


# make a new hello method
hello2 <- function() return("mars!")
# override the hello method in foo
utils::assignInNamespace("hello", hello2, "foo")

Try it with any package. It’s fun.

You can do this in a package, by using a .onAttach directive.

.onAttach <- function(libname, pkgname) {
  utils::assignInNamespace("bar", bar, "foo")
}

Anyway, monkey patching isn’t really a thing in R, so that makes it more difficult to port Ruby things to R. The inability to do this in R makes many things much harder. For example, in vcr and webmockr I couldn’t simply override methods in http libraries they hook into, but have to make changes in the http libraries themselves to support the HTTP mocking - we get there in the end, but it takes much longer, though possibly safer?

0 (Ruby) vs. 1 (R) based indexing

Never hurts to keep repeating this.

sequences

Ruby has the ability to construct numeric sequences with .. and ..., e.g.,

# inclusive of second number
x = 1..3
x.to_a
=> [1, 2, 3]
# exclusive of second number
x = 1...3
x.to_a
=> [1, 2]

AFAIK, in R we can only do inclusive sequences

1:3
#> [1] 1 2 3

explicit imports

In at least Ruby and Python you have to be explicit about saying where to import methods from other files.

Whereas in R you can just use a function/etc. from any other file in the package without stating that you need it. This makes it harder to reason about the dependent functions/etc. needed in any one file. One tool that helps with this is functionMap (though last commit in 2016, not sure if maintained anymore, is it Gábor?).

On a related note, in Ruby we can use global variables like:

$foo = 5

From what I understand the above is bad pratice, but I do use them sometimes in my own Ruby stuff.

In R all variables/methods/classes are “global” within the namespace of the package.

adding strings

ugh, I wish R had the ability to add strings together with +.

? as a valid character

um, yes please. I love methods in Ruby like nil?, empty?, etc. Such a straight-forward way to indicate intent. Wish we had these in R, but ? isn’t even a valid character on its own, so not (ever?) gonna happen.

Classes

R’s closest class system to Ruby (that I’m willing to use) is R6 from Winston Chang. Using R6 makes it a bit easier to port from Ruby or a similar language as you can directly translate classes that have public vs. private methods, an initializer, print method, etc. Plus, with any sufficiently complex R package, using R6 makes it much easier to manage the complexity.

Ruby’s ||=

In ruby this operator means essentially “if a is undefined or falsey, evaluate b and set a to the result”. In R there’s AFAIK nothing like this. ||= was used extensively in the Ruby gems I was porting, making the ported version in R more verbose. I could do in R a %||% b (where %||% = function(x, y) if (is.null(x) || !x) y else x) essentially doing “if a is null, undefined or falsey, evaluate b”; but then I have to still assign the result, giving a = a %||% b.

splat args

The splat operator is used heavily in Ruby. It looks like:

def foo(*args)
  p args
end
foo(1, 2, 3)
# => [1, 2, 3]

In R the most similar thing we have is the ellipsis, so

foo <- function(...) c(...)
foo(1, 2, 3)
#> [1] 1 2 3

Ruby splat args won’t trip you up if you know how to do this conversion. Of course there’s rlang and such in R as well.

Discussion

pic
Editor guide
Collapse
daveparr profile image
Dave Parr

I've been looking at the dev.to codebase, and trying to understand it a bit recently. It's written with Ruby on Rails. Do you have any suggestions for resources to learn Ruby that you particularly like?