Fixes for the FFI blog post
I found a number of issues in the FFI blog post. I am guessing it will be easier to fix them if I just enumerate them in the order in which they appear, rather than following the strict "Describe the bug / To Reproduce / Expected Behaviour" template. Please let me know if you'd prefer separate tickets and proposed fixes following that template.
-
The blog post refers to the type
JSRefseveral times, but I believe this type has since been renamed toJSVal. -
Missing import in the "Haskell calling JavaScript" code snippet:
Here is what Asterius lets you do today:
import Control.Monad [...] foreign import javascript "new Date()" js_current_time :: IO JSRef [...]If you would like to try this at home, put the above snippet in a file called
jsffi.hsin the current directory.However, if we follow these instructions and replace the
JSRefwithJSVal, we get the errorNot in scope: type constructor or class ‘JSVal’, because the code snippet is missing the lineimport Asterius.Types. -
First broken link in the "Haskell calling JavaScript" section:
Then, you can invoke
ahc-linkto compile it to.wasm/.jsfiles (using the pre-built Asterius Docker images, as explained in the Asterius documentation): [...]"Asterius documentation" links to https://tweag.github.io/asterius/, which 404's. Should probably link to https://asterius.netlify.app/images.html instead.
-
Second broken link in the "Haskell calling JavaScript" section:
Our current implementation supports a variety of primitive types, such as,
Bool,Char, andIntas argument and result types of imported functions. See the reference documentation for a full list."reference documentation" links to https://tweag.github.io/asterius/jsffi/, which 404's. Should probably link to https://asterius.netlify.app/jsffi.html#directly-marshalable-value-types instead.
-
If the previous link is indeed changed to https://asterius.netlify.app/jsffi.html#directly-marshalable-value-types, then this introduces a new issue. The text surrounding the link promises that the linked documentation provides the full list of supported primitive types, but https://asterius.netlify.app/jsffi.html#directly-marshalable-value-types is also limited to a partial list:
Regular Haskell value types like
Int,Ptr,StablePtr, etc.An easy fix would be to change the text to see "See the reference documentation for more information" instead of "for a full list", but a much better fix would be to change https://asterius.netlify.app/jsffi.html#directly-marshalable-value-types to provide a full list, as it's not at all obvious what this
etc.stands for. -
Outdated flag and code snippet in the "JavaScript calling Haskell":
The tool
ahc-linkprovides a flag--asterius-instance-callback=, which takes a JavaScript callback function, which is to be called once an Asterius instance has been successfully initiated. [...] Continuing with the above example, in order to callmult_hsin JavaScript, the callback that we need to supply would be:i => { i.wasmInstance.exports.hs_init(); console.log(i.wasmInstance.exports.mult_hs(6, 7)); }However,
ahc-linkno longer supports the--asterius-instance-callback=flag, the exported haskell functions are no longer nested inside awasmInstancefield, thehs_initcall no longer seems necessary, andmult_hsnow returns the Promise of an Int rather than an Int. This section of the blog post thus needs some major changes. Here is a suggested rewrite:By default, the tool
ahc-linkgenerates a file<HaskellFileName>.mjswhich loads the generated wasm code, creates an Asterius instance namedi, and callsi.exports.main()in order to run the Haskell file'smainfunction:import * as rts from "./rts.mjs"; import module from "./<HaskellFileName>.wasm.mjs"; import req from "./<HaskellFileName>.req.mjs"; module .then(m => rts.newAsteriusInstance(Object.assign(req, { module: m }))) .then(i => { i.exports.main(); });This default setup assumes that your program's entry point is your Haskell file's
mainfunction. If you instead want your program's entry point to be on the JavaScript side, you can write your ownmjsfile which does something different. Here is one which calls our exportedmult_hsfunction:$ cat Example.hs module Example where foreign export javascript "mult_hs" (*) :: Int -> Int -> Int $ cat Example.mjs import * as rts from "./rts.mjs"; import module from "./Example.wasm.mjs"; import req from "./Example.req.mjs"; module .then(m => rts.newAsteriusInstance(Object.assign(req, { module: m }))) .then(i => i.exports.mult_hs(6, 7)) .then(r => { console.log("6 * 7 = ", r); }); $ ahc-link --input-hs=Example.hs --no-main --export-function=mult_hs --input-mjs=Example.mjs --run [...] 6 * 7 = 42 -
Outdated types and code snippet in "Using Haskell closures as JavaScript callbacks" section.
The section talks about
StablePtrandmakeHaskellCallback, butmakeHaskellCallbackhas been replaced withwrapper, which has a simpler API which doesn't requireStablePtr. Also, thebeforeExitexample loops forever, because aJSFunctionnow returns a Promise, and scheduling a Promise inside abeforeExithandler causes the Node.js process not to exit after all, but instead to wait for that Promise to complete, at which point thebeforeExithandler is called again, etc. This section of the blog post is thus also in need of some major changes. Here is a suggested rewrite:One limitation of the
foreign export javascriptapproach is that it is only possible to export top-level definitions. Asterius also supports dynamically converting a Haskell function into a form which can be called from the JavaScript side. Here is a simple example:$ cat Example.hs import Asterius.Types foreign import javascript "wrapper" wrapDoubleToDouble :: (Double -> Double) -> IO JSFunction foreign import javascript "$1(42).then(console.log)" js_call_with_42_then_print :: JSFunction -> IO () main :: IO () main = do js_double <- wrapDoubleToDouble (*2) js_call_with_42_then_print js_double $ ahc-link --input-hs=Example.hs --run [...] 84On the JavaScript side, the
JSFunctioncan be called like a regular JavaScript function which returns a Promise.wrapperis also able to wrap functions of multiple arguments as well as IO actions. -
The name
makeHaskellCallbackis still mentioned at the end of the RTS documentation, even though the new name is nowwrapper.