extendr / rextendr

An R package that helps scaffolding extendr-enabled packages or compiling Rust code dynamically
https://extendr.github.io/rextendr/
Other
180 stars 27 forks source link

Definition persistence #14

Closed molpopgen closed 3 years ago

molpopgen commented 3 years ago

Following up on comments from #12.

RMarkdown blocks are most useful when definitions are visible from downstream code fences, allowing one to mix code and prose. This currently works with R, Rcpp, and Python. (It does not, however, work with a c block and a few others, last I checked).

Working example (tested via vim + pandoc plugins):

```{r setup}
library(reticulate)

Rcpp

#include <Rcpp.h>

//[[Rcpp::export]]
Rcpp::IntegerVector double2Me(Rcpp::IntegerVector x) {
  return x + x;
}

Blah blah blah.

double2Me(c(2, 2))

Python

import attr

@attr.s(auto_attribs=True)
class Test(object):
    a: int
    b: int

Blah blah blah--let's see this in action:

t = Test(1, 2)
print(f"{t.a} {t.b}")

The analog fails with rust code:
library(rextendr)

Inline code

Here's a type we may want to work with.

pub struct TestStruct {
    x: i32,
    y: String,
}

More prose.

Code blocks are not persistent and this fails, being unaware of TestStruct:

let t = TestStruct{ x: 1, y: "boo!".as_string() };
rprintln!("{} {}", t.x, y.y);

This is similar to the situation with C:
typedef struct foo{
int x;
double y;
} Foo;

Let's use it:

/* Uncomment the below to get this block to work */
/*
typedef struct foo{
int x;
double y;
} Foo;
*/
void x(Foo * f) {
}
molpopgen commented 3 years ago

I just realized my Rcpp example is incorrect: it is C++ being seen from R. It may not be possible from Rcpp to see definitions in separate fences. (I haven't used Rcpp in years....)

Corrected Rcpp example with help from the bookdown docs:

```{Rcpp, ref.label=knitr::all_rcpp_labels(), include=FALSE}

Rcpp

#include <Rcpp.h>

//[[Rcpp::export]]
Rcpp::IntegerVector double2Me(Rcpp::IntegerVector x) {
  return x + x;
}

Blah blah blah.

#include <Rcpp.h>

//[[Rcpp::export]]
Rcpp::IntegerVector callit() {
    return double2Me(Rcpp::IntegerVector{2, 2});
}

Now, call from R:

callit()
clauswilke commented 3 years ago

I basically copied the Rcpp chunk engine when I wrote the extendr chunk engine, so I'd expect this not to work in Rcpp either. But we can make it work, though not automatically (I think that would be a bad design, honestly, for a compiled language). Just have to think a bit about the best interface.

A workaround is of course to use two separate chunks, one with echo = TRUE, eval = FALSE and one with echo = FALSE, eval = TRUE.

clauswilke commented 3 years ago

Well, the all_labels() trick should work right now with extendr chunks. But it mostly only makes sense for function definitions. It still won't give you step-by-step evaluations.

clauswilke commented 3 years ago

To string chunks together, we could probably use similar tricks as the learnr package does. See relevant code here: https://github.com/rstudio/learnr/blob/master/R/knitr-hooks.R

I envision something like this:

```{extender chunk1-preamble}
pub struct TestStruct {
    x: i32,
    y: String,
}
let t = TestStruct{ x: 1, y: "boo!".as_string() };
rprintln!("{} {}", t.x, y.y);
which would cause the code in `chunk1-preamble` to be prepended to the code in chunk1 before compilation.

Possibly easier to implement and maybe even more useful would be this:
pub struct TestStruct {
    x: i32,
    y: String,
}
let t = TestStruct{ x: 1, y: "boo!".as_string() };
rprintln!("{} {}", t.x, y.y);
clauswilke commented 3 years ago

I'm pretty sure the second proposed API can be implemented with a simple call to knitr::knit_code$get(), so I'll give it a try.

clauswilke commented 3 years ago

Ok, this was easier than expected. Example here: https://extendr.github.io/rextendr/articles/rmarkdown.html#chaining-chunks-together-1

molpopgen commented 3 years ago

Thanks! I'll give this a try "soon" (teaching...)