22

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.

Gareth Charnock
  • 1,166
  • 7
  • 17
  • 1
    if [this](http://weblog.luite.com/wordpress/?p=14) still applies it will not be pretty - if you want that direction [fay](https://github.com/faylang/fay/wiki/Calling-Fay-from-JavaScript) might be a better choice – Random Dev Apr 30 '15 at 12:14
  • If worse comes to worse you could potentially use the FFI to solve the problem. So if you have `var haskell = {}, export = functon (name, val) { haskell[name] = val; };` and then bring `export` into Haskell via FFI, `export "sayHello" sayHello` should potentially set `haskell.sayHello` to be whatever the function is, without weird `h$main()` variables littering everything. – CR Drost Apr 30 '15 at 13:44

2 Answers2

12

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.

Joachim Breitner
  • 25,395
  • 6
  • 78
  • 139
  • Do you happen to have this somewhere in github? So I could compile it using GHCJS ? – jhegedus Jul 30 '15 at 16:39
  • Thanks, I have a look. – jhegedus Jul 31 '15 at 07:11
  • What is the return value here ? https://github.com/nomeata/incredible/blob/master/logic/js/js-interface-wrapper.js#L8 Where is `incredibleLogic_` coming from here https://github.com/nomeata/incredible/blob/master/logic/js/js-interface-wrapper.js#L10 ? – jhegedus Jul 31 '15 at 07:16
  • Sorry for the brief comment, I was on a mobile phone. I now elaborated on it in the answer itself – does it help? – Joachim Breitner Jul 31 '15 at 07:33
  • So, calling `js_set_somethingUseful` in `main.hs` causes `somethingUseful_` to appear in the Javascript global namespace ? Do I understand it correctly? – jhegedus Jul 31 '15 at 07:52
  • Yes; have a close look at the `foreign import javascript unsafe` declaration: It compiles to the JS code `"incredibleLogic_ = $1"`. – Joachim Breitner Jul 31 '15 at 07:53
  • Many thanks for the explanation. I think I will write soon my own little example and perhaps post it here as well. – jhegedus Jul 31 '15 at 08:11
  • @JoachimBreitner Would you be willing to include in your answer what imports did you use in Haskell? I'm trying to compile the above code and am getting 'Not in scope: type constructor or class ‘JSFun' and similar. I'll try to hunt down where the imports could be coming from, but I think it could help me and/or others if the imports were also included in the answer. – Wizek Jan 12 '16 at 11:45
  • https://github.com/ghcjs/ghcjs-base/issues/6#issuecomment-147378568 For one, `JSFun` seems to have been renamed to `Callback` – Wizek Jan 12 '16 at 12:27
  • The 2015-12-18 answer works with the renamed types and functions. – Dave Compton Jan 17 '16 at 03:55
10

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.

Dave Compton
  • 1,421
  • 1
  • 11
  • 18
  • Which version of GHCJS did you use? I get these errors: `Module ‘GHCJS.Marshal’ does not export ‘fromJSVal’`, `Module ‘GHCJS.Types’ does not export ‘JSVal’` when I try to compile the above code. I'm using `ghcjs-0.2.0.20151001_ghc-7.10.2`. – Wizek Jan 12 '16 at 11:22
  • dave@6d322ff6c63d:~$ ghcjs --version The Glorious Glasgow Haskell Compilation System for JavaScript, version 0.2.0 (GHC 7.10.3) – Dave Compton Jan 12 '16 at 17:26
  • That is very curious. Any ideas as to what might cause us to have different modules if we both have GHCJS-0.2.0? – Wizek Jan 12 '16 at 17:28
  • Not offhand. I can look into this in more detail in a few hours. – Dave Compton Jan 12 '16 at 17:38
  • Thanks, I would appreciate that. – Wizek Jan 12 '16 at 17:40
  • Can you tell me a little more about your environment? How are you invoking ghcjs? Are you using cabal? How did you install ghcjs? – Dave Compton Jan 12 '16 at 20:22
  • I've used stack to install GHCJS. Here is my stack.yaml and .cabal files: https://gist.github.com/Wizek/5052b377cbc97439e523 – Wizek Jan 12 '16 at 20:25
  • Actually, maybe I can post to github the whole project where the compile errors are reproducible, just a sec. – Wizek Jan 12 '16 at 20:31
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/100515/discussion-between-wizek-and-dave). – Wizek Jan 12 '16 at 20:31
  • The stack configuration in this github repo was configured to use ghcjs-0.2.0.20151001_ghc-7.10.2 per the stack/ghcjs documentation : http://docs.haskellstack.org/en/stable/ghcjs.html . When compiling with that configuration, I also see the errors that @Wizek reports. However, if you modify the stack.yaml file to specify ghcjs-0.2.0.20151029_ghc-7.10.2 , as described on the same documentation page, then the code compiles and runs. – Dave Compton Jan 17 '16 at 03:48
  • How would one access `js_callback_` from a nodejs? You would have to get ghcjs to programmatically place `js_callback_` within the `module.exports` of its parent file (`all.js` I'm assuming). Is this possible? – George Apr 12 '16 at 08:32
  • 1
    The above example is available in github : https://github.com/dc25/ghcjsCallback . I added a branch, nodejs, that compiles to code that runs under node.js : https://github.com/dc25/ghcjsCallback/tree/nodejs . This branch contains a haskell -> javascript -> haskell calling sequence in which the javascript is in a javascript only source file, `Main.jsexe/tc.js`, The callback to haskell is done via the `js_callback_` variable. It was not necessary to export `js_callback_` to access it outside of `all.js` . – Dave Compton Apr 13 '16 at 03:38