Running Go language chunks in R Markdown (Rmd) files

By Bob Rudis (@hrbrmstr)
Mon 08 June 2015 | tags: blog, r, rstats, -- (permalink)

After seeing IPython Markdown Opportunities in IPython Notebooks and Rstudio in the feeds today I wondered how easy/hard it would be to write a handler for something like go code. After taking a look at Yihui Xie‘s knitr source it seemed like it would be fairly easy to mimic a combination of Rcpp and “shell” block handling to process standalone Go code blocks.

I call these “standalone” blocks since they don’t preserve anthing between the code chunks, so you’re really just embedding a language script for reproducible processing. My naive Go implementation also doesn’t handle any visualizations that code would generate nor does it format the Go code nicely (that’s definitely on the TODO list, though). However, it does show how to make a minimal working chunk language processor and provides basic functionality for Go scripts.

Here’s the start of a self-contained Rmd file for supporting Go language blocks:

---
output: html_document
---
```{r setup, echo=FALSE}

library(knitr)

eng_go <- function(options) {

  # create a temporary file

  f <- basename(tempfile("go", '.', paste('.', "go", sep = '')))
  on.exit(unlink(f)) # cleanup temp file on function exit
  writeLines(options$code, f)

  out <- ''

  # if eval != FALSE compile/run the code, preserving output

  if (options$eval) {
    out <- system(sprintf('go run %s', paste(f, options$engine.opts)), intern=TRUE)
  }

  # spit back stuff to the user

  engine_output(options, options$code, out)
}

knitr::knit_engines$set(go=eng_go)

```

It’s pretty self-explanatory, but the steps in the eng_go function are:

  • create a temporary file for the go source code
  • ensure that temp file is deleted on function exit
  • if told to eval, then run the go file with any options/arguments and preserve the output
  • pass the code & output back to knitr

And, then all we have to do is register the engine with the last line.

Now, we can take the Go language ‘slices’ example code and put it in a chunk below that:

```{r go-ex, engine='go', eval=TRUE, echo=FALSE}
package main

import "fmt"

func main() {

    // Unlike arrays, slices are typed only by the
    // elements they contain (not the number of elements).
    // To create an empty slice with non-zero length, use
    // the builtin `make`. Here we make a slice of
    // `string`s of length `3` (initially zero-valued).
    s := make([]string, 3)
    fmt.Println("emp:", s)

    // We can set and get just like with arrays.
    s[0] = "a"
    s[1] = "b"
    s[2] = "c"
    fmt.Println("set:", s)
    fmt.Println("get:", s[2])

    // `len` returns the length of the slice as expected.
    fmt.Println("len:", len(s))

    // In addition to these basic operations, slices
    // support several more that make them richer than
    // arrays. One is the builtin `append`, which
    // returns a slice containing one or more new values.
    // Note that we need to accept a return value from
    // append as we may get a new slice value.
    s = append(s, "d")
    s = append(s, "e", "f")
    fmt.Println("apd:", s)

    // Slices can also be copied. Here we create an
    // empty slice `c` of the same length as `s` and copy
    // into `c` from `s`.
    c := make([]string, len(s))
    copy(c, s)
    fmt.Println("cpy:", c)

    // Slices support a "slice" operator with the syntax
    // `slice[low:high]`. For example, this gets a slice
    // of the elements `s[2]`, `s[3]`, and `s[4]`.
    l := s[2:5]
    fmt.Println("sl1:", l)

    // This slices up to (but excluding) `s[5]`.
    l = s[:5]
    fmt.Println("sl2:", l)

    // And this slices up from (and including) `s[2]`.
    l = s[2:]
    fmt.Println("sl3:", l)

    // We can declare and initialize a variable for slice
    // in a single line as well.
    t := []string{"g", "h", "i"}
    fmt.Println("dcl:", t)

    // Slices can be composed into multi-dimensional data
    // structures. The length of the inner slices can
    // vary, unlike with multi-dimensional arrays.
    twoD := make([][]int, 3)
    for i := 0; i < 3; i++ {
        innerLen := i + 1
        twoD[i] = make([]int, innerLen)
        for j := 0; j < innerLen; j++ {
            twoD[i][j] = i + j
        }
    }
    fmt.Println("2d: ", twoD)
}
```

And, check the output after knitting:

## emp: [  ]
## set: [a b c]
## get: c
## len: 3
## apd: [a b c d e f]
## cpy: [a b c d e f]
## sl1: [c d e]
## sl2: [a b c d e]
## sl3: [c d e f]
## dcl: [g h i]
## 2d:  [[0] [1 2] [2 3 4]]

If you remove the echo=FALSE you’ll get the Go code in a block before the output.

You can make this a package for handling Go code and then just issue a library call to it in the “setup” chunk. To ensure the language handler registration happens, just add knitr::knit_engines$set(go=eng_go) to the package’s .onLoad function.

You can find the complete R Markdown file on github.

Lots more can (and will) be done to extend this example. If you’ve already made a more robust Go handler, please drop a note in the comments with a link!

comments powered by Disqus