Skip to content

Latest commit

 

History

History
124 lines (98 loc) · 4.62 KB

README.md

File metadata and controls

124 lines (98 loc) · 4.62 KB

ghcjs-commonjs

CircleCI Build Status


ghcjs-commonjs is a work-in-progress collection of little hacks to make GHCJS generated JavaScript and CommonJS integration less troublesome.

This hasn't been tested in production yet, so try the examples and report any issues that you have

We provide the following tools:

  • ghcjs-require - Calling Haskell from Node.js and CommonJS
  • ghcjs-commonjs - Exposing Haskell to be called from CommonJS
  • ghcjs-register - require('./MyHaskellModule.hs')
  • ghcjs-loader - A webpack loader for GHCJS

See the examples! You can run them all with make.

The modules have not been published to Hackage or NPM yet, but should be installable from git or the filesystem.

Utilities for loading GHCJS .jsexes from CommonJS land.

Callling Haskell from JavaScript

This is main.js:

const ghcjsRequire = require('ghcjs-require');
const Main = ghcjsRequire(module, './Main.jsexe');
// ^^ This is a function that boots the Haskell RTS
Main(({wrapped}) => { // <- This callback is executed after the RTS is loaded
  wrapped.someFunction().then(() => console.log('someFunction is over'));
  // ^^ This function was generated, it'll call Haskell code asynchronously and
  //    return a promise to the result

  wrapped.hello('John').then((ret) => console.log(ret));
  // ^^ Arguments and return values are automatically (de-)serialized we can use
  //    multi-arg functions, IO actions and pure values
});

The Haskell side of things for exposing code to ghcjs-require.

Exposing Haskell to JavaScript

This is Main.hs:

import Control.Concurrent (threadDelay)
import GHCJS.CommonJS (exportMain, exports)
someFunction = do
    putStrLn "Waiting for a second"
    threadDelay (1000 * 1000)
    putStrLn "Done!"
main = exportMain [ "someFunction" `exports` someFunction
                  , "hello" `exports` \name -> "Hello " ++ name
                  ]

On the likes of coffee-script/register or babel-register:

require('ghcjs-register');
const Main = require('./Main.hs');

This is a webpack loader for GHCJS. See the examples.

How does it work?

Following the guidelines set on "Writing Atom plugins in Haskell using GHCJS", this project exports two main libraries: ghcjs-require is a JavaScript library for wrapping GHCJS output in a CommonJS module and ghcjs-commonjs uses this library's semantics to expose Haskell values to the JavaScript world.

Other than wrapping GHCJS' output in a module, ghcjs-require injects an EventEmitter instance, which then serves as a message bus between Haskell and JavaScript.

The ghcjs-commonjs package uses this bus through the exports and exportMain primitives, listening for events on the emitter and executing functions as requested.

The JavaScript side does some more wrapping to hide the event calling and callbacks from the user, providing a promise based API.

The type of exportMain is:

exportMain :: (Foldable t) => t (String, CommonJSExport) -> IO ()

So we can receive any Foldable instance of tuples of the export name to a CommonJSExport. A CommonJSExport is:

type CommonJSExport = [JSVal] -> IO [JSVal]

So a function that can take an arbitrary amount of arguments and return an arbitrary amount of results

We then use an exports primitive:

exports :: ToCommonJSExport (String, e) => String -> e -> (String, CommonJSExport)

This uses a type-class ToCommonJSExport, which knows how to convert certain Haskell values to CommonJSExports, which then can be trivially wrapped onto JavaScript functions.

To interact with the EventEmitter we use an internal, work-in-progress, wrapper exposed in JavaScript.EventEmitter and a global variable which we get from ghcjs-register.

This should be enough for Haskell/Node.js interop. For browsers, the webpack integration should offer a way of using Haskell code in a state-of-the-art JavaScript set-up.

All in all, it needs testing and examples that show that it adds or doesn't add value, but I recommend you clone the repository and try the example code out.

License

All code under this repository is licensed under the MIT license.