4

I am trying to implement a dynamic set of wires in netwire 5 "properly". I've read the answer to wires of wires question, and I don't particularly like how the code in the example relies on the Event converted to a behaviour to show up non-empty on exactly one execution of stepWire.

So, I want to add and remove wires in the dynamic set via Events, and hopefully without tapping into Unsafe.Event or equivalent hackery. Let's drop the removing part for simplicity, and just make it possible to add Wires:

dynWireSet1 :: (Monad m, Monoid s)
            => Wire s e m (a, Event (Wire s e m a b)) [b]

Each event adds a new wire to the (initially empty) list (or other set) of wires hidden inside, and they all run, all getting the input of type a, and have their outputs collected into a list.

The running part is relatively easy, with googleable examples, e.g.:

dynWireSet1 = runWires1 []
runWires1 :: (Monad m, Monoid s)
          => [Wire s e m a b]
          -> Wire s e m (a, Event (Wire s e m a b)) [b]
runWires1 wires = mkGen $ \session (input, event) -> do
  stepped <- mapM (\w -> stepWire w session (Right input)) wires
  let (outputs, newwires) = unzip stepped
  return (sequence outputs, runWires1 newwires)

Example above ignores events. I suspect that it is impossible to use the event inside the transition function, other than by the event function from Unsafe.Event. Is that correct? I want to avoid Unsafe.Event.

When I step back and look at the suggested ways of using events, I see a function that looks very promising:

krSwitch :: Monad m
         => Wire s e m a b
         -> Wire s e m (a, Event (Wire s e m a b -> Wire s e m a b)) b

Now, what if I start with simplified runWires:

runWires2 :: (Monad m, Monoid s)
          => [Wire s e m a b]
          -> Wire s e m a [b]
runWires2 wires = mkGen $ \session input -> do
  stepped <- mapM (\w -> stepWire w session (Right input)) wires
  let (outputs, newwires) = unzip stepped
  return (sequence outputs, runWires2 newwires)

and make dynWireSet a krSwitch:

dynWireSet2 :: (Monad m, Monoid s)
            => Wire s e m (a, Event (Wire s e m a b)) [b]
dynWireSet2 = krSwitch (runWires2 []) . second (mkSF_ (fmap addWire))
addWire :: Wire s e m a b -> Wire s e m a [b] -> Wire s e m a [b]
addWire = undefined

I am almost there! Now if I only could fmap a (:) over runWires2 and get the new wire inserted into newwires, I'd be all set! But this is not possible, in general case. In fact, fmap over WGen just fmaps over the output, if I get it right. Useless.

And now, here is my idea. Let's introduce a new variant of data Wire, I'll provisionally call it WCarry g st because it will carry its internal state in a distinct data type. It's transition function will be of the type

((a, c) -> m (b, c))

and, given the initial state, the constructor will produce a Wire like this:

mkCarry :: Monad m => ((a, c) -> m (b, c)) -> c -> Wire s e m a b
mkCarry transfun state = mkGenN $ \input -> do
  (output, newstate) <- transfun (input, state)
  return (Right output, mkCarry transfun newstate)

only introducing WCarry type instead of WGen type into the resulting wire. It is easy to reformulate runWires in terms of mkCarry.

Then, fmap instance would look something like this:

fmap f (WCarry g st) = WCarry g (fmap f st)

It will change the "hidden inside" state object, and we'll be able to use krSwitch function meaningfully on this sort of Wires, to tweak their internal state without losing the previous value.

Does this make sense? If what I am trying to do is possible in a simpler way, please advice! If what I am talking about makes sense, how could I go about it? Is is possible to locally extend the data Wire definition with WCarry, and extend add the interesting Class instances with corresponding definitions? Any other advice?

Thanks.

Community
  • 1
  • 1
crosser
  • 649
  • 3
  • 17

1 Answers1

2

I was using Netwire and I encountered the exact same problem, so I think it will be useful to answer this. I agree that using (safe) Events is the right way to go. However I don't like adding the WCarry, it doesn't seem very intuitive.

You were actually extremely close to the answer. The key to make addWire relies in that you don't want to 'modify' the old wire. What you want is create a new wire with the given subwire's output added, so this may be what you were looking for:

addWire w ws = fmap (uncurry (:)) (w &&& ws)

This wire feeds both wires and then joins the outputs. Hope it helps!

MartínV
  • 116
  • 4
  • It works for combining outputs of the existing set and a new wire. But on the next step I will obviously want to _remove_ the wires from the set (using e.g. a map for representation). I can remove unwanted outputs the same way as you suggest, but won't that lead to memory leak? – crosser Dec 16 '15 at 21:34
  • Commenting on own comment, perhaps using a lazy map would alleviate the memory leak problem? Would it? – crosser Dec 17 '15 at 07:28
  • 1
    You can make `w &&& ws` respond to an event that switches back to `ws` when you want to remove `w`. – Mokosha Dec 31 '15 at 07:40