问题
I've been playing about with GHCJS. The FFI can be used to call javascript from Haskell but I can't figure out how do go the other way round. Say I had a super useful utility function I wrote in Haskell:
sayHello :: String -> IO ()
sayHello name = print $ "hello, " ++ name
Is it possible do something so I could call it from Javascript? The closest I've got is noticing that h$main(h$main2CMainzimain)
will trigger my Haskell main function.
回答1:
Here is a way to make it work. Assume we have some useful function, like
revString :: String -> String
revString = reverse
somethingUseful :: JSString -> IO JSString
somethingUseful = return . toJSString . revString . fromJSString
In order to export that, we need to make it a callback via one of the *Callback
functions in GHCJS.Foreign
. But these would discard the return value, so we need a wrapper that puts the result into a second argument:
returnViaArgument :: (JSRef a -> IO (JSRef b)) -> JSRef a -> JSRef c -> IO ()
returnViaArgument f arg retObj = do
r <- f arg
setProp "ret" r retObj
My main
function creates the callback, and saves it as something that’s global to JavaScript:
foreign import javascript unsafe "somethingUseful_ = $1"
js_set_somethingUseful :: JSFun a -> IO ()
main = do
callback <- syncCallback2 NeverRetain False (returnViaArgument somethingUseful)
js_set_somethingUseful callback
Finally, we need a little un-wrapper on the JS side:
function somethingUseful (arg) {x = {}; somethingUseful_(arg, x); return x.ret};
and now we can use our nice Haskell-implemented function:
somethingUseful("Hello World!")
"!dlroW olleH"
I am using this trick in a real-world application. In JsInterface.hs, which is defined as main-in
of the executable
in the Cabal file, the main
function sets the global java script variable incredibleLogic_
, while the JavaScript glue code takes care of packing and unpacking the parameters.
回答2:
Here's an example that shows how to call a Haskell function from Javascript. This is similar to the example provided by Joachim but compiles and runs with the latest ghcjs.
import GHCJS.Marshal(fromJSVal)
import GHCJS.Foreign.Callback (Callback, syncCallback1, OnBlocked(ContinueAsync))
import Data.JSString (JSString, unpack, pack)
import GHCJS.Types (JSVal)
sayHello :: String -> IO ()
sayHello name = print $ "hello, " ++ name
sayHello' :: JSVal -> IO ()
sayHello' jsval = do
Just str <- fromJSVal jsval
sayHello $ unpack str
foreign import javascript unsafe "js_callback_ = $1"
set_callback :: Callback a -> IO ()
foreign import javascript unsafe "js_callback_($1)"
test_callback :: JSString -> IO ()
main = do
callback <- syncCallback1 ContinueAsync sayHello'
set_callback callback
test_callback $ pack "world"
The test works by calling from Haskell into Javascript code that then calls back into Haskell. The variable, "js_callback_", becomes available within Javascript for use as a function that takes one string argument.
来源:https://stackoverflow.com/questions/29967135/how-to-call-haskell-from-javascript-with-ghcjs