在reflex-dom中,如何将动态值传递给外部JavaScript函数(FFI)?

5
我是Haskell和reflex-dom的新手,但我非常喜欢这门语言。我一直在使用https://github.com/hansroland/reflex-dom-inbits/blob/master/tutorial.md进行学习,它非常有帮助。
我目前正在尝试创建一个函数,该函数接受一个动态参数,并创建一个元素,每当动态参数的值发生变化时调用一个FFI函数。这是我尝试做的简化版本。
{-# LANGUAGE OverloadedStrings #-}
import Data.Text as T
import qualified GHCJS.DOM.Types as GDT
import GHCJS.Types
import Reflex.Dom

foreign import javascript safe
  "$1.value = $2"
  testSet :: JSVal -> JSVal -> IO()

testTB :: DomBuilder t m => Dynamic t T.Text -> m ()
testTB dt = do
  (e, _) <- elAttr' "input" ("type" =: "text") blank
  bob <- (testSet (GDT.pToJSVal e) . GDT.pToJSVal) <$> dt
  return ()

main = mainWidget $ testTB $ constDyn "Hello World!"

这会导致编译时错误:

reflex-canvas.hs:14:10: error:
    • Couldn't match type ‘m’ withDynamic t’
      ‘m’ is a rigid type variable bound by
        the type signature for:
          testTB :: forall t (m :: * -> *).
                    DomBuilder t m =>
                    Dynamic t Text -> m ()
        at reflex-canvas.hs:11:11
      Expected type: m (IO ())
        Actual type: Dynamic t (IO ())
    • In a stmt of a 'do' block:
        bob <- (testSet (GDT.pToJSVal e) . GDT.pToJSVal) <$> dt
      In the expression:
        do { (e, _) <- elAttr' "input" ("type" =: "text") blank;
             bob <- (testSet (GDT.pToJSVal e) . GDT.pToJSVal) <$> dt;
             return () }
      In an equation for ‘testTB’:
          testTB dt
            = do { (e, _) <- elAttr' "input" ("type" =: "text") blank;
                   bob <- (testSet (GDT.pToJSVal e) . GDT.pToJSVal) <$> dt;
                   return () }
    • Relevant bindings include
        e :: Element EventResult (DomBuilderSpace m) t
          (bound at reflex-canvas.hs:13:4)
        dt :: Dynamic t Text (bound at reflex-canvas.hs:12:8)
        testTB :: Dynamic t Text -> m () (bound at reflex-canvas.hs:12:1)

我尝试了各种方法将动态内容转换为m(),但无法弄清楚。如何最好地实现这一点?


你尝试过 liftIO 吗?我的意思是 liftIO (testSet (GDTpToJSVal e) . GDT.pToJSVal) =<< (sample $ current dt)?我不是 Reflex 的专家,但这应该可以通过类型检查,虽然我必须说我没有验证过。 - epsilonhalbe
您选择省略了大部分错误信息,而这些信息通常在完整的情况下非常有用。理解错误信息很可能会告诉您如何修复它;一个好的答案将给出修复方法并解释错误(教人捕鱼而非授人以鱼...)。 - user2407038
@epsilonhalbe 我刚试了几个liftM和liftIO的变化,但好像无法使其正常工作。 - Thanacles
@user2407038 刚刚添加了完整的错误信息。 - Thanacles
你无法直接从Dynamic monad中获取T.Text值并将其直接用作_testSet_函数的参数。您必须在monad内部以applicative模式运行_testSet_函数。类似testSet <$> ptrToHTMLControl <*> ptrToText这样的东西。 - Jogger
1个回答

3

performEvent_函数将强制执行JavaScript函数,但performEvent_需要一个Event t (WidgetHost m ()),而正如错误消息所指出的那样,你得到了一个Dynamic t (IO())

你可以使用updated将你的Dynamic t (IO ())转换为Event t (IO ()),并且你可以使用fmap liftIOEvent中的IO ()更改为WidgetHost m (),这样你就得到了Event t (WidgetHost m ()),可以传递给performEvent_

以下是经过这些修改后的代码。我删除了testSet中的第一个参数以及testTB中的元素创建,因为它们与问题/解决方案无关。我还添加了一些额外的类型声明。这些不是必需的,但可能会使事情更清晰。

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
import Data.Text as T (Text)
import qualified GHCJS.DOM.Types as GDT (pToJSVal)
import GHCJS.Types (JSVal)
import Reflex.Dom
import Control.Monad.Trans (liftIO)

foreign import javascript safe
  "console.log $1"
  testSet :: JSVal -> IO()

testTB :: forall t m.  MonadWidget t m => Dynamic t T.Text -> m ()
testTB dt = do 
    let bob :: Dynamic t (IO ())
        bob = (testSet.(GDT.pToJSVal)) <$> dt  

        bobIOEvent :: Event t (IO ())
        bobIOEvent = updated bob

        bobWidgetHostEvent :: Event t (WidgetHost m ())
        bobWidgetHostEvent = fmap liftIO bobIOEvent

    performEvent_ bobWidgetHostEvent

main = mainWidget $ do
    ti <- textInput def 
    let dt = value ti
    testTB dt

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