3

I'm trying to write a framework for real-time interactive graphics in Haskell. I've been trying to get a handle on things by using Netwire 5, but I don't seem to have a good handle on how things "depend" on one another. For example, the following code should produce val for two seconds before switching to (val + 1), and then continuing on indefinitely.

someWire :: Num a => a -> Wire s e m a a
someWire x = (-->) ((pure x) &&& (periodic 2) >>> until) (someWire (x + 1))

However, this creates some sort of memory leak where my program stalls and just keeps allocating memory until something on my system crashes. Alternatively, this definition

someWire :: Num a => a -> Wire s e m a a
someWire x = (-->) ((pure x) &&& (at 2) >>> until) (someWire (x + 1))

behaves the way that I expect it to: count from val onwards having the value change every two seconds. Can someone please explain this behavior?

Mokosha
  • 2,737
  • 16
  • 33
  • 1
    Your `someWire`s don't typecheck. I assume you meant e.g. `((pure val &&& (periodic 2)) >>> until) --> (someWire (val + 1))`. – duplode May 17 '14 at 18:25
  • Thanks, I fixed it. There's a better full crashing example here: https://gist.github.com/Mokosha/2b65655450981cfded65 – Mokosha May 17 '14 at 18:31

2 Answers2

1

The key insight is that periodic produces an event immediately.

Hence, when we go to produce a value from this wire, we have to evaluate it into the following:

someWire x
(-->) ((pure x) &&& (periodic 2) >>> until) (someWire (x + 1))
(-->) (pure (x, Event _) >>> until) (someWire (x + 1))
(-->) *inhibition* (someWire (x + 1))
someWire (x + 1)

Since this isn't tail recursive, the garbage collector isn't allowed to clean up the previous instances that are allocated for the wire and we run out of memory (instead of getting an infinite loop).

Mokosha
  • 2,737
  • 16
  • 33
0

Let's look at the working version:

import Control.Wire hiding (until)
import qualified Control.Wire (until) as W

someWire :: Num a => a -> Wire s e m a a
someWire x = (-->) (pure x &&& at 2 >>> W.until) (someWire (x + 1))

-- To test in GHCi: testWire clockSession_ $ someWire 3 

Wires are Arrows, and so it will be easier to understand what is going on if we can follow what the arrow combinators are doing.

(&&&) :: Arrow a => a b c -> a b c' -> a b (c, c')

(&&&) builds an arrow which forks its input (which, in our case, can be thought of as the heartbeat driving the events) into a pair, applying one arrow to each component. Here, we fork with an arrow which always produces x and an arrow which gives back an event which is to happen in two seconds.

(>>>) :: Category cat => cat a b -> cat b c -> cat a c

(>>>) is just composition of arrows (Arrow is a subclass of Category), written in first-then-second order. So we are sending the output of pure x &&& at 2 to W.until.

W.until :: Monoid e => Wire s e m (a, Event b) a

Composing with W.until converts an arrow which produces a value paired with an event (such as pure x &&& at 2) into an arrow which produces a value until the event happens. Once that happens, we switch to the other wire using (-->).

Now it should be easier to see why you can't use periodic instead of at. at occurs at just a single instant, while periodic keeps on happening, and so W.until never terminates. (If you dig through the sources, you will find that W.until does something akin to a fold over event occurrences, which matches the intuitive explanation.)

duplode
  • 33,731
  • 7
  • 79
  • 150
  • Hrm, this still doesn't make sense to me. If I replace `periodic 2` with `periodic 2 >>> (delay NoEvent)` then everything behaves as I would expect it to (it switches every other wire step). The "folding" that `W.until` does as described by the docs is solely to consume the value of the event if it occured. I believe the memory consumption has something to do with the space-time dependencies of wires that I'm not understanding... – Mokosha May 17 '14 at 20:02