[racket] Re-inventing dataflow languages
On Thu, Oct 11, 2012 at 4:26 PM, John Clements
<clements at brinckerhoff.org> wrote:
> 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.
Yes. There are significant opportunities for performance improvement
in FrTime, but for this sort of scenario, I think a "pull" evaluator
will be hard to beat.
> 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.
>
> BUT, HERE COMES THE PROBLEM.
>
> 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:
Can you not just use the presence of the #:init keyword to disable
evaluation for the first step (or the first n steps in general)?
That's probably what I'd do, though I may be missing something that
makes this problematic...
--Greg
> [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!
>
>
> John
>
>
>