Reactive monad
IObservable is (also) a monad. This article is an instalment in an article series about monads. While the previous articles showed, in great detail, how to turn various classes into monads, this article mostly serves as a place-holder. The purpose is only to point out that you don't have to create all monads yourself. Sometimes, they come as part of a reusable library. Rx define such libraries, and IObservable forms a monad. Reactive Extensions for .NET define a SelectMany method for IObservable, so if source is an IObservable, you can translate it to IObservable like this: IObservable dest = source.SelectMany(i => Observable.Repeat('x', i)); Since the SelectMany method is, indeed, called SelectMany and has the signature public static IObservable SelectMany( this IObservable source, Func selector) you can also use C#'s query syntax: IObservable dest = from i in source from x in Observable.Repeat('x', i) select x; In both of the above examples, I've explicitly declared the type of dest instead of using the var keyword. There's no practical reason to do this; I only did it to make the type clear to you. Left identity # As I've already written time and again, a few test cases don't prove that any of the monad laws hold, but they can help illustrate what they imply. For example, here's an illustration of the left-identity law, written as a parametrized xUnit.net test: [Theory] [InlineData(1)] [InlineData(2)] [InlineData(3)] public async Task LeftIdentity(int a) { IObservable h(int i) => Observable.Repeat('x', i); IList left = await Observable.Return(a).SelectMany(h).ToList(); IList right = await h(a).ToList(); Assert.Equal(left, right); } Not only does the System.Reactive library define monadic bind in the form of SelectMany, but also return, with the aptly named Observable.Return function. .NET APIs often forget to do so explicitly, which means that I often have to go hunting for it, or guessing what the developers may have called it. Not here; thank you, Rx team. Right identity # In the same spirit, we may write another test to illustrate the right-identity law: [Theory] [InlineData("foo")] [InlineData("bar")] [InlineData("baz")] public async Task RightIdentity(string a) { IObservable f(string s) => s.ToObservable(); IObservable m = f(a); IList left = await m.SelectMany(Observable.Return).ToList(); IList right = await m.ToList(); Assert.Equal(left, right); } In both this and the previous test, you can see that the test has to await the observables in order to verify that the resulting collections are identical. Clearly, if you're instead dealing with infinite streams of data, you can't rely on such a simplifying assumption. For the general case, you must instead turn to other (proof) techniques to convince yourself that the laws hold. That's not my agenda here, so I'll skip that part. Associativity # Finally, we may illustrate the associativity law like this: [Theory] [InlineData("foo")] [InlineData("123")] [InlineData("4t2")] public async Task Associativity(string a) { IObservable f(string s) => s.ToObservable(); IObservable g(char c) { if (byte.TryParse(c.ToString(), out var b)) return Observable.Return(b); else return Observable.Empty(); } IObservable h(byte b) => Observable.Repeat(b % 2 == 0, b); IObservable m = f(a); IList left = await m.SelectMany(g).SelectMany(h).ToList(); IList right = await m.SelectMany(x => g(x).SelectMany(h)).ToList(); Assert.Equal(left, right); } This test composes three observable-producing functions in two different ways, to verify that they produce the same values. The first function, f, simply turns a string into an observable stream. The string "foo" becomes the stream of characters 'f', 'o', 'o', and so on. The next function, g, tries to parse the incoming character as a number. I've chosen byte as the data type, since there's no reason trying to parse a value that can, at best, be one of the digits 0 to 9 into a full 32-bit integer. A byte is already too large. If the character can be parsed, it's published as a byte value; if not, an empty stream of data is returned. For example, the character stream 'f', 'o', 'o' results in three empty streams, whereas the stream 4, t, 2 produces one singleton stream containing the byte 4, followed by an empty stream, followed again by a stream containing the single number 2. The third and final function, h, turns a number into a stream

