As I understand, in FRP (Functional Reactive Programming), we model the system as a component which receives some input signals and generates some output signals:
,------------.
--- input1$ --> | | -- output1$ -->
| System | -- output2$ -->
--- input2$ --> | | -- output3$ -->
`------------'
In this way, if we have multiple subsystems, we can plump them together as long as we can provide operators that can pipe inputs and outputs.
Now, I'm building an app, which processes video frames asynchronously. The actual processing logic is abstracted and can be provided as an argument. In non-FRP way of thinking, I can construct the app as
new App(async (frame) => {
return await processFrame(frame)
})
The App is responsible for establishing communication with underlying video pipeline and repeatedly get video frames and then pass that frame to the given callback, and once the callback resolves,App sends back the processed frame.
Now I want to model the App in a FRP way so I can flexibly design the frame processing.
const processedFrameSubject = new Subject()
const { frame$ } = createApp(processedFrameSubject)
frame$.pipe(
map(toRGB),
mergeMap(processRGBFrame),
map(toYUV)
).subscribe(processedFrameSubject)
The benefit is that it enables the consumer of createApp to define the processing pipeline declaratively.
However, in createApp, given a processedFrame, I need to reason about which frame it is related to. Since frame$ and processedFrameSubject is now separated, it's really hard for createApp to reason about which frame a processedFrame is related to, which was quite easy in non-FRP implementation because the frame and processedFrame were in same closure.
In functional reactive programming, you would avoid using side effects as much as possible, this means avoiding
.subscribe(,tap(() => subject.next()), etc. With FRP, your state is declared on how it works and how it's wired up, but it doesn't execute until someone needs it and performs the side effect.So I think that the following API would still be considered FRP:
Something worth noting is that
createAppis returning anew Observable. Insidenew Observable(we can escape from FRP because it's the only way we can integrate with external parties, and all the side effects we have written won't be called until someone.subscribe()s to the observable.This API is simple and would still be FRP, but it has one limitation: the
processFramecallback can only process frames independently from others.If you need an API that supports that, then you need to expose the
frames$, but again, this is a project function forcreateApp:I'm guessing here
registerVideoFrameHandlerwill call the function one-by-one without overlap? If there's overlap then you'd need to track the frame number in some way, if the SDK doesn't give you any option, then try something like: