Empty Distribution

In this lesson, we will implement the empty distribution.

Previously, we mentioned that we will be needing the empty distribution.

Implementing the Empty Distribution

In this lesson, we will be implementing the Empty Distribution.

public sealed class Empty<T> : IDiscreteDistribution<T>
{
  public static readonly Empty<T> Distribution = new Empty<T>();
  private Empty() { }
  public T Sample() => throw new Exception(“Cannot sample from empty distribution”);
  public IEnumerable<T> Support() => Enumerable.Empty<T>();
  public int Weight(T t) => 0;
}

Now that we have this, we can fix up our other distributions to use it. The WeightedInteger factory becomes:

public static IDiscreteDistribution<int> Distribution(IEnumerable<int> weights)
{
  List<int> w = weights.ToList();

  if (w.Any(x => x < 0))
    throw new ArgumentException();

  if (!w.Any(x => x > 0))
    return Empty<int>.Distribution;

[...]

And the Bernoulli factory becomes:

public static IDiscreteDistribution<int> Distribution(int zero, int one)
{
  if (zero < 0 || one < 0)
    throw new ArgumentException();

  if (zero == 0 && one == 0)
    return Empty<int>.Distribution;

[...]

And the StandardDiscreteUniform factory becomes:

public static IDiscreteDistribution<int> Distribution(int min, int max)
{
  if (min > max)
    return Empty<int>.Distribution;

[...]

And the Projected factory becomes:

public static IDiscreteDistribution<R> Distribution(IDiscreteDistribution<A> underlying, Func<A, R> projection)
{
  var result = new Projected<A, R>(underlying, projection);

  if (result.weights.Count == 0)
    return Empty<R>.Distribution;

[...]
}

And one more thing needs to change. Our computation in SelectMany assumed that none of the total weights are zero. Easily fixed:

int lcm = prior.Support()
  .Select(a => likelihood(a).TotalWeight())
  .Where(x => x != 0)
  .LCM();

We also have a division by total weight; don’t we have to worry about dividing by zero? Nope. Remember, the empty distribution’s support is the empty sequence, so when we then say:

var w = from a in prior.Support()
        let pb = likelihood(a)
        from b in pb.Support()
        group prior.Weight(a) * pb.Weight(b) *
          lcm / pb.TotalWeight()
        by projection(a, b);

If prior.Support() is empty, then the whole query is empty and so the division is never executed. If prior.Support() is not empty but one of the pb.Support() is empty then there is nothing from which to compute a group key. We never actually divide by total weight, and so there is no division by zero error to avoid.

That was relatively painless, but it is probably still very unclear why we’d ever need an empty distribution. It seems to be like a little bomb hiding in the program, waiting for someone to sample it. Have we just committed another “null reference” design fault? In a few lessons, we’ll see what benefits justify the costs.

Implementation

Here is the complete implementation:

We have added the empty distribution to implementation of the lesson Discrete Probability Distribution Type as a Monad

The output remains the same, however, some parts of the code have changed.

Get hands-on with 1200+ tech skills courses.