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.
One Trackback/Pingback
[…] 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”… Continue reading → […]