Heyho, I've been playing around with implementing reactivex (or rather
expanding an existing lib to more fully implement reactivex), which is a lib to
implement reactive programming, aka observables, subjects etc. . This allows
you to make values depend on one another via a push instead of a pull
mechanism. There I stumbled into a problem.
Strictly speaking, the concepts of reactive x create a graph of 3 types of
nodes:
* "Source" nodes that are the start for a value that gets pushed through the
graph/chain of nodes. These are Subjects or Observables
* "Intermediate/Operator" nodes which get attached to "Source" nodes. They
receive values from the SourceNode and transform it in some case (think the
`map` operator on lists in functional program) or introduce conditions on it
(think the `filter` or `tap` operators on lists in functional programming).
* "Consumer/Subscriber" nodes which can subscribe to any of the other 2 nodes
and receive their value and do some final operation with it.
So more conceptually speaking I want to implement a graph that will not contain
cycles, but can strictly speaking in totality have an arbitrary amount of
generic types.
That naturally runs into a problem with the type system, because the
implementation I can think of would roughly look like this:
type Subject[T] = ref object # Source node
observer: Observer[T]
type Observable[T] = ref object # Source node
observer: Observer[T]
value: T
type ParentKind = enum
pkObservable, pkSubject, pkOperator
type Operator[T, S] = ref object # Intermediate/Operator node
case kind: ParentKind:
of pkObservable:
obsParent: Observable[T]
of pkSubject:
subjParent: Subject[T]
of pkOperator:
opParent: Operator[R, T] # <-- Wait, so `Operator` needs the third
parameter R, but now this might be Q, R, T, S if your parent operator now has
Q, R, T... etc.
Run
The problem is the second you want to chain nodes (aka your parent is another
Operator-Node) then you also need the types of the parent. So suddenly instead
of generic types `T` and `S`, you also need `R`. But then you also may have a
parent-node with that has a parent, node. In _that_ scenario your OperatorNode
type suddenly needs also `Q`, and so on and so forth.
Is there some kind of way to solve this in a fashion that doesn't create
limitations?
If RXJS is anything to go by, then this seems like it might be impossible to
solve in an unlimited fashion and I should just define a dozen or so `Operator`
types with various amounts of generic types. Because _they_ apparently gave up
looking at their type-declarations for their `pipe` function
pipe(): Observable<T>;
pipe<A>(op1: OperatorFunction<T, A>): Observable<A>;
pipe<A, B>(op1: OperatorFunction<T, A>, op2: OperatorFunction<A, B>):
Observable<B>;
pipe<A, B, C>(op1: OperatorFunction<T, A>, op2: OperatorFunction<A, B>,
op3: OperatorFunction<B, C>): Observable<C>;
pipe<A, B, C, D>(
op1: OperatorFunction<T, A>,
op2: OperatorFunction<A, B>,
op3: OperatorFunction<B, C>,
op4: OperatorFunction<C, D>
): Observable<D>;
pipe<A, B, C, D, E>(
op1: OperatorFunction<T, A>,
op2: OperatorFunction<A, B>,
op3: OperatorFunction<B, C>,
op4: OperatorFunction<C, D>,
op5: OperatorFunction<D, E>
): Observable<E>;
.... they play this game up to I
Run