[Haskell-cafe] Call for GUI examples - Functional Reactive Programming

Henning Thielemann lemming at henning-thielemann.de
Sat Jul 9 12:58:00 CEST 2011


On Sat, 9 Jul 2011, Heinrich Apfelmus wrote:

>> Oh, I am addressed explicitly, thanks! Yes, GUI for 'streamed' would be 
>> nice, too.
>> In the meantime I switched from an approach with lazy lists to one with 
>> arrow-like stream processors. This way I could resolve all issues with 
>> wrong timing and inappropriate waiting, but now code looks more low-level. 
>> There are some examples lying around, that I even not started to implement, 
>> because I expect that implementing them in the current low-level way will 
>> yield nasty bugs. Thus I am highly interested in more sophisticated MIDI 
>> stream editor combinators. I expect that inspiration from other FRP 
>> frameworks would help me.
>
> Of course, my intention was that you throw away your stream processors and 
> use reactive-banana for everything. ;D  I have made sure that it can be used 
> both for real-time and for offline computations.
>
> Could you expand a little on your arrow-like stream processors? What do the 
> arrows look like,
>
>    data SF a b = SF (a -> (b, SF a b))
>
> ?

My stream processors are not Arrows, because 'first' cannot be 
implemented. However, 'arr' and '.' can be implemented.

As far as I understand, the SF banana can only produce an output if an 
input event arrives. But my stream processor can set alarms in order to 
produce a clock signal. This way they can generate output without any 
input.

Currently my stream processors are defined using an explicit state, that 
is hidden using an existential quantifier, but of course I would prefer a 
Haskell 98 solution.

For example, an arpeggiator stream processor consists of two 
functions:
  1. Receive key up/down events and keep track of the currently pressed keys.
  2. Receive a MIDI controller that controls the tempo of the arpeggiator.
     The stream processor sets the alarm according the current tempo
     and at every alarm event it sends a single key
     chosen from the set of the currently pressed keys.

This example shows, that a stream processor cannot support Arrow.first, 
that is extending (arrow a b) to (arrow (a,c) (b,c)), since for an alarm 
event, we have no input of type c that could be passed through.

Currently I have build the two tasks into one stream processor. I would 
like to split these into two processors. This looks difficult to me, since 
the first stream processor (for management of pressed keys) must provide a 
state (the set of pressed keys) whenever the second stream processor (for 
clock generation) needs it.

> Which additional primitives did you include, for instance
>
>    switch :: SF in (out, Maybe t) -> (t -> SF in out) -> SF in out
>    delay  :: Time -> SF a b -> SF a b
>
> ?

Since I have no Arrow instance I even have to write my own combinators 
that are counterparts to (&&&) and friends.

> And of course, I am particularly interested in the nasty examples that you 
> came up with. :)

For example I want to write a Guitar simulator: You press a set of keys 
together and the processor converts this into successive tones on a 
guitar.
   Technically I like to do it this way: When a key is pressed, collect all 
key press events in the following 10ms. After this period emit all pressed 
keys according to a certain pattern. When one of the pressed keys is 
released, then send key-press(!) events for all currently pressed keys 
according to another pattern. Repeat this cycle. Manage somehow the keys 
that are pressed after the first key-down-collecting phase and the keys 
that are released during this initial phase. Ignore them or do something 
more sensible about them, but make sure that in the output all key-down 
events are eventually matched with a key-up event and that for the same 
key you never send two successive key-down events or two successive key-up 
events. That is, for the same key, key-up and key-down events must 
alternate. An exception might be if you receive bogus input. But even 
then, the number of key-up and key-down events for one note in the output 
shall match, whenever this is true for the input.

  For lazy lists you can do time related processing by splitting the list 
of events and process the prefix differently from the suffix and then 
concatenate the results. This is easy and declarative, but you have no 
guarantee that the resulting stream processing is causal and that the 
timing is correct. (The second problem arises when merging two event 
lists. Merging implies that when waiting for two events then actually the 
system waits for the later of the events, but we need to wait for the 
first of the two events.)



More information about the Haskell-Cafe mailing list