3

I'm doing some research into practical aspects of FRP for UI's and I've been struggling with implementing the following functionality using reactive banana: based on the value of a selection box, a variable amount of list boxes are rendered which display some results. (I'm using WxHaskell.)

It was pretty straightforward to implement this using a bunch of prepared list boxes that are hidden and shown based on the result behavior, but this time I want it to create and destroy list boxes as needed, each list box linked to the results behavior.

So far I have the following ingredients:

  • an event eParam which is bound to the selection box
  • a behavior bResults :: Behavior t [[String]] defined with eParam (and stepper) which holds all the results (lists of items per list box)
  • an update function updateResultControls :: [SingleListBox ()] -> [[String]] -> IO [SingleListBox ()] which destroys or builds the list boxes based on the results. Note that the return type is in IO.

Looking at the BarTab example, I've tried to implement the following:

  • a behavior bResultControls :: Behavior t [SingleListBox ()] with the list boxes, defined as stepper [] eUpdateResultControls.
  • an event eUpdateResultControls :: Event t [SingleListBox ()] that performs the UI update. This event depends on the behaviors bResultControls and bResults. However, it also has to update the network and run IO, so I suspect Moment and execute will be involved. This is where I got stuck.

My latest attempt is this:

rec
  let
    bResultControls = stepper [] eResultControls
    bResultControlsUpdate = updateResultControls <$> bResultControls <*> bResults

  eResultControls <- execute $ FrameworksMoment . liftIO <$> (bResultControlsUpdate <@ eParam)

But I get the following type error:

Couldn't match type `m0 [SingleListBox ()]'
              with `forall t1. Frameworks t1 => Moment t1 [SingleListBox ()]'
Expected type: IO [SingleListBox ()]
               -> forall t. Frameworks t => Moment t [SingleListBox ()]
  Actual type: IO [SingleListBox ()] -> m0 [SingleListBox ()]
In the second argument of `(.)', namely `liftIO'
In the first argument of `(<$>)', namely
  `FrameworksMoment . liftIO'
In the second argument of `($)', namely
  `FrameworksMoment . liftIO <$> (bResultControlsUpdate <@ eParam)'

I suspect this will involve trimming some behaviors, or perhaps I'm going about this entirely the wrong way.

Jacob
  • 106
  • 5
  • (The way of working around this problem I was advised to use in other languages is to have a fixed set of boxes and a scrollbar. Based on what the user does with the scrollbar you edit what's in the boxes without actually moving them so you don't create an arbitrary number of boxes. I never liked it, so if there's a better way that's not a memory leak, I'd love to hear it; thanks for the question.) – AndrewC Aug 03 '14 at 16:46
  • 1
    You need to be careful about the type parameter `t`, which you can think of as starting time. The argument to the `FrameworksMoment` constructor must be polymorphic in `t`, but your example isn't, that's why you get a type error. I'd recommend to study the `BarTab` example more closely, and understand why the `AnyMoment` type is there, this will hopefully help. The underlying issue is that the behaviors for the list boxes start at different times, and the type system is used to track that. – Heinrich Apfelmus Aug 05 '14 at 15:21
  • Hi, first of all thanks for the very interesting work on reactive banana. :) I think a difference between `BarTab` and what I'm trying to do is that I want to use an existing behavior to define these listboxes, which seems to leak the `t` bound by `networkDescription` (so the original starting time) into the `Moment` I'm trying to define for `execute` (which can't be polymorphic as a result. (I have tried with type signatures btw.)) Could this be the case? (I'm not so much interesting in creating behaviors based on these new list boxes in this example, which is what `BarTab` does.) – Jacob Aug 05 '14 at 22:13

1 Answers1

2

After some more reading and experimenting I got it to work with some careful trimming and refactoring (as hinted at by Heinrich):

networkDescription :: forall t. Frameworks t => Moment t ()
networkDescription = do
  eParam <- choiceSelection cParam

  let bResults = results <$> stepper x eParam

  bResults_ <- trimB bResults

  rec
    let
      bResultControls = stepper [] eResultControls

      mkResultControls :: [SingleListBox ()] -> [[String]] -> FrameworksMoment [SingleListBox ()]
      mkResultControls cs rs = FrameworksMoment $ do
        slResults <- liftIO $ updateResultControls cs rs

        bResults <- now bResults_

        sequence_ [sink sl [items :== (!! i) <$> bResults] | sl <- slResults | i <- [0..]]

        liftIO $ do
          let n = length rs
          set f [clientSize := sz (150 * n) 200]
          set pResults [layout := fill $ boxed "results" $ row n (map (fill . widget) slResults)]
          refit f

        return slResults

    eResultControls <- execute $ (mkResultControls <$> stepper [] eResultControls <*> bResults) <@ eParam

  return ()

(Just got a little bug now where the event fires before the behavior updates but that should be easy to fix.)

Jacob
  • 106
  • 5
  • 1
    Looks good to me. Remark: The use of `stepper [] eResultControls` in the penultimate line can be replaced by `bResultControls`. As for the event firing before the behavior: that's always the case. You may have to use `accumE` to get the desired simultaneity, but I'm not sure if it works well with `execute`. In general, using functions in `IO` to calculate event values is somewhat discouraged, because the order of `IO` actions is undefined. Sometimes you need to do that, though, that's why we have `execute` and `reactimate`. – Heinrich Apfelmus Aug 07 '14 at 11:14