There are mainly 3 kinds of marshalable value types which can be directly used as function arguments and return values in either JSFFI imports or exports:
- Regular Haskell value types like
StablePtr, etc. When the
UnliftedFFITypesextensions are enabled, some unboxed types like
Int#are also supported.
JSValtype and its
JSVal type is exported by
imports to obtain
them in Haskell data structures like ordinary Haskell values.
garbage collected, but it's also possible to call
freeJSVal to explicitly free
them in the runtime.
Any type in
GHC.Exts represents a boxed Haskell value, which is a
managed pointer into the heap. This is only intended to be used by power users.
Just like regular
ccall imports/exports, the result type of
imports/exports can be wrapped in
IO or not.
Asterius.Types additionally exports these types:
JSVal and can be directly used as argument or result
e.g. it won't check if
typeof $1 === "string" when
$1 is declared as a
JSString. It's up to the users to guarantee the runtime invariants about such
JSVal wrapper types.
JSVal can also be used as marshalable value types,
as long as the
newtype constructor is available in scope.
The Asterius standard library provides functions for common marshaling purposes:
import Asterius.Aeson import Asterius.ByteString import Asterius.Text import Asterius.Types fromJSArray :: JSArray -> [JSVal] toJSArray :: [JSVal] -> JSArray fromJSString :: JSString -> String toJSString :: String -> JSString byteStringFromJSUint8Array :: JSUint8Array -> ByteString byteStringToJSUint8Array :: ByteString -> JSUint8Array textFromJSString :: JSString -> Text textToJSString :: Text -> JSString jsonToJSVal :: ToJSON a => a -> JSVal jsonFromJSVal :: FromJSON a => JSVal -> Either String a jsonFromJSVal' :: FromJSON a => JSVal -> a
Keep in mind that when passing 64-bit integers via
Word, etc, precision
can be lost, since they're represented by
the future, we may consider using
bigints instead of
numbers as the
The source text of
$n to refer to the
n-th argument (starting from
1). It's possible to use IIFE(Immediately Invoked Function Expression) in the
The safety level in a
unsafe import, the whole runtime blocks until the result is
Promise which later resolves with the result. The current thread will be
suspended when such an import function is called, and resumed when the
resolves or rejects. Other threads may continue execution when a thread is
blocked by a call to an async import.
throw exceptions or reject the
Promise with errors. They are wrapped as
JSExceptions and thrown in the calling thread, and the
JSExceptions can be
handled like regular synchronous exceptions in Haskell.
JSException is also
Asterius.Types; it contains both a
JSVal reference to the
String representation of
In the source text of a
everything in the global scope and the function arguments. Additionally, there
__asterius_jsffi binding which represents the Asterius instance object.
__asterius_jsffi exposes certain interfaces for power users, e.g.
typed array. The interfaces are largely undocumented and not likely to be useful
to regular users.
There is one usage of
__asterius_jsffi which may be useful to regular users
though. Say that we'd like the JSFFI import code to call some 3rd-party library
code, but we don't want to pollute the global scope; we can assign the library
functions as additional fields of the Asterius instance object after it's
newAsteriusInstance(), then access them using
in the JSFFI import code.
function name, which must be globally unique. The supported export function
types are the same with JSFFI imports.
exported function needs an additional
--export-function flag to be passed to
newAsteriusInstance() returns the Asterius instance
object, one can access the exported functions in the
const r = await i.exports.mult_hs(6, 7);
Promise resolves with the result when the thread successfully
serialized form of the Haskell exception if present.
It's safe to call a JSFFI export function multiple times, or call another JSFFI
export function before a previous call resolves/rejects. The export functions
imported back to Haskell as
JSVals and called in Haskell.
signature must be of the form
Fun -> IO JSVal, where
Fun represents a
marshalable JSFFI function type in either JSFFI imports or static exports, and
the result can be
JSVal or its
After declaring the "wrapper" function, one can pass a Haskell function closure
to it and obtain the
exported function can be used in the same way as the JSFFI static exports.
When a JSFFI dynamic export is no longer useful, call
free it. The
StablePtr of the Haskell closure will be freed.
Sometimes, we expect a JSFFI dynamic export to be one-shot, being called for
only once. For such one-shot exports, use
freeHaskellCallback for one-shot exports.