1

Based on my previous question whose setting I refined gradually (How to create monadic behaviour in reactive-banana):

Suppose there is eKey, an event fired whenever a key is pressed, b of type Buffer which is modified appropriately whenever a supported event occurs in eKey, and finally there are IO actions to be taken on some of the events. These IO actions depend on the state of b (for simplicity assume they print the current state of b to the console).

Currently, I have this to select the action that happens on an event:

getKeyAction :: KeyVal -> Maybe (IO Buffer -> IO Buffer)
getKeyAction 65288 = Just . liftM $ backspace
getKeyAction 65293 = Just $ \mb -> do
    b <- mb
    print $ "PRESSED ENTER: " ++ toString b
    return emptyBuffer
getKeyAction 65360 = Just . liftM $ leftMost
getKeyAction 65361 = Just . liftM $ left
...
getKeyAction _     = Nothing

and then I do:

let
    eBufferActions = filterJust $ getKeyAction <$> eKey
    bBuffer        = accumB (return emptyBuffer) eBufferActions -- model `b`
eBuffer <- changes bBuffer
reactimate' $ fmap displayBuffer <$> eBuffer

for some displayBuffer :: IO Buffer -> IO ().

It does not seem to work as intended. The state of bBuffer seems to be evaluated on every event anew (effectively running all IO actions collected thus far every time an event occurs) which makes sense when I think about it in retrospect.

How should I refactor my code to do this correctly? (i.e. IO actions see current state of the buffer, nothing except the buffer gets accumulated)


If I could construct an Event that bears value of the bBuffer on occasion of an appropriate eKey event, then I could simply map my a dedicated IO action over it and reactimate. What do you think? How to do that? Would <@ achieve what I am trying to do? But how would I postpone the current change to b associated with the current key press after I take snapshot of b with <@ to map the Buffer -> IO () over it?

Community
  • 1
  • 1
jakubdaniel
  • 2,233
  • 1
  • 13
  • 20

1 Answers1

1

OK, so I believe this solves my problem, but I am not sure it is the right thing to do. So please comment.

  1. I factor out those actions that do some non-trivial IO (other than return)
  2. I filter the eKey event into two: eBuffer and eConfirm
    1. eBuffer collects all modifying events (including clearing the buffer on confirmation)
    2. eConfirm collects all confirmation events
  3. I tag eConfirm with values of bBuffer, which captures the evolution of the buffer
  4. Finally, I reactimate separately the IO and the changes of the buffer

The code fragment:

getKeyAction :: KeyVal -> Maybe (Buffer -> Buffer)
getKeyAction 65288 = Just backspace
-- omit action for ENTER
...


getConfirm :: KeyVal -> Maybe (Buffer -> Buffer)
getConfirm 65293 = Just (const mkBuffer) -- Clear buffer on ENTER
getConfirm _     = Nothing

Then in the description of the network:

let
    eBuffer  = filterJust $ getKeyAction <$> eKey
    eConfirm = filterJust $ getConfirm   <$> eKey
    bBuffer  = accumB mkBuffer $ unions [ eBuffer, eConfirm ]
    eEval    = bBuffer <@ eConfirm

eBufferChanges <- changes bBuffer

reactimate  $         evalBuffer <$> eEval
reactimate' $ fmap displayBuffer <$> eBufferChanges

for evalBuffer :: Buffer -> IO () and displayBuffer :: Buffer -> IO ().

jakubdaniel
  • 2,233
  • 1
  • 13
  • 20
  • Looks good to me. Step 1 is the key to everything. That said, I'm a little confused about your naming conventions. Why do you call it `evalBuffer`? As far as I understand, `Buffer` is an immutable value, which you change by applying pure functions `Buffer -> Buffer`. – Heinrich Apfelmus Oct 15 '15 at 12:49
  • `evalBuffer` is an `IO` action that takes the "current" state of `Buffer` and evaluates it (sends the buffer to an interpreter and that prints a result to console). If you meant `eEval` those are the events that require evaluation. I personally do not stand by this naming, it was just the first idea I had. – jakubdaniel Oct 15 '15 at 13:02