EDIT
Here's the F # solution:
type DU = | A | B type State = { AValue : int; BValue : int } let solution2 (aObservable:IObservable<_>, bObservable:IObservable<_>) = let union = aObservable.Select(fun _ -> A).Merge(bObservable.Select(fun _ -> B)) let result = union.Scan({AValue = 0; BValue = 1}, fun state du -> match du with | A -> { state with AValue = state.AValue + state.BValue } | B -> { state with BValue = state.AValue } ) result
F # is actually a great language for this, thanks to its built-in discriminated associations and records. Here is an answer written in C # with the usual Discriminatory Union; my f # is pretty rusty.
The trick is to turn your two observables into one observable using a discriminated union. Thus, basically the union of a and b into one observable from the delimited union:
a : *---*---*---** b : -*-*--*---*--- du: ab-ba-bab-aa
Once this is done, you can respond if the item is a button press of “A” or “B”.
Just for confirmation, I assume that there is no way to explicitly set the value embedded in ButtonA / ButtonB. If there is, then these changes should be modeled as observable, and work in a discriminatory union.
var a = new Subject<Unit>(); var b = new Subject<Unit>(); var observable = a.DiscriminatedUnion(b) .Scan(new State(0, 1), (state, du) => du.Unify( _ => new State(state.A + state.B, state.B), _ => new State(state.A, state.A) ) ); observable.Subscribe(state => Console.WriteLine($"a = {state.A}, b = {state.B}")); a.OnNext(Unit.Default); a.OnNext(Unit.Default); a.OnNext(Unit.Default); a.OnNext(Unit.Default); b.OnNext(Unit.Default); a.OnNext(Unit.Default); a.OnNext(Unit.Default); a.OnNext(Unit.Default); a.OnNext(Unit.Default); b.OnNext(Unit.Default);
Here are the classes this relies on C # for. Most of this translates easily to F # built-in types.
public class State /*easily replaced with an F# record */ { public State(int a, int b) { A = a; B = b; } public int A { get; } public int B { get; } } public static class DiscriminatedUnionExtensions { public static IObservable<DiscriminatedUnionClass<T1, T2>> DiscriminatedUnion<T1, T2>(this IObservable<T1> a, IObservable<T2> b) { return Observable.Merge( a.Select(t1 => DiscriminatedUnionClass<T1, T2>.Create(t1)), b.Select(t2 => DiscriminatedUnionClass<T1, T2>.Create(t2)) ); } public static IObservable<TResult> Unify<T1, T2, TResult>(this IObservable<DiscriminatedUnionClass<T1, T2>> source, Func<T1, TResult> f1, Func<T2, TResult> f2) { return source.Select(union => Unify(union, f1, f2)); } public static TResult Unify<T1, T2, TResult>(this DiscriminatedUnionClass<T1, T2> union, Func<T1, TResult> f1, Func<T2, TResult> f2) { return union.Item == 1 ? f1(union.Item1) : f2(union.Item2) ; } } public class DiscriminatedUnionClass<T1, T2> { private readonly T1 _t1; private readonly T2 _t2; private readonly int _item; private DiscriminatedUnionClass(T1 t1, T2 t2, int item) { _t1 = t1; _t2 = t2; _item = item; } public int Item { get { return _item; } } public T1 Item1 { get { return _t1; } } public T2 Item2 { get { return _t2; } } public static DiscriminatedUnionClass<T1, T2> Create(T1 t1) { return new DiscriminatedUnionClass<T1, T2>(t1, default(T2), 1); } public static DiscriminatedUnionClass<T1, T2> Create(T2 t2) { return new DiscriminatedUnionClass<T1, T2>(default(T1), t2, 2); } }