With Reflex.GI.Gtk, how to use and force evaluation of a dynamic from within an event

149 Views Asked by At

When using reflex-gi-gtk-0.2.0.0 I can access a dynamic from within an event:

submitButtonE4 <- eventOnSignal submitButton #clicked 
          (
            do
             let processDyn dynCompany = do
                   case dynCompany of 
                     Just company -> do 
                       path <- chartAnnualROA company fileOptions800x600 --generateChart company
                       Gtk.imageClear chartImage
                       Gtk.set chartImage  [#file := T.pack defaultReportPath]
                       --return x -- path 
                       case T.null $ T.pack path of
                         True ->   return "" --dynCompany
                     Nothing -> return "" --  dynCompany 
             return $ ffor  maybeCompanyDyn processDyn
            >>= )

But in order to be evaluated, I need to bind it to a label: sink submitClickStatusLabel [#label :== ffor submitButtonE4 (T.pack . show)] which does not work as it is in Dynamic (SpiderTimeline x) (IO (Maybe Company)).

So instead I must go and get the info that the dynamic was bound to:

          (
            do
              name <- Gtk.get companyCboxBoxEntryWidget #text
              case Map.lookup name companyMap of 
                Just company -> do 
                  path <- chartAnnualROA company fileOptions800x600 --generateChart company
                  Gtk.imageClear chartImage
                  Gtk.set chartImage  [#file := T.pack defaultReportPath]
                  return path 
                Nothing -> return "../investingRIO/src/Data/Reports/initialChart.svg"
            >>= )

and now I can sink it and cause evalution.

sink submitClickStatusLabel [#label :== ffor submitButtonE (T.pack . show)]

I am unable to find any way to force the evaluation when using the first method. How do I force the evalution without sinking to another widget?

Thanks

2

There are 2 best solutions below

2
On

Here is the new version, based on Kritzefitz's answer.

An event for selecting a company from a combobox, which is same as before

companySelectionE <- eventOnAttribute companyCboxBoxEntryWidget #text

Replaced dynamic with a behavior.

companySelectionB <- hold Nothing $ ffor companySelectionE (`Map.lookup` companyMap) 

generateChart (renamed from processDyn) returns a () instead of a FilePath, which was an attempt at forcing evaluation, now done by performEvent.

    let 
      generateChart company = do
        case company of 
          Just companyJ -> do 
            chartAnnualROA companyJ fileOptions800x600 
            Gtk.imageClear chartImage
            Gtk.set chartImage  [#file := T.pack defaultReportPath]
            return () 
          Nothing -> return () 

submitClickedE now uses eventOnSignal0 instead of eventOnSignal

    submitClickedE <- eventOnSignal0 submitButton #clicked

Creating a chart from the selected company is now a behavior instead of a dynamic.

    let generateChartB = generateChart <$> companySelectionB

Now I use <@ to create a new event from the submit event and generate chart behavior.

     let generateChartE = generateChartB <@ submitClickedE 

And the use of performEvent, which eliminated all the labels I was creating and sinking to in an attempt to get my IO to evaluate. It also eliminated the FilePath return from generateChart, aslo an attempt to force evaluation.

    processedCompany <- performEvent $ runGtk <$> generateChartE

Thank cleared up a lot of things for me, thanks. Here it is in a single quote for easier reading:

    companySelectionE <- eventOnAttribute companyCbox #text
    companySelectionB <- hold Nothing $ ffor companySelectionE (`Map.lookup` companyMap)
    let 
        generateChart company = do
          case company of 
            Just companyJ -> do 
              chartAnnualROA companyJ fileOptions800x600 
              Gtk.set chartImage  [#file := T.pack defaultReportPath]
              return () 
            Nothing -> return () 
         
    submitClickedE <- eventOnSignal0 submitButton #clicked
    let generateChartB = generateChart <$> companySelectionB
    let generateChartE = generateChartB <@ submitClickedE
    processedCompany <- performEvent $ runGtk <$> generateChartE

1
On

I think most of your trouble comes from the fact, that you want to do substantial amounts of work inside eventOnSignal . This place is not intended to do the actual heavy lifting of your business logic and it doesn't provide you with the proper context to effectively work with reactive values, such as Dynamics, as you are currently experiencing.

The actual use case for the eventOnSignal* family of functions is to obtain basic inputs for your reactive network. The input provided by a button doesn't carry any actual information. It just provides the information when the button has been clicked. For cases like this you usually don't want to use eventOnSignal directly, but rather eventOnSignal0, so let's do that:

submitClickedE <- eventOnSignal0 submitButton #clicked

The type returned by this is submitClickedE :: Event t (). As you can see, the Event has a () as its value, which is what we want, because merely clicking the button doesn't produce any value by itself. But you want to call an IO-producing function on the value inside processDyn, so let's first construct the IO action you want to execute:

let processDynD = processDyn <$> dynCompany

The assignment here has the type processDynD :: Dynamic t (IO (Maybe Company)). As you can see, the IO hasn't been executed yet. Luckily reflex provides an operation to execute IO actions inside reactive values, called performEvent :: Event t (Performable m a) -> m (Event t a). There are two things about this type, that don't quite fit what we need at the moment. First, it expects the monad to be performed to be a Performable m whereas we have IO, but we will get to that in a moment. The second and more pressing concern is that performEvent expects an Event, not a Dynamic. This makes sense, because you can't execute an IO action continuously. You have to decide when the IO action is executed.

AIUI you want the IO to be executed, when the submitButton is clicked. So we want an Event that fires whenever submitClickedE fires, but it should fire the current value inside processDynD. Doing something like this is called “sampling a Behavior with an Event” and can be done using the operator (<@). In your case you want to sample a Dynamic, but you can always turn a Dynamic into a Behavior using current. So to get the expected Event you can use this:

let processDynE = current processDynD <@ submitClickedE

The assignment has the value processDynE :: Event t (IO (Maybe Company)). But as you can see, the IO still hasn't been executed. We can now do that using performEvent as discussed earlier:

processedCompany <- performEvent $ runGtk <$> processDynE

We use runGtk to lift the IO in processDynE to the required Performable m. The returned value has the type processedCompany :: Event t (Maybe Company). You can now sink this into your output label, as was your original intention:

sink submitClickStatusLabel [#label :== T.pack . show <$> processedCompany]

Note though, that unlike your original attempt, we now ended up with an Event instead of a Dynamic. If you actually need a Dynamic from all of this, you have to construct it from the Event using holdDyn initialValue processedCompany. But then you have to provide an initialValue because otherwise there is no value for the Dynamic before the submitButton has been clicked for the first time.