The curious case of Any() and All()

Last week, I posted a puzzle on twitter. Fill in the GetSpecialSequence() method such that Any() returns false, and All() returns true. Here’s the code from the puzzle:

using static System.Console;

using System.Collections.Generic;

using System.Linq;

namespace AnyAndAll

{

public class Program

{

public static void Main(string[] args)

{

var sequence = GetSpecialSequence();

// Prints "True"

WriteLine(sequence.All(b => b == true));

// Prints "False"

WriteLine(sequence.Any(b => b == true));

}

private static IEnumerable<bool> GetSpecialSequence()

{

// Puzzle: What should this implementation be?

return null;

}

}

}

The answer is simply to return the empty sequence:

private static IEnumerable<bool> GetSpecialSequence()

{

return Enumerable.Empty<bool>();

}

Let’s discuss why this is both important, and a good thing.

Several developers on twitter did get the right answer to the puzzle. Many of those that did get the right answer also responded by saying how they disliked this behavior, and why it was confusing.

The behavior is correct if you understand set theory from mathematics:

- Any() returns true when at least one item in the set satisfies the condition. It follows that the empty set always return false.
- All() returns true when all of the items in the set satisfy the condition. It follows that the empty set always return true.

Let’s drill into that second statement: All 0 elements in the empty set satisfy the condition. Stated another way, there are no elements in the set that do not satisfy the condition.

Let’s discuss how this bit of set theory can make our lives as developers easier.

If you know that a set of data always satisfies a condition, or is empty, we can often simplify the conditional branching in our code. In the C# spec meetings, it came up as a proposal for simplifying the rules around Definite Assignment. This code does not generate an error because it uses a variable before it has been assigned:

int x;

if (false)

WriteLine(x);

There are two rules in the existing spec that define this behavior. Paraphrasing, one says that a variable ‘v’ is definitely assigned at the beginning of any unreachable statement. The other (more complicated rule) says that v is definitely assigned if and only if v is definitely assigned on all control flow transfers that target the beginning of the statement.

Read it carefully with set theory in mind, and you see that the second rule makes the first rule redundant: if a variable is definitely assigned on all (none) of the control paths that target the statement, that variable is definitely assigned. We could remove similar redundancies in this section of the spec by finding other rules that effectively created an empty set of possible paths, and clearing them out.

Consider how this might apply in your work. If you can find logic in your code that finds data which satisfies a particular criteria and then branches based on that data. Can you simplify that code if the empty set code “just works” the same as other paths? Can you find other locations in code where “for all items” can work for the interesting case of “for all none of these items”?

I hope you enjoyed the puzzle. If you want another brain teaser, work out why this solution satisfies the original puzzle.

Hat tip to Neal Gafter for doing the work to simplify (and correct) the Definite Assignment section of the C# spec.

Hat tip to Evan Hauck for the most interesting solution to the puzzle.

Tags:
C#

Created: 8/2/2016 3:52:00 PM

I create content for .NET Core. My work appears in the .NET Core documentation site. I'm primarily responsible for the section that will help you learn C#.

All of these projects are Open Source (using the Creative Commons license for content, and the MIT license for code). If you would like to contribute, visit our GitHub Repository. Or, if you have questions, comments, or ideas for improvement, please create an issue for us.