turion / rhine

Haskell Functional Reactive Programming framework with type-level clocks
http://hackage.haskell.org/package/rhine
121 stars 21 forks source link

Feedback for signal networks and rhines #180

Closed SheetKey closed 2 years ago

SheetKey commented 2 years ago

I'm still learning rhine, but so far feedback seems to be extremely useful. Unfortunately is only works on ClSFs. It would be very useful to have the ability to capture the same idea of internal state within SNs, which would also allow feedback of Rhines. I think this should be possible with the constraint

feedbackSN :: (Monad m, Time (In cl) ~ Time (Out cl)) => c -> SN m cl (a, c) (b, c) -> SN m cl a b

I'm not well versed enough with the internals of rhine and my attempts at writing this haven't gotten far. The Synchronous case seems straightforward but for Sequential and Parallel signal networks it seems they would have to be completed deconstructed into MSFs before being reconstructed in some way as to not lose the type level clock information. Alternatively the time constraint could be removed it the c part of the output was fed into a resampling buffer perhaps.

It would follow for Rhines

feedbackRh :: (Monad m, Time (In cl) ~ Time (Out cl)) => c -> Rhine m cl (a, c) (b, c) -> Rhine m cl a b
feedbackRh c (Rhine {..}) = Rhine (feedbackSN c sn) clock
turion commented 2 years ago

Hi @SheetKey , welcome :wave:

Yes, something like that would in principle be very useful! It's not quite as simple as you propose, though. A signal network potentially consists of many different components that tick at different times. So we cannot always expect that a value c is output by the network at the same time when a new input is required. This is different for ClSFs, because those are synchronous. For SNs, we need a buffer between the two cs. This concept already exists in form of a ResamplingBuffer, so basically the function you want to write should have this type signature:

  feedback
    :: ( Clock m (In cl),  Clock m (Out cl)
       , Time (In cl) ~ Time cl
       , Time (Out cl) ~ Time cl
       )
    => ResBuf m (Out cl) (In cl) d c
    -> SN     m cl (a, c) (b, d)
    -> SN     m cl  a      b

The ResBuf takes the place of your single c.

And indeed this can be implemented! Again it's not immediately obvious how to do it. It doesn't work as an operation on the SN type as it is defined right now, because it's a nonlocal operation. By that I mean that it wires two potentially far removed ends together. Other combinators like >>>^ only operate in one place and are easier to define. feedback needs to be defined as a new constructor for SN, and then interpreted correctly in the ClockErasure module.

I had a similar idea recently and got stuck halfway. Since you asked for it, I had a look at it again and in fact I got unstuck, ironically, by using the MSF feedback function. So thanks for the inspiration! Have a look at this MR: https://github.com/turion/rhine/pull/181

turion commented 2 years ago

I merged #181, so this is in fact implemented now. Can you play around with it a bit and see whether you can make progress now?

SheetKey commented 2 years ago

Thanks so much! I'll take a look.