If you do not already know what a monad is, today would be a great day to learn. I have a gentle introduction for OO programmers here:
https://ericlippert.com/2013/02/21/monads-part-one/
Your scenario is a small extension to the "maybe monad", also known as Nullable<T>
in C# and Optional<T>
in other languages.
Let's suppose you have an abstract type to represent the monad:
abstract class Measurement<T> { ... }
and then three subclasses:
final class Unknown<T> : Measurement<T> { ... a singleton ...}
final class Empty<T> : Measurement<T> { ... a singleton ... }
final class Actual<T> : Measurement<T> { ... a wrapper around a T ...}
We need an implementation of Bind:
abstract class Measurement<T>
{
public Measurement<R> Bind(Func<T, Measurement<R>> f)
{
if (this is Unknown<T>) return Unknown<R>.Singleton;
if (this is Empty<T>) return Empty<R>.Singleton;
if (this is Actual<T>) return f(((Actual<T>)this).Value);
throw ...
}
From this you can write this simplified version of Bind:
public Measurement<R> Bind(Func<A, R> f)
{
return this.Bind(a => new Actual<R>(f(a));
}
And now you're done. You have a Measurement<int>
in hand. You want to double it:
Measurement<int> m = whatever;
Measurement<int> doubled = m.Bind(a => a * 2);
Measurement<string> asString = m.Bind(a => a.ToString());
And follow the logic; if m
is Empty<int>
then asString
is Empty<String>
, excellent.
Similarly, if we have
Measurement<int> First()
and
Measurement<double> Second(int i);
then we can combine two measurements:
Measurement<double> d = First().Bind(Second);
and again, if First()
is Empty<int>
then d
is Empty<double>
and so on.
The key step is to get the bind operation correct. Think hard about it.