haskellffireflex

In reflex-dom, how to pass a value from a dynamic to a foreign javascript function (FFI)


I am new to Haskell and reflex-dom, but really like the language. I've been using https://github.com/hansroland/reflex-dom-inbits/blob/master/tutorial.md to learn, and it's been very helpful.

I am currently trying to create a function that takes in a dynamic and creates an element and calls an FFI function every time the value changes in the dynamic. Here is a simplified version of what I'm trying to do.

{-# 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!"

This results in the compile time error:

reflex-canvas.hs:14:10: error:
    • Couldn't match type ‘m’ with ‘Dynamic 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)

I've tried various ways to convert the Dynamic to a m() but cannot figure it out. What is the best practice for doing this?


Solution

  • The function performEvent_ will force execution of a javascript function but performEvent_ wants an Event t (WidgetHost m ()) and, as the error message points out, you've got a Dynamic t (IO ()).

    You can use updated to convert your Dynamic t (IO ()) to an Event t (IO ()) and you can use fmap liftIO to change the IO () in the Event to a WidgetHost m () leaving you with Event t (WidgetHost m ()) which you can pass to performEvent_

    Here's your code with those modifications. I removed the first argument to testSet and the element creation inside testTB because those were not relevant to the problem/solution. I also added some extra type declarations. These were not required but may make things clearer.

    {-# 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