Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
After writing the initial version of a tutorial on wrapping and binding R functions on the javascript side of WebR, I had a number of other WebR projects on the TODO list. But, I stuck around noodling on the whole “wrapping & binding” thing, and it dawned on me that there was a “pretty simple” way to make R functions available to javascript using javascript’s Function() constructor. I’ll eventually get this whole thing into the GH webr-experiments repo, but you can see below and just view-source (it’s as of the time of this post, in the 130’s re: line #’s) to check it out before that.
Dynamic JavaScript Function Creation
The Function()
constructor has many modes, one of which involves providing the whole function source and letting it build a function for you. R folks likely know you can do that in R (ref: my {curlconverter} pkg), and it’s as straightforward on the javascript side. Open up DevTools Console and enter this:
const sayHello = new Function('return function (name) { return `Hello, ${name}` }')();
then call the function with some string value.
We only need one input to create a dynamic R function wrapper (for SUPER BASIC R functions ONLY). Yes, I truly mean that you can just do this:
let rbeta = await wrapRFunction("rbeta"); await rbeta(10, 1, 1, 0)
and get back the result in the way WebR’s toJs()
function returns R objects:
{ "type": "double", "names": null, "values": [ 0.9398577840605595, 0.42045006265859153, 0.26946718094298633, 0.3913958406551122, 0.8123499099597378, 0.49116132695862963, 0.754970193716774, 0.2952198011408607, 0.11734111483990002, 0.6263863870230043 ] }
Formal Attire
R’s formals()
function will let us get the argument list to a function, including any default values. We won’t be using them in this MVP version of the auto-wrapper, but I see no reason we couldn’t do that in a later iteration. It’s “easy” to make that a javascript function “the hard way”:
async function formals(rFunctionName) { let result = await globalThis.webR.evalR(`formals(${rFunctionName})`); let output = await result.toJs(); return(Promise.resolve(output)) }
Now, we just need to use those names to construct the function. This is an ugly little function, but it’s really just doing some basic things:
async function wrapRFunction(rFunctionName) { let f = await formals(rFunctionName) let argNames = f.names.filter(argName => argName != '...'); let params = argNames.join(', ') let env = argNames.map(name => `${name}: ${name}`).join(', '); let fbody = `return async function ${rFunctionName}(${params}) { let result = await globalThis.webR.evalR( '${rFunctionName}(${params})', { env: { ${env} }} ); let output = await result.toJs(); globalThis.webR.destroy(result); return Promise.resolve(output); }`; return new Function(fbody)() }
- first, we get the formals for the function name provided to us
- then we remove ones we can’t handle (yet!)
- then we take the R function parameter names and make two objects:
- one that will make a comma-separated parameter declaration list (e.g.
param1, param2, param3
) - another that does the same, but as
key: value
pairs to pass as the environment (see previous WebR blog posts and experiments)
- one that will make a comma-separated parameter declaration list (e.g.
- the function body is another template string that just makes the standard WebR set operations to evaluate an R object and get the value back.
We pass the built function string to the Function()
constructor, and we have an automagic javascript wrapper for it.
FIN
This is a very naive wrapper. It’s all on the caller to specify all the values. R has some fun features, like the ability to not specify a value for a given parameter, and then check on the R side to see if it’s missing. It can also intermix named and positional parameters, and then there’s the dreaded ...
.
I’m going to noodle a bit more on how best to account and/or implement ^^, but I’ll 100% be using this idiom to wrap R functions as I keep experimenting.
One more thing: I had to abandon the use of the microlight
syntax highlighter. It’s super tiny and fast, but it doesn’t handle the code I need, so I’ll be updating my nascent webr-template to incorporate what I am using now: Shiki. The next release will also include these basic wrapper functions.
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.