Introduction to ghcjs-base
It is a minimal low-level base library for GHCJS, used by higher level libraries like JSC
It contains modules for
This section assumes you read GHCJS foreign function interface which you can find on ghcjs/ghcjs.
JSVal is defined as
data JSVal = JSVal ByteArray#
But, it is an implementation detail you should not have to care about in most cases.
It is defined in
newtype JSString = JSString JSVal
JSString is also available from
If you already knew how to use TypedArray, understanding
would not be difficult.
It is represented by
GHCJS.Buffer is an obsolete implementation of ArrayBuffer.
It seems people use
Unsafe.Coerce to convert
pack :: String -> JSString unpack :: JSString -> String
Use them as in the following example.
instance IsString JSString, so it's possible to write
GHCJS FFI by itself cannot marshal a lot of types from and to
JSVal. If you want to convert
JSVal into such types, you need to make wrapper functions that call foreign functions, convert the result to a value of the desired type, and return the converted value. Here is a minimal example.
If the conversion from and to
JSVal doesn't involve side effects, you can use pure marshalling API.
class PToJSVal a where pToJSVal :: a -> JSVal class PFromJSVal a where pFromJSVal :: JSVal -> a
PToJSVal instances for various basic types.
If the conversion from and to
JSVal involves side effects or doesn't return
the same output every time for the same input, you may want to use
import qualified Data.Aeson as AE toJSVal_aeson :: AE.ToJSON a => a -> IO JSVal toJSVal_pure :: PToJSVal a => a -> IO JSVal class ToJSVal a where toJSVal :: a -> IO JSVal -- other functions are omitted for simplicity class FromJSVal a where fromJSVal :: JSVal -> IO (Maybe a) -- other functions are omitted for simplicity
As far as I know, since
ToJSVal are generic typeclasses, you can use Generics to derive instances for
ToJSVal without boiler plates if you know how to use Generics.
If you want to express
Maybe in imported foreign functions, use
Nullable is defined in
newtype Nullable = Nullable JSVal
It is simply a newtype wrapper around
JSVal. The following functions turn
Nullable into something more than a mere newtype wrapper around
nullableToMaybe :: PFromJSVal a => Nullable a -> Maybe a maybeToNullable :: PToJSVal a => Maybe a -> Nullable a
The type signatures of those function make it clear that you need to implement pure marshalling typeclasses if you want to marshal
You can use
Nullable as below.
In the above example, I didn't need
instance PFromJSVal Int because it is already implemented in
GHCJS.Marshal.Pure. As stated before,
PToJSVal instances for many basic types.
Callback is defined as
newtype Callback a = Callback JSVal
It's just a newtype wrapper around
JSVal. There are currently two kinds of callbacks, synchronous callbacks and asynchronous callbacks.
asyncCallback :: IO () -> IO (Callback (IO ())) asyncCallback1 :: (JSVal -> IO ()) -> IO (Callback (JSVal -> IO ())) asyncCallback2 :: (JSVal -> JSVal -> IO ()) -> IO (Callback (JSVal -> JSVal -> IO ())) -- There is also asyncCallback3
asyncCallback accepts an IO action and returns a callback in
asyncCallback1 returns a callback that accepts one argument.
asyncCallback2 for a callback of 2 arguments. You can guess what
asyncCallback3 is for.
- Synchronous callback that throws
GHCJS.Concurrent.WouldBlockExceptionwhen its thread blocks
- One that becomes asynchronous when the thread blocks
Let's look at the functions that generate synchronous callbacks.
syncCallback :: OnBlocked -- ^ what to do when the thread blocks -> IO () -- ^ the Haskell action -> IO (Callback (IO ())) -- ^ the callback
It's almost the same as
asyncCallback except the argument for
OnBlocked is defined in
data OnBlocked = ContinueAsync -- ^ continue the thread asynchronously if blocked | ThrowWouldBlock -- ^ throw 'WouldBlockException' if blocked deriving (Data, Typeable, Enum, Show, Eq, Ord)
You can guess what
syncCallback1', and so on generate synchronous callbacks that throw
WouldBlockException when their threads block. It's the same as
syncCallback1 ThrowWouldBlock, and so on.
An Example of Using Callback in NodeJs
Caveats on Callbacks
- All callbacks should be manually released from memory by
GHCJS.Foreign.Export should be used.
newtype Export a = Export JSVal export :: Typeable a => a -> IO (Export a) withExport :: Typeable a => a -> (Export a -> IO b) -> IO b derefExport :: forall a. Typeable a => Export a -> IO (Maybe a) releaseExport :: Export a -> IO ()
Export ais actually
releaseExport: releases all memory associated with the export. Subsequent calls to 'derefExport' will return 'Nothing'
derefExport: retrieves the haskell value from an export. It returns 'Nothing' if the type does not match or the export has already been released.
withExport: exports a given value, runs the action
IO b, and returns
IO b. The value is only exported for the duration of the action. Dereferencing it after the
withExportcall has returned will always return
isBoolean, etc, ... You can inspect those functions and understand them easily.