IObservable
This article is an instalment in an article series about monads. While the previous articles showed, in great detail, how to turn various classes into monads, this article mostly serves as a place-holder. The purpose is only to point out that you don't have to create all monads yourself. Sometimes, they come as part of a reusable library.
Rx define such libraries, and IObservable
forms a monad. Reactive Extensions for .NET define a SelectMany
method for IObservable
, so if source
is an IObservable<int>
, you can translate it to IObservable<char>
like this:
IObservable<char> dest = source.SelectMany(i => Observable.Repeat('x', i));
Since the SelectMany
method is, indeed, called SelectMany
and has the signature
public static IObservable<TResult> SelectMany<TSource, TResult>( this IObservable<TSource> source, Func<TSource, IObservable<TResult>> selector)
you can also use C#'s query syntax:
IObservable<char> dest = from i in source from x in Observable.Repeat('x', i) select x;
In both of the above examples, I've explicitly declared the type of dest
instead of using the var
keyword. There's no practical reason to do this; I only did it to make the type clear to you.
Left identity #
As I've already written time and again, a few test cases don't prove that any of the monad laws hold, but they can help illustrate what they imply. For example, here's an illustration of the left-identity law, written as a parametrized xUnit.net test:
[Theory] [InlineData(1)] [InlineData(2)] [InlineData(3)] public async Task LeftIdentity(int a) { IObservable<char> h(int i) => Observable.Repeat('x', i); IList<char> left = await Observable.Return(a).SelectMany(h).ToList(); IList<char> right = await h(a).ToList(); Assert.Equal(left, right); }
Not only does the System.Reactive library define monadic bind in the form of SelectMany
, but also return, with the aptly named Observable.Return
function. .NET APIs often forget to do so explicitly, which means that I often have to go hunting for it, or guessing what the developers may have called it. Not here; thank you, Rx team.
Right identity #
In the same spirit, we may write another test to illustrate the right-identity law:
[Theory] [InlineData("foo")] [InlineData("bar")] [InlineData("baz")] public async Task RightIdentity(string a) { IObservable<char> f(string s) => s.ToObservable(); IObservable<char> m = f(a); IList<char> left = await m.SelectMany(Observable.Return).ToList(); IList<char> right = await m.ToList(); Assert.Equal(left, right); }
In both this and the previous test, you can see that the test has to await
the observables in order to verify that the resulting collections are identical. Clearly, if you're instead dealing with infinite streams of data, you can't rely on such a simplifying assumption. For the general case, you must instead turn to other (proof) techniques to convince yourself that the laws hold. That's not my agenda here, so I'll skip that part.
Associativity #
Finally, we may illustrate the associativity law like this:
[Theory] [InlineData("foo")] [InlineData("123")] [InlineData("4t2")] public async Task Associativity(string a) { IObservable<char> f(string s) => s.ToObservable(); IObservable<byte> g(char c) { if (byte.TryParse(c.ToString(), out var b)) return Observable.Return(b); else return Observable.Empty<byte>(); } IObservable<bool> h(byte b) => Observable.Repeat(b % 2 == 0, b); IObservable<char> m = f(a); IList<bool> left = await m.SelectMany(g).SelectMany(h).ToList(); IList<bool> right = await m.SelectMany(x => g(x).SelectMany(h)).ToList(); Assert.Equal(left, right); }
This test composes three observable-producing functions in two different ways, to verify that they produce the same values.
The first function, f
, simply turns a string into an observable stream. The string "foo"
becomes the stream of characters 'f'
, 'o'
, 'o'
, and so on.
The next function, g
, tries to parse the incoming character as a number. I've chosen byte
as the data type, since there's no reason trying to parse a value that can, at best, be one of the digits 0
to 9
into a full 32-bit integer. A byte
is already too large. If the character can be parsed, it's published as a byte value; if not, an empty stream of data is returned. For example, the character stream 'f'
, 'o'
, 'o'
results in three empty streams, whereas the stream 4
, t
, 2
produces one singleton stream containing the byte
4
, followed by an empty stream, followed again by a stream containing the single number 2
.
The third and final function, h
, turns a number into a stream of Boolean values; true
if the number is even, and false
if it's odd. The number of values is equal to the number itself. Thus, when composed together, "123"
becomes the stream false
, true
, true
, false
, false
, false
. This is true for both the left
and the right
list, even though they're results of two different compositions.
Conclusion #
The point of this article is mostly that monads are commonplace. While you may discover them in your own code, they may also come in a reusable library. If you already know C# LINQ based off IEnumerable
, parts of Rx will be easy for you to learn. After all, it's the same abstraction, and familiar abstractions make code readable.
Next: The IO monad.
This blog is totally free, but if you like it, please consider supporting it.