It is a minimal low-level base library for GHCJS, used by higher level libraries like JSC
It contains modules for
GHCJS/
JavaScript/
This section assumes you read GHCJS foreign function interface which you can find on ghcjs/ghcjs.
Arbitrary javascript values are represented by GHCJS.Types.JSVal
import GHCJS.Types (JSVal)
foreign import javascript unsafe
"$1 + $2" add :: JSVal -> Int -> JSVal
foreign import javascript unsafe
"require($1)" require :: JSVal -> JSVal
Internally, 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 Data.JSString
as
newtype JSString = JSString JSVal
JSString
is also available from GHCJS.Types
.
If you already knew how to use TypedArray, understanding JavaScript.TypedArray
would not be difficult.
It is represented by JavaScript.TypedArray.ArrayBuffer
.
GHCJS.Buffer
is an obsolete implementation of ArrayBuffer.
It seems people use unsafeCoerce
from Unsafe.Coerce
to convert JSVal
to other Javascript types and vice versa. There is JavaScript.Cast
, but it is considered incomplete.
Data.JSString
contains
pack :: String -> JSString
unpack :: JSString -> String
Use them as in the following example.
import Data.JSString as S
foreign import javascript unsafe
"require('console').log($1)" console_log :: S.JSString -> IO ()
main :: IO ()
main = do
let message = "yo" :: String
console_log (S.pack message)
Data.JSString
contains instance IsString JSString
, so it's possible to write
import Data.JSString as S
foreign import javascript unsafe
"require('console').log($1)" console_log :: S.JSString -> IO ()
main :: IO ()
main = do
console_log "yo"
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.
foreign import javascript unsafe
"$1 === 0" js_isEqualToZero :: Int -> Bool
data NewBool = Yes | No
isEqualToZero :: Int -> NewBool
isEqualToZero n = if js_isEqualToZero n then Yes else No
If the conversion from and to JSVal
doesn't involve side effects, you can use pure marshalling API.
GHCJS.Marshal.Pure
exports
class PToJSVal a where
pToJSVal :: a -> JSVal
class PFromJSVal a where
pFromJSVal :: JSVal -> a
GHCJS.Marshal.Pure
has PFromJSVal
and 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
GHCJS.Marshal
. GHCJS.Marshal
exports
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 FromJSVal
and ToJSVal
are generic typeclasses, you can use Generics to derive instances for FromJSVal
and ToJSVal
without boiler plates if you know how to use Generics.
Maybe
in Javascript FunctionsIf you want to express Maybe
in imported foreign functions, use GHCJS.Nullable
. Nullable
is defined in GHCJS.Nullable
as
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 JSVal
.
GHCJS.Nullable
exports
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 Maybe
.
You can use Nullable
as below.
import GHCJS.Nullable
foreign import javascript unsafe
"if($1 === 0) { $r = null; } else { $r=$1; }"
js_nullIfZero :: Int -> Nullable Int
maybeZero :: Int -> Maybe Int
maybeZero n = nullableToMaybe (js_nullIfZero n)
In the above example, I didn't need instance PFromJSVal Int
because it is already implemented in GHCJS.Marshal.Pure
. As stated before, GHCJS.Marshal.Pure
has PFromJSVal
and PToJSVal
instances for many basic types.
With GHCJS.Foreign.Callback
, you can create callbacks that you can pass to imported javascript functions. 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.
Asynchronous callbacks are simpler than synchronous callbacks. If an asynchronous callback is passed to a javascript function and the function calls the callback, the callback launches an asynchronous haskell thread. Let's look at the functions that generate 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 IO
. asyncCallback1
returns a callback that accepts one argument. asyncCallback2
for a callback of 2 arguments. You can guess what asyncCallback3
is for.
When a synchronous callback is called in javascript, it launches a new synchronous thread. There are two kinds of synchronous callback.
GHCJS.Concurrent.WouldBlockException
when its thread blocksLet'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
. OnBlocked
is defined in GHCJS.Concurrent
as
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
, syncCallback2
, and syncCallback3
do.
syncCallback'
, syncCallback1'
, and so on generate synchronous callbacks that throw WouldBlockException
when their threads block. It's the same as syncCallback ThrowWouldBlock
, syncCallback1 ThrowWouldBlock
, and so on.
import GHCJS.Foreign.Callback
import Data.JSString -- This includes an IsString instance for JSString
import GHCJS.Types (JSVal)
foreign import javascript unsafe
"require('console').log($1)" js_consoleLog :: JSVal -> IO ()
foreign import javascript unsafe
"require('fs').stat($1, $2)"
js_fsStat :: JSString -> Callback (JSVal -> JSVal -> IO ()) -> IO ()
main :: IO ()
main = do
cb <- asyncCallback2 $ \err stat -> js_consoleLog stat
js_fsStat "/home" cb
releaseCallback cb
releaseCallback
later.For example, if you wanted arbitrary haskell data structures to be stored in and retrieved from javascript hashmap,
GHCJS.Foreign.Export
should be used. GHCJS.Foreign.Export
exports
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
: exports an arbitrary haskell data structure as a blob of javascript value. Since Export a
is actually JSVal
, Export a
can be passed to and retrieved from javascript data structures such as hashmap.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 withExport
call has returned will always return Nothing
.GHCJS.Foreign
exports jsTrue
, jsFalse
, jsNull
, isTruthy
, isString
, isBoolean
, etc, ... You can inspect those functions and understand them easily.
If you already knew javascript APIs, it wouldn't be difficult to inspect and understand JavaScript.Object
, JavaScript.Array
, JavaScript.String
, JavaScript.RegExp
, etc, ...