{"id":110121,"date":"2024-08-12T07:00:00","date_gmt":"2024-08-12T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=110121"},"modified":"2024-08-12T08:40:50","modified_gmt":"2024-08-12T15:40:50","slug":"20240812-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240812-00\/?p=110121","title":{"rendered":"Embracing the power of the empty set in API design (and applying this principle to selectors and filters)"},"content":{"rendered":"<p>In mathematics, the empty set is a set with no members. This sounds totally useless, but it&#8217;s actually quite powerful.<\/p>\n<p>Suppose you have a Widget object and a method <code>GetChildren()<\/code> that returns the child widgets. What should you do if the Widget has no children?<\/p>\n<p>Sometimes, teams try to be clever and say, &#8220;Oh, if there are no children, then I will just return a null pointer instead of a list of child elements.&#8221; For example, <code>IShell\u00adFolder::<wbr \/>Enum\u00adObjects<\/code> has the rule that <a title=\"If I don't have any items, what error should my IFolderView::Items method return?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20231222-00\/?p=109178\"> if it returns <code>S_FALSE<\/code>, then it is allowed to return a null pointer instead of an enumerator<\/a>.<\/p>\n<p>As a result, this code is subtly wrong:<\/p>\n<pre>ComPtr&lt;IEnumIDList&gt; e;\r\nif (<span style=\"border: solid 1px currentcolor;\">SUCCEEDED(shellFolder-&gt;EnumObjects(hwnd, SHCONTF_FOLDERS, &amp;e)<\/span>) {\r\n    for (CComHeapPtr&lt;IDLIST_RELATIVE&gt; child;\r\n         e-&gt;Next(1, &amp;child, nullptr) == S_OK;\r\n         child.Free()) {\r\n        \/\/ process the child folder\r\n    }\r\n}\r\n<\/pre>\n<p>The above code is at risk of crashing if there are no child folders: The <code>IShellFolder<\/code> implementation may chose to use the special case carve-out and return <code>S_FALSE<\/code> from the <code>Enum\u00adObjects<\/code> method, and set <code>e = nullptr<\/code>. If that happens, then the enumeration loop crashes on a null pointer.<\/p>\n<p>The correct code would have to check for this corner case and skip the enumeration loop. One way is to check for <code>S_FALSE<\/code> explicitly:<\/p>\n<pre>ComPtr&lt;IEnumIDList&gt; e;\r\n<span style=\"border: solid 1px currentcolor; border-bottom: none;\">HRESULT hr = shellFolder-&gt;EnumObjects(hwnd, SHCONTF_FOLDERS, &amp;e);<\/span>\r\n<span style=\"border: solid 1px currentcolor; border-top: none;\">if (SUCCEEDED(hr) &amp;&amp; (hr != S_FALSE)) {                          <\/span>\r\n    for (CComHeapPtr&lt;IDLIST_RELATIVE&gt; child;\r\n         e-&gt;Next(1, &amp;child, nullptr) == S_OK;\r\n         child.Free()) {\r\n        \/\/ process the child folder\r\n    }\r\n}\r\n<\/pre>\n<p>Another is to check the enumerator object for null.<\/p>\n<pre>ComPtr&lt;IEnumIDList&gt; e;\r\nif (SUCCEEDED(shellFolder-&gt;EnumObjects(hwnd, SHCONTF_FOLDERS, &amp;e) <span style=\"border: solid 1px currentcolor;\">&amp;&amp; e<\/span>) {\r\n    for (CComHeapPtr&lt;IDLIST_RELATIVE&gt; child;\r\n         e-&gt;Next(1, &amp;child, nullptr) == S_OK;\r\n         child.Free()) {\r\n        \/\/ process the child folder\r\n    }\r\n}\r\n<\/pre>\n<p>Another is to check the return value for <code>S_OK<\/code> exactly:<\/p>\n<pre>ComPtr&lt;IEnumIDList&gt; e;\r\nif (shellFolder-&gt;EnumObjects(hwnd, SHCONTF_FOLDERS, &amp;e) <span style=\"border: solid 1px currentcolor;\">== S_OK<\/span>) {\r\n    for (CComHeapPtr&lt;IDLIST_RELATIVE&gt; child;\r\n         e-&gt;Next(1, &amp;child, nullptr) == S_OK;\r\n         child.Free()) {\r\n        \/\/ process the child folder\r\n    }\r\n}\r\n<\/pre>\n<p>Regardless of how you deal with the issue, it doesn&#8217;t hide the fact that this special case is an extra bit of hassle, and more importantly, it&#8217;s the sort of hassle that is <i>easily overlooked<\/i> when writing code.<\/p>\n<p>The obvious-looking code is wrong. The weird-looking code is correct. This is the opposite of what we want: We want the natural code to be the correct one.<\/p>\n<p>The general rule for methods that return collections is therefore that if there are no items to return, then the methods should return empty collections, rather than null references. The empty set is a perfectly valid set, and existing algorithms operate properly on an empty set. For example, you iterate over an empty collection the same way as a nonempty collection.<\/p>\n<p>The empty set also follows mathematical rules when applied to union and intersection. If you have collection of sets that happens to be empty, then <a href=\"https:\/\/math.stackexchange.com\/questions\/370188\/empty-intersection-and-empty-union\"> mathematically<\/a>, the union of the elements of the empty set is another empty set. And the intersection of the elements of the empty set is the entire universe.<\/p>\n<p>When applied to functions, this means that if a method takes a set of criteria with the requirement that elements must match at least one of the criteria (a &#8220;selector&#8221; pattern), then an empty set selects no elements. On the other hand, if the requirement is that elements must match <i>all<\/i> of the criteria (a &#8220;filter&#8221; pattern), then an empty set selects all elements.\u00b9<\/p>\n<p>For example, suppose we have a method that finds all widgets of the specified colors:<\/p>\n<pre>IVectorView&lt;Widget&gt;\r\n    FindWidgetsOfColors(IIterable&lt;Color&gt; colors);\r\n<\/pre>\n<p>What should happen if you pass an empty list of colors?<\/p>\n<p>Well, since you didn&#8217;t specify any colors, then are no matching widgets, so mathematically speaking, this should return an empty vector. You might also choose to declare that choosing widgets by color must provide at least one color, so calling <code>Find\u00adWidgets\u00adOf\u00adColors<\/code> with an empty set of colors is allowed to fail with <code>E_<wbr \/>INVALID\u00adARG<\/code>. But if you&#8217;re going to return something, you had better return the empty set. Do not design the function so that passing an empty list of colors returns all the widgets.<\/p>\n<p>Conversely, suppose you have a method that finds widgets subject to filter criteria:<\/p>\n<pre>enum WidgetFilter\r\n{\r\n    Active,\r\n    Connected,\r\n};\r\n\r\nIVectorView&lt;Widget&gt;\r\n    FindWidgetsWithFilter(IIterable&lt;WidgetFilter&gt; filters);\r\n<\/pre>\n<p>What should happen if you pass an empty list of filters?<\/p>\n<p>Mathematically, an empty list of filters is an unfiltered query, and you should return all the widgets. Again, you might decide that a filtered query must provide at least one filter, but if you choose to allow an empty list of filters, it had better return all the widgets.<\/p>\n<p>These powers of the empty set mean that many operations have their natural meanings. For example, if you have a filter set and decide that you want to allow inactive widgets, you can remove <code>Widget\u00adFilter.<wbr \/>Active<\/code> from your filters. If <code>Find\u00adWidgets\u00adWith\u00adFilter<\/code> treated an empty filter list as meaning &#8220;Return no widgets at all&#8221;, then you&#8217;d need a special case for the possibility that the <code>Active<\/code> filter was the last filter.<\/p>\n<p><b>Bonus chatter<\/b>: Most programming languages understand the power of the empty set. For example, C++ <code>std::all_of<\/code> and JavaScript <code>Array.<wbr \/>prototype.<wbr \/>every<\/code> return <code>true<\/code> when given an empty collection, whereas C++ <code>std::any_of<\/code> and JavaScript <code>Array.<wbr \/>prototype.<wbr \/>some<\/code> return <code>false<\/code> when given an empty collection.<\/p>\n<p>\u00b9 This naming distinction between &#8220;selector&#8221; patterns (where each selector adds to the list of results) and &#8220;filter&#8221; patterns (where each filter shrinks the list of results) is my own and is not officially part of the Windows API design patterns.<\/p>\n<p>My proposal that was accepted is that filters should be named after what they allow to pass through, not by what they remove. For example, passing the <code>Widget\u00adFilter.Active<\/code> filter means that you want the active widgets. It doesn&#8217;t mean that you want to filter out the active widgets (and return only the inactive ones). If you want a filter to remove active widgets, then the filter should be named something like <code>Widget\u00adFilter.Inactive<\/code>. For adjectives that don&#8217;t have an obvious antonym, you can use the prefix <code>Not<\/code>, as in <code>Widget\u00adFilter.Not\u00adBlinking<\/code>.<\/p>\n<p><b>Bonus chatter<\/b>: We saw another application of this principle some time ago when we looked at <a title=\"Awaiting a set of handles with a timeout, part 7: Just doing it one at a time\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240508-00\/?p=109732\"> what a timeout of zero should mean<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>You got plenty of nothing.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-110121","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>You got plenty of nothing.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/110121","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=110121"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/110121\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=110121"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=110121"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=110121"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}