Re: [heycam/webidl] Add a section on overloads vs. union/optional (#426)

domenic commented on this pull request.

Another round, this time with some suggestions toward incorporating @annevk's worries.

> @@ -3341,6 +3341,219 @@ be the same.
 </div>
 
 
+<h5 id="idl-overloading-vs-union">Overloading vs. union types</h5>
+
+<i>This section is informative.</i>
+
+<div class="non-normative">
+
+For specifications defining IDL [=operations=], it may seem that [=overloaded|overloads=] and a
+combination of [=union types=] and [=optional arguments=] have some feature overlap.
+
+It is first important to note that [=overloaded|overloads=] may have different behaviors than
+[=union types=] or [=optional arguments=], and one <em>cannot</em be fully defined using the other

Missing `>` kills much of the output here

> +
+It is first important to note that [=overloaded|overloads=] may have different behaviors than
+[=union types=] or [=optional arguments=], and one <em>cannot</em be fully defined using the other
+(unless, of course, additional prose is provided, which can defeat the purpose of the Web IDL type
+system). For example, consider the {{CanvasDrawPath/stroke()}} operations defined on the
+{{CanvasDrawPath}} interface [[HTML]]:
+
+<pre highlight="webidl">
+    interface CanvasDrawPathExcerpt {
+      void stroke();
+      void stroke(Path2D path);
+    };
+</pre>
+
+Per the ECMAScript language binding, calling <code>stroke(undefined)</code> on a object implementing
+{{CanvasDrawPath}} would attempt to call the second overload, yielding a {{TypeError}} since

Implementing CanvasDrawPathExcerpt?

> +{{CanvasDrawPath}} interface [[HTML]]:
+
+<pre highlight="webidl">
+    interface CanvasDrawPathExcerpt {
+      void stroke();
+      void stroke(Path2D path);
+    };
+</pre>
+
+Per the ECMAScript language binding, calling <code>stroke(undefined)</code> on a object implementing
+{{CanvasDrawPath}} would attempt to call the second overload, yielding a {{TypeError}} since
+<emu-val>undefined</emu-val> cannot be <a href="#es-to-interface">converted</a> to a {{Path2D}}.
+However, if the operations were instead defined with [=optional arguments=] and merged into one,
+
+<pre highlight="webidl">
+    interface CanvasDrawPathExcerpt {

CanvasDrawPathExcerpt2?

> +    };
+</pre>
+
+Per the ECMAScript language binding, calling <code>stroke(undefined)</code> on a object implementing
+{{CanvasDrawPath}} would attempt to call the second overload, yielding a {{TypeError}} since
+<emu-val>undefined</emu-val> cannot be <a href="#es-to-interface">converted</a> to a {{Path2D}}.
+However, if the operations were instead defined with [=optional arguments=] and merged into one,
+
+<pre highlight="webidl">
+    interface CanvasDrawPathExcerpt {
+      void stroke(optional Path2D path);
+    };
+</pre>
+
+the [=overload resolution algorithm=] would treat the <var ignore>path</var> argument as not present
+given the same ECMAScript language code <code>stroke(undefined)</code>, and not throw any

Maybe "not present, given the same call `stroke(undefined)`,"

> +</pre>
+
+Per the ECMAScript language binding, calling <code>stroke(undefined)</code> on a object implementing
+{{CanvasDrawPath}} would attempt to call the second overload, yielding a {{TypeError}} since
+<emu-val>undefined</emu-val> cannot be <a href="#es-to-interface">converted</a> to a {{Path2D}}.
+However, if the operations were instead defined with [=optional arguments=] and merged into one,
+
+<pre highlight="webidl">
+    interface CanvasDrawPathExcerpt {
+      void stroke(optional Path2D path);
+    };
+</pre>
+
+the [=overload resolution algorithm=] would treat the <var ignore>path</var> argument as not present
+given the same ECMAScript language code <code>stroke(undefined)</code>, and not throw any
+exceptions.

Maybe we should note this latter behavior is actually more like what web developers expect, and that if CanvasDrawPath were defined today, this would use optional arguments instead.

> +However, if the operations were instead defined with [=optional arguments=] and merged into one,
+
+<pre highlight="webidl">
+    interface CanvasDrawPathExcerpt {
+      void stroke(optional Path2D path);
+    };
+</pre>
+
+the [=overload resolution algorithm=] would treat the <var ignore>path</var> argument as not present
+given the same ECMAScript language code <code>stroke(undefined)</code>, and not throw any
+exceptions.
+
+Additionally, there are semantic differences as well. [=Overloaded=] operations are designed to map
+well to language features such as C++ overloading, and are usually a better fit for operations with
+more substantial differences in what they do given arguments of different types. [=Union types=], in
+contrast, are usually used in the sense that "any of the types would work in about the same way".

This may be a good place to insert something like

> In most cases, operations with such substantial differences are best off with different names. As such, overloads are rarely appropriate, often appearing in legacy APIs or in specialized circumstances.

> +</pre>
+
+the [=overload resolution algorithm=] would treat the <var ignore>path</var> argument as not present
+given the same ECMAScript language code <code>stroke(undefined)</code>, and not throw any
+exceptions.
+
+Additionally, there are semantic differences as well. [=Overloaded=] operations are designed to map
+well to language features such as C++ overloading, and are usually a better fit for operations with
+more substantial differences in what they do given arguments of different types. [=Union types=], in
+contrast, are usually used in the sense that "any of the types would work in about the same way".
+
+That being said, we offer the following recommendations and examples in case of difficulties to
+determine what Web IDL language feature to use:
+
+*   When the operation must return values of different types for different argument types,
+    [=overloaded|overloading=] is almost always a better choice.

Yeah. So maybe "In the unusual case where an operation must return values... This is almost never appropriate API design, and separate operations with distinct names should be used instead. But if we assume you have such an API, overloading is almost always a better choice."

> +        typedef (long or DOMString or CalculatableInterface) Calculatable;
+        interface A {
+          Calculatable calculateWithType(Calculatable input);
+        };
+    </pre>
+
+    which does not convey the fact that the return value is always of the same type as <var
+    ignore>input</var>.
+
+    The problem is exacerbated when one of the overloads has a return type of {{void}}, since
+    [=union types=] cannot even contain {{void}} as a [=member type=]. In that case, a return type
+    of {{any}} must be used with appropriate prose defining the return type, further decreasing
+    expressiveness.
+
+*   When the operation has significantly different semantics for different argument types or
+    lengths, [=overloaded|overloading=] is preferred.

Maybe: "Again, in such scenarios, it is usually better to create separate operations with distinct names, but legacy APIs sometimes follow this pattern."

> +        };
+    </pre>
+
+    Using [=optional arguments=] one can rewrite the IDL fragment as follows:
+
+    <pre highlight="webidl">
+        partial interface CSS {
+          static boolean supports(CSSOMString propertyOrConditionText, optional CSSOMString value);
+        };
+    </pre>
+
+    Even though the IDL is shorter, two distinctively different concepts are conflated in the first
+    argument. This makes the second version remarkably less readable than the first.
+
+    Another consideration is that the prose for [=overloaded=] operations can be specified in
+    separate blocks, which can aid in both reading and writing specifications, but not [=optional

"reading and writing specifications. This is not the case for optional arguments."

> +
+    <pre highlight="webidl">
+        interface A {
+          void foo(optional Node? arg);
+        };
+    </pre>
+
+    In general, optionality is best expressed using the <emu-t>optional</emu-t> keyword, and not
+    using overloads.
+
+When the case fits none of the categories above, it is up to the specification author to choose the
+style, since it is most likely that either style would sufficiently and conveniently describe the
+intended behavior. However, the definition and <a href="#es-to-union">conversion algorithms</a> of
+[=union types=] are simpler to implement and reason about than [=overload resolution
+algorithm|those=] of [=overloaded|overloads=], and can discourage implementation mistakes. Thus,
+unless any other considerations apply, [=union types=] should be the default choice.

Maybe "discourage implementation mistakes" is not the real concern here, but instead "and usually result in more idiomatic APIs in the ECMAScript binding" or something.

> +
+    <pre highlight="webidl">
+        interface A {
+          void foo(optional Node? arg);
+        };
+    </pre>
+
+    In general, optionality is best expressed using the <emu-t>optional</emu-t> keyword, and not
+    using overloads.
+
+When the case fits none of the categories above, it is up to the specification author to choose the
+style, since it is most likely that either style would sufficiently and conveniently describe the
+intended behavior. However, the definition and <a href="#es-to-union">conversion algorithms</a> of
+[=union types=] are simpler to implement and reason about than [=overload resolution
+algorithm|those=] of [=overloaded|overloads=], and can discourage implementation mistakes. Thus,
+unless any other considerations apply, [=union types=] should be the default choice.

"union types (and/or optional arguments)"?

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/heycam/webidl/pull/426#pullrequestreview-58756359

Received on Friday, 25 August 2017 20:42:29 UTC