You seem to have some wrong impressions of how pipe operator works, so I'll try to clear those up.
Let's make things simpler to type out and say we have functions foo and bar:
let foo (a, b, f) = f a b
let bar a b f = f a b
foo has more or less the same shape as Observable.Zip and takes a tuple as an argument (this is how C# functions are seen in F#), bar is the same but is curried.
This works:
foo (ob1, ob2, fun a b -> a + b)
bar ob1 ob2 (fun a b -> a + b)
This doesn't work:
ob1 |> bar ob2 (fun a b -> a + b)
That's because what pipe operator does, is taking the value on the left, and passing it as the last argument to the function on the right. You'd need bar to be defined like this:
let bar b f a = f a b
That's why the functions in for example the List module are defined in a way that the actual list is passed in as the last argument - that makes pipelining work in a nice way.
This also doesn't work:
ob1 |> foo (ob2, fun a b -> a + b)
Apart from the previous problem, it would also require pipe operator to look inside a tuple and attach a value there, and that's really not how it works. A tuple is a single value in F#. The function that would work for that example would be this one:
let foo (b, f) a = f a b
But clearly that's not what we want.
You can still use Observable.Zip in a pipeline fashion like this:
ob1 |> fun x -> Observable.Zip(x, ob2, Func<_,_,_> (fun a b -> a + b))
Or simply go with the wrapper the other answer suggests.
zipfunction from the module as an answer! - Tomas Petricek