Skip to content

Instantly share code, notes, and snippets.

@osoftware
Last active June 9, 2019 20:34
Show Gist options
  • Save osoftware/5aad2ca13798cbc5919b43aacb0e33e1 to your computer and use it in GitHub Desktop.
Save osoftware/5aad2ca13798cbc5919b43aacb0e33e1 to your computer and use it in GitHub Desktop.
Using a Haskell program compiled with GHCJS as a library in a nodejs app.
// A sample application in TypeScript
import { Worker } from "worker_threads";
export interface Disposable {
dispose(): void;
}
// A class that wraps a worker thread and provides a method to call a function previously exposed in wrapper.js
export class Bridge implements Disposable {
private requests = {};
private counter = 0;
constructor(private worker: Worker) {
this.worker.on("message", this.handleMessage.bind(this));
}
private handleMessage({ id, result }) {
this.requests[id](result);
delete this.requests[id];
}
// Calls a Haskell method.
public request(func: string, ...args: Array<any>) {
return new Promise((resolve, _) => {
const id = this.counter++;
this.requests[id] = resolve;
this.worker.postMessage({ id, func, args });
});
}
// Clean shutdown of the worker thread.
public dispose() {
this.worker.postMessage({ id: -1, func: "dispose" });
}
}
// Forgive my C# habits but I had to :)
export async function using<T extends Disposable>(
resource: T,
action: (resource: T) => Promise<void>
) {
try {
await action(resource);
} finally {
resource.dispose();
}
}
// A sample usage
using(new Bridge(new Worker("./lib.js")), async bridge => {
console.log(await bridge.request("cat", "first", "second"));
console.log(await bridge.request("main"));
});
rm -f lib.js
echo "(function(global){" >> lib.js
cat dist-newstyle/build/x86_64-linux/ghcjs-8.6.0.1/app-0.1.0.0/x/app-exe/build/app-exe/app-exe.jsexe/rts.js >> lib.js
cat dist-newstyle/build/x86_64-linux/ghcjs-8.6.0.1/app-0.1.0.0/x/app-exe/build/app-exe/app-exe.jsexe/lib.js >> lib.js
cat dist-newstyle/build/x86_64-linux/ghcjs-8.6.0.1/app-0.1.0.0/x/app-exe/build/app-exe/app-exe.jsexe/out.js >> lib.js
cat wrapper.js >> lib.js
echo "})(exports);" >> lib.js
-- Just a sample Haskell program with some functions.
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Data.JSString
main :: IO JSString
main = return "elo"
-- Notice we return IO JSString, not just JSString. Every interop HS<->JS is an IO.
cat :: JSString -> JSString -> IO JSString
cat str str2 = return . pack $ (unpack str) ++ (unpack str2)
// This is a file that will be bundled together with rts.js, lib.js and out.js produced by GHCJS.
// Just a bunch of helper functions that don't have cryptic, unreadable names unlike the original ones in rts.
const ref = arg => h$c1(h$ghcjszmprimZCGHCJSziPrimziJSVal_con_e, arg);
const ap1 = (f, args) => h$c2(h$ap1_e, f, ...args);
const ap2 = (f, args) => h$c3(h$ap2_e, f, ...args);
const ap3 = (f, args) => h$c4(h$ap3_e, f, ...args);
const func0 = (f) => () => h$runSyncReturn(f);
const func1 = (f) => (args) => h$runSyncReturn(ap1(f, args.map(ref)));
const func2 = (f) => (args) => h$runSyncReturn(ap2(f, args.map(ref)));
const func3 = (f) => (args) => h$runSyncReturn(ap3(f, args.map(ref)));
// Haskell app runs as a worker thread. Function calls and function results are passed as messages.
workers = require("worker_threads");
workers.parentPort.on("message", function({id, func, args}) {
switch (func) {
case "dispose":
workers.parentPort.close();
h$doneMain();
// ^ This is the main reason this gist was written in the first place.
// Once the main loop in rts started, it keeps running, preventing the app from closing,
// until this function is called, but that just kills it.
// By moving the rts to a worker_thread we can kill just a thread.
default:
const result = global[func](args);
workers.parentPort.postMessage({ id, result });
break;
}
});
// Here we specify which functions should be callable from JS.
global["main"] = func0(h$mainZCMainzimain); // main::Main.main
global["cat"] = func2(h$mainZCMainzicat); // main::Main.cat
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment