Cómo llamar a Haskell desde Javascript con GHCJS
(2)
He estado jugando con GHCJS. El FFI se puede utilizar para llamar a javascript desde Haskell, pero no puedo averiguar cómo se hace al revés. Digamos que tenía una función de utilidad super útil que escribí en Haskell:
sayHello :: String -> IO ()
sayHello name = print $ "hello, " ++ name
¿Es posible hacer algo para poder llamarlo desde Javascript? Lo más cercano que tengo es notar que h$main(h$main2CMainzimain)
activará mi función principal de Haskell.
Aquí hay un ejemplo que muestra cómo llamar a una función de Haskell desde Javascript. Esto es similar al ejemplo proporcionado por Joachim pero compila y ejecuta con los últimos 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"
La prueba funciona al llamar de Haskell al código Javascript que luego vuelve a llamar a Haskell. La variable, "js_callback_", está disponible dentro de Javascript para usarla como una función que toma un argumento de cadena.
Aquí hay una manera de hacerlo funcionar. Supongamos que tenemos alguna función útil, como
revString :: String -> String
revString = reverse
somethingUseful :: JSString -> IO JSString
somethingUseful = return . toJSString . revString . fromJSString
Para exportar eso, necesitamos hacer una devolución de llamada a través de una de las *Callback
Funciones de GHCJS.Foreign
*Callback
en GHCJS.Foreign
. Pero estos descartarían el valor de retorno, por lo que necesitamos una envoltura que ponga el resultado en un segundo argumento:
returnViaArgument :: (JSRef a -> IO (JSRef b)) -> JSRef a -> JSRef c -> IO ()
returnViaArgument f arg retObj = do
r <- f arg
setProp "ret" r retObj
Mi función main
crea la devolución de llamada y la guarda como algo que es global para 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
Finalmente, necesitamos un poco de envoltura en el lado de JS:
function somethingUseful (arg) {x = {}; somethingUseful_(arg, x); return x.ret};
y ahora podemos usar nuestra buena función implementada por Haskell:
somethingUseful("Hello World!")
"!dlroW olleH"
Estoy usando este truco en una aplicación del mundo real. En JsInterface.hs , que se define como main-in
del executable
en el archivo Cabal , la función main
establece la variable de script java global incredibleLogic_
, mientras que el código de pegamento de JavaScript se encarga de empaquetar y desempaquetar los parámetros.