Last active
June 9, 2019 20:34
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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")); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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 file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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