1

I'm trying to set an IORef in threepenny-gui but I can't get it to work. In my app the IORef itself will be more complicated and not itself be displayed - but this example demonstrates the problem I think.

Here is my try:

testIORef2 :: IORef String -> Window -> UI ()
testIORef2 ref window = void $ do
    return window # set title "Test IORef"

    inCell <- UI.input
    outCell   <- UI.input

    getBody window #+ [
            column [
                grid [[string " In cell::", element inCell]
                     ,[string "Out cell::"  , element outCell  ]]
            , string "Cells should update while typing."
            ]]

    -- When value changes write to IORef
    on  UI.valueChange inCell $ \_ -> do
        inValue <- get value inCell
        liftIO $ writeIORef ref inValue    

    -- Read the IORef
    refVal <- liftIO $ readIORef ref

    -- Behaviour which holds the string value in the input cell
    inValue <- stepper "0" $ UI.valueChange inCell
    -- Behaviour which holds the value in the ref
    let outValue = (const refVal) <$> inValue

    -- Set the value of the output cell to the outValue
    element outCell # sink value outValue

The code sort of works but the outValue is not quite up to date.

How do I fix it so that the updates are on time. Also, any improvements to the code would be welcome.

Thanks.

Robin Green
  • 32,079
  • 16
  • 104
  • 187
b1g3ar5
  • 335
  • 2
  • 9

2 Answers2

2

The code you wrote is probably not what you intended to do. The line

let outValue = (const refVal) <$> inValue

specifies that outValue is a Behavior whose value is constant and equal to refValue. In turn, the latter value is obtained from

refVal <- liftIO $ readIORef ref

which means that its the value stored by IORef at this point in time in the UI monad.


When using IORef, you want to read the value of the reference when something changes, and use this value to modify the UI content, for instance like this:

on  UI.valueChange inCell $ \_ -> do
    inValue  <- get value inCell
    liftIO $ writeIORef ref inValue
    outValue <- liftIO $ readIORef ref    
    element outCell # set value outValue

For reasons of consistency (order of operations), it is not advisable to use an IORef as a source for a Behavior — it's either the latter or the former.

Heinrich Apfelmus
  • 11,034
  • 1
  • 39
  • 67
  • But in that code you could just write element outCell # set value inValue. There's no need for a ref. What I was trying to do was have separate code for setting up the inCell and outCell - so the inCell setup function saves an IORef and the outCell setup function reads the ref. – b1g3ar5 May 02 '15 at 10:38
  • Well, you have to know when the `IORef` is written to in order to know when to read it and update the dependencies. In a way, that's what a `Behavior` does, though its more restricted, as it excludes `IO` actions, as their order would matter. In your case, you could register two events with `on UI.valueChange`: one that writes the IORef, and one that reads it. – Heinrich Apfelmus May 03 '15 at 06:59
  • Thanks. Got this working now using the 'on' function. Now going to try to convert to the FRP side. – b1g3ar5 May 03 '15 at 12:22
0

I'm not an expert on threepenny-gui, but here's my guess.

The code you write outside the on event handler is executed just once. You therefore want to include the outValue update inside said handler, e.g.

-- When value changes write to IORef
on  UI.valueChange inCell $ \_ -> do
    inValue <- get value inCell
    liftIO $ writeIORef ref inValue    
    ... -- compute outValue
    element outCell # sink value outValue
chi
  • 111,837
  • 3
  • 133
  • 218
  • The outValue does get updated when I refresh the page. So, maybe the code outside the on handler just gets run once on a refresh... – b1g3ar5 May 01 '15 at 13:39