如何使用GHCJS从Javascript调用Haskell

22

我一直在尝试使用GHCJS。FFI可以用来从Haskell调用javascript,但我无法弄清楚如何反过来。比方说,我有一个非常有用的实用函数是我用Haskell编写的:

sayHello :: String -> IO ()
sayHello name = print $ "hello, " ++ name

有没有可能让我从Javascript中调用某些东西? 我最接近的是注意到 h$main(h$main2CMainzimain) 将触发我的Haskell主函数。


1
如果 这个 仍然适用,那将不会很好看 - 如果你想要那个方向,fay 可能是一个更好的选择。 - Random Dev
如果情况变得更糟,你可以考虑使用FFI来解决问题。因此,如果你有var haskell = {},export = functon(name,val){ haskell [name] = val; };,然后通过FFI将export带入Haskell中,export“sayHello”sayHello应该可以将haskell.sayHello设置为任何函数,而不会出现奇怪的h$main()变量混杂在其中。 - CR Drost
2个回答

12

这里有一个使它正常工作的方法。假设我们有一些有用的函数,比如:

revString :: String -> String
revString = reverse

somethingUseful :: JSString -> IO JSString
somethingUseful = return . toJSString . revString  . fromJSString
为了导出它,我们需要通过GHCJS.Foreign中的*Callback函数之一将其变为回调。但这些会丢弃返回值,因此我们需要一个包装器,将结果放入第二个参数中:

为了导出它,我们需要通过 GHCJS.Foreign 中的 *Callback 函数之一将其变为回调。但是这些函数会丢弃返回值,因此我们需要一个包装器,将结果放入第二个参数中:

returnViaArgument :: (JSRef a -> IO (JSRef b)) -> JSRef a -> JSRef c -> IO ()
returnViaArgument f arg retObj = do
    r <- f arg
    setProp "ret" r retObj

我的main函数创建了一个回调函数,并将其保存为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

最后,JS端需要一个小的反封装函数:

function somethingUseful (arg) {x = {}; somethingUseful_(arg, x); return x.ret};

现在我们可以使用我们漂亮的Haskell实现的函数:

somethingUseful("Hello World!")
"!dlroW olleH"

我在一个真实世界的应用程序中使用了这个技巧。在JsInterface.hs中,它被定义为Cabal文件executable中的main-inmain函数设置全局javascript变量incredibleLogic_,而JavaScript粘合代码则负责打包和解包参数。


你是否在Github上有这个项目?这样我就可以使用GHCJS编译它了。 - jhegedus
谢谢,我会看一下。 - jhegedus
这里的返回值是什么?https://github.com/nomeata/incredible/blob/master/logic/js/js-interface-wrapper.js#L8 在这里,incredibleLogic_是从哪里来的?https://github.com/nomeata/incredible/blob/master/logic/js/js-interface-wrapper.js#L10 - jhegedus
抱歉评论很简短,我当时在用手机。现在我已经在答案中详细说明了 - 这有帮助吗? - Joachim Breitner
请仔细查看 foreign import javascript unsafe 声明:它编译成 JS 代码 "incredibleLogic_ = $1" - Joachim Breitner
显示剩余5条评论

10

这里有一个示例,展示了如何从Javascript调用Haskell函数。这类似于Joachim提供的示例,但是它使用最新的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"

这个测试通过从Haskell调用Javascript代码,然后由Javascript回调到Haskell来实现。变量"js_callback_"在Javascript内部可用作一个函数,该函数接受一个字符串参数。


你使用的 GHCJS 版本是哪个?当我尝试编译上面的代码时,我得到了这些错误:Module ‘GHCJS.Marshal’ does not export ‘fromJSVal’Module ‘GHCJS.Types’ does not export ‘JSVal’。我正在使用 ghcjs-0.2.0.20151001_ghc-7.10.2 - Wizek
dave@6d322ff6c63d:~$ ghcjs --version 伟大的格拉斯哥Haskell编译系统,用于JavaScript,版本0.2.0(GHC 7.10.3) - Dave Compton
这非常奇怪。你有什么想法可能会导致我们拥有不同的模块,即使我们都有 GHCJS-0.2.0吗? - Wizek
不是很确定。我可以在几个小时后更详细地研究一下。 - Dave Compton
1
以上示例可在Github上找到:https://github.com/dc25/ghcjsCallback。我添加了一个名为nodejs的分支,它编译成在node.js下运行的代码:https://github.com/dc25/ghcjsCallback/tree/nodejs。该分支包含一个Haskell-> JavaScript-> Haskell调用序列,其中JavaScript位于仅JavaScript源文件Main.jsexe/tc.js中,对Haskell的回调通过js_callback_变量完成。无需导出js_callback_以在all.js之外访问它。 - Dave Compton
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接