Skip to content

JavaScript: implement callbacks in the FFI

!10128 (closed)

Introduction

GHCJS previously implemented a Callback a data type, which is currently missing from GHC's JavaScript backend. This type represents functions passable in the FFI that use a more standard JavaScript calling convention - rather than the calling conventions used in the JS generated code.

GHCJS supported functions up to 3 arity, and it supported various synchronicities of callbacks. Arguments are passed as an untyped JSVal - essentially a representation of a plain JavaScript value.

Usage

To construct callbacks, the following functions are used depending on synchronisation and the existence (or lack thereof) of a return value:

data OnBlocked = ContinueAsync | ThrowWouldBlock

asyncCallback1 :: (JSVal -> IO ()) -> IO (Callback (JSVal -> IO ()))
syncCallback1 :: OnBlocked -> (JSVal -> IO ()) -> IO (Callback (JSVal -> IO ()))
syncCallback1' :: (JSVal -> IO JSVal) -> IO (Callback (JSVal -> IO JSVal))

Only the 1-arity signatures are demonstrated here, but sizes up to 3 - including 0 - are available.

These are expected to be used as a way to call into Haskell-generated code from regular JavaScript, via FFI imports. A simple example demonstrating this is as follows:

foreign import javascript "((f,x) -> { f(x); })"
  js_apply :: Callback (JSVal -> IO ()) -> JSVal -> IO ()

fn x = if isNull x then putStrLn "isNull" else fromJSString x

main = do
  f <- syncCallback1 ThrowWouldBlock fn
  js_apply f (toJSString "example!")
  releaseCallback f -- Memory can't be automatically freed because we don't know if the function is being held on the JavaScript side

Callbacks as FFI "exports"

Callbacks enable a form of FFI "exports", through setting global variables:

foreign import javascript "((f) => { globalF = f; })"
  setF :: Callback (JSVal -> IO ()) -> IO ()

fn x = if isNull x then putStrLn "isNull" else fromJSString x

main = setF =<< syncCallback1 ThrowWouldBlock fn

Then, if our generated Haskell-main is called in the HTML head, globalF will be available as a callback for e.g. HTML buttons.

Implementation

The GHCJS.Foreign.Callback module from the ghcjs-base package ports with minimal changes to the module and without changes to the current JavaScript backend (except for a few bugfixes that have already been merged).

Edited by Josh Meredith
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information