Как вызвать Haskell из Javascript с помощью GHCJS

Я играл с GHCJS. FFI можно использовать для вызова javascript из Haskell, но я не могу понять, как сделать наоборот. Скажем, у меня есть очень полезная служебная функция, которую я написал на Haskell:

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

Можно ли что-то сделать, чтобы я мог вызвать его из Javascript? Самое близкое, что у меня есть, это заметить, что h$main(h$main2CMainzimain) вызовет мою основную функцию Haskell.


person Gareth Charnock    schedule 30.04.2015    source источник
comment
если это по-прежнему применимо, это будет некрасиво - если вы хотите это направление fay может быть лучшим выбором   -  person Random Dev    schedule 30.04.2015
comment
Если станет еще хуже, вы потенциально можете использовать FFI для решения проблемы. Таким образом, если у вас есть var haskell = {}, export = functon (name, val) { haskell[name] = val; };, а затем вы переносите export в Haskell через FFI, export "sayHello" sayHello потенциально должен установить haskell.sayHello на любую функцию, без странных переменных h$main(), засоряющих все вокруг.   -  person CR Drost    schedule 30.04.2015


Ответы (2)


Вот способ заставить это работать. Предположим, у нас есть какая-то полезная функция, например

revString :: String -> String
revString = reverse

somethingUseful :: JSString -> IO JSString
somethingUseful = return . toJSString . revString  . fromJSString

Чтобы экспортировать это, нам нужно сделать обратный вызов с помощью одной из функций *Callback в GHCJS.Foreign. Но они отбрасывают возвращаемое значение, поэтому нам нужна оболочка, которая помещает результат во второй аргумент:

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, который определен как main-in из executable в Cabal-файле функция main устанавливает глобальная переменная java-скрипта incredibleLogic_, а клей JavaScript code заботится об упаковке и распаковке параметров.

person Joachim Breitner    schedule 24.07.2015
comment
У вас случайно нет такого на гитхабе? Так что я мог бы скомпилировать его с помощью GHCJS? - person jhegedus; 30.07.2015
comment
Спасибо, посмотрю. - person jhegedus; 31.07.2015
comment
Какое возвращаемое значение здесь? github.com/nomeata/incredible/ blob/master/logic/js/ Откуда incredibleLogic_ берется отсюда github.com/nomeata/incredible/blob/master/logic/js/ ? - person jhegedus; 31.07.2015
comment
Извините за краткий комментарий, я был с мобильного телефона. Теперь я подробно остановился на этом в самом ответе - помогает ли это? - person Joachim Breitner; 31.07.2015
comment
Итак, вызов js_set_somethingUseful в main.hs приводит к тому, что somethingUseful_ появляется в глобальном пространстве имен Javascript? Я правильно понимаю? - person jhegedus; 31.07.2015
comment
Да; внимательно посмотрите на объявление foreign import javascript unsafe: оно компилируется в код JS "incredibleLogic_ = $1". - person Joachim Breitner; 31.07.2015
comment
Большое спасибо за объяснение. Я думаю, что скоро напишу свой собственный небольшой пример и, возможно, также опубликую его здесь. - person jhegedus; 31.07.2015
comment
@JoachimBreitner Не могли бы вы включить в свой ответ, какой импорт вы использовали в Haskell? Я пытаюсь скомпилировать приведенный выше код и получаю сообщение «Не входит в область действия: конструктор типа или класс JSFun» и тому подобное. Я попытаюсь выяснить, откуда мог поступать импорт, но я думаю, что это могло бы помочь мне и / или другим, если бы импорт также был включен в ответ. - person Wizek; 12.01.2016
comment
github.com/ghcjs/ghcjs-base/issues/6#issuecomment- 147378568 Во-первых, JSFun похоже переименовали в Callback - person Wizek; 12.01.2016
comment
Ответ 2015-12-18 работает с переименованными типами и функциями. - person Dave Compton; 17.01.2016

Вот пример, который показывает, как вызвать функцию Haskell из Javascript. Это похоже на пример, предоставленный Иоахимом, но компилируется и работает с последними версиями 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, который затем вызывает обратно в Haskell. Переменная «js_callback_» становится доступной в Javascript для использования в качестве функции, принимающей один строковый аргумент.

person Dave Compton    schedule 18.12.2015
comment
Какую версию GHCJS вы использовали? Я получаю эти ошибки: Module ‘GHCJS.Marshal’ does not export ‘fromJSVal’, Module ‘GHCJS.Types’ does not export ‘JSVal’, когда пытаюсь скомпилировать приведенный выше код. Я использую ghcjs-0.2.0.20151001_ghc-7.10.2. - person Wizek; 12.01.2016
comment
dave@6d322ff6c63d:~$ ghcjs --version The Glorious Glasgow Haskell Compilation System for JavaScript, версия 0.2.0 (GHC 7.10.3) - person Dave Compton; 12.01.2016
comment
Это очень любопытно. Любые идеи относительно того, что может привести к тому, что у нас будут разные модули, если у нас обоих есть GHCJS-0.2.0? - person Wizek; 12.01.2016
comment
Не навскидку. Я могу изучить это более подробно через несколько часов. - person Dave Compton; 12.01.2016
comment
Спасибо, я был бы признателен. - person Wizek; 12.01.2016
comment
Не могли бы вы рассказать мне немного больше о вашем окружении? Как вы вызываете ghcjs? Вы используете кабалу? Как вы установили ghcjs? - person Dave Compton; 12.01.2016
comment
Я использовал стек для установки GHCJS. Вот мои файлы stack.yaml и .cabal: gist.github.com/Wizek/5052b377cbc97439e523 - person Wizek; 12.01.2016
comment
На самом деле, может быть, я могу опубликовать на github весь проект, где ошибки компиляции воспроизводятся, секундочку. - person Wizek; 12.01.2016
comment
Давайте продолжим обсуждение в чате. - person Wizek; 12.01.2016
comment
Конфигурация стека в этом репозитории github была настроена на использование ghcjs-0.2.0.20151001_ghc-7.10.2 в соответствии с документацией стека/ghcjs: docs.haskellstack.org/en/stable/ghcjs.html . При компиляции с этой конфигурацией я также вижу ошибки, о которых сообщает @Wizek. Однако если вы измените файл stack.yaml, указав ghcjs-0.2.0.20151029_ghc-7.10.2 , как описано на той же странице документации, код скомпилируется и запустится. - person Dave Compton; 17.01.2016
comment
Как можно получить доступ к js_callback_ из nodejs? Вам нужно будет заставить ghcjs программно поместить js_callback_ в module.exports его родительского файла (я предполагаю, all.js). Это возможно? - person George; 12.04.2016
comment
Приведенный выше пример доступен на github: github.com/dc25/ghcjsCallback. Я добавил ветку nodejs, которая компилируется в код, работающий под node.js: github.com/ dc25/ghcjsCallback/tree/nodejs . Эта ветвь содержит последовательность вызовов haskell -> javascript -> haskell, в которой javascript находится в исходном файле только javascript, Main.jsexe/tc.js. Обратный вызов haskell выполняется через переменную js_callback_. Не нужно было экспортировать js_callback_, чтобы получить к нему доступ за пределами all.js. - person Dave Compton; 13.04.2016