[racket] Re-inventing dataflow languages

From: John Clements (clements at brinckerhoff.org)
Date: Thu Oct 11 19:26:58 EDT 2012

As part of the rsound package, I find myself reinventing data flow languages; or, at least, the parts that I need. I've come to a design question that I don't know how to answer, and I'm hoping that those with more experience can make suggestions.

First: I'm re-inventing dataflow languages because:
- the natural way to specify audio signals is as dataflow networks (e.g., I have a low-frequency oscillator controlling the frequency of this other oscillator…), and
- FrTime is sadly not fast enough to use, here.

Also, I'm fine with dataflow, here, I don't need full FRP.

The other connection here is to World-style programming; in a big-bang, you specify an initial world and then a transition function (or functions), that map one world to the next. I can't use this framework as-is because of parallel composition issues; specifically, if I have the two oscillators I describe above, then the natural way to represent their state would be as a structure containing the state of each individual component. Allocating a new one of these 44K times per second uses up a lot of memory really quickly. So, I've developed my own framework, that uses mutation but hides it from the user.

Using my "network" syntax, I might specify the above as:

(network ()
        [lfo (sine-wave 2)]
        [out (sine-wave (+ 200 (* 10 lfo)))])

This creates a sine wave at 2 Hz, controlling another sine wave whose frequency varies between 210 and 190 Hz twice per second.

Everything fine so far.

In order to make this work, we need signals that refer to old values of themselves, as in the world model. To allow this, I have a "prev" primitive that allows you to refer to the prior value of some signal node. So, for instance, here's a simple counter that goes up one each tick:

(define simple-ctr
 (network ()
          [out (add1 (prev out)) #:init 0]))

So, this counter rises by one on each tick. Note that we need to specify an initial value, as with big-bang.


What should the first value of this signal be? Should it be zero, or should it be one? Put differently: does this node's updater get called on the first tick, producing 1, or should we just use the initial value, zero?

Zero is the one I really want, but then I have a problem: how do I distinguish between clauses, like this one, that should not be evaluated the first time through, from others, something like this:

[sum (+ sig-a sig-b)]

… that *should* be evaluated the first time through?

My current solution is to treat them uniformly, and to evaluate all of them each time. This means that I wind up with dreadful hacks like this one:

[out (add1 (prev out)) #:init -1]

… so that the -1 gets bumped up to a zero on the first output. It turns out that this trick is fragile; if you don't know what the increment will be, you can't accurately "pre-decrement" it.

I can imagine doing something more complicated, but what I really want to ask is this: for those of you with experience in other dataflow languages, how do they solve this? 

Sorry for the long e-mail; I'm hoping that someone can point to a nice solution that exists in another language!


-------------- next part --------------
A non-text attachment was scrubbed...
Name: smime.p7s
Type: application/pkcs7-signature
Size: 4800 bytes
Desc: not available
URL: <http://lists.racket-lang.org/users/archive/attachments/20121011/8ae850a4/attachment.p7s>

Posted on the users mailing list.