diff --git a/index.bs b/index.bs index bb5187ff..245c1876 100644 --- a/index.bs +++ b/index.bs @@ -3255,6 +3255,250 @@ be the same. +
Overloading vs. union types
+ +This section is informative. + +
+ +For specifications defining IDL [=operations=], it might 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=] have different behaviors than [=union +types=] or [=optional arguments=], and one cannot 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]]: + +
+    interface CanvasDrawPathExcerpt {
+      void stroke();
+      void stroke(Path2D path);
+    };
+
+ +Per the ECMAScript language binding, calling stroke(undefined) on an object +implementing CanvasDrawPathExcerpt would attempt to call the second +overload, yielding a {{TypeError}} since undefined cannot be converted to a {{Path2D}}. However, if the operations were instead +defined with [=optional arguments=] and merged into one, + +
+    interface CanvasDrawPathExcerptOptional {
+      void stroke(optional Path2D path);
+    };
+
+ +the [=overload resolution algorithm=] would treat the path argument as [=not +present=] given the same call stroke(undefined), and not throw any exceptions. + +Note: For this particular example, the latter behavior is actually what Web developers would +generally expect. If {{CanvasDrawPath}} were to be designed today, [=optional arguments=] would be +used for stroke(). + +Additionally, there are semantic differences as well. [=Union types=] are usually used in the sense +that "any of the types would work in about the same way". In contrast, [=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. +However, in most cases, operations with such substantial differences are best off with different +names to avoid confusion for Web developers, since the ECMAScript language does not provide +language-level overloading. As such, overloads are rarely appropriate for new APIs, instead often +appearing in legacy APIs or in specialized circumstances. + +That being said, we offer the following recommendations and examples in case of difficulties to +determine what Web IDL language feature to use: + +* In the unusual case where the operation needs to return values of different types for different + argument types, [=overloaded|overloading=] will result in more expressive IDL fragments. This is almost never appropriate API design, and separate operations with distinct + names usually are a better choice for such cases. + + Suppose there is an operation calculate() that accepts a {{long}}, + {{DOMString}}, or CalculatableInterface (an [=interface type=]) as its + only argument, and returns a value of the same type as its argument. It would be clearer to + write the IDL fragment using [=overloaded=] operations as + +
+        interface A {
+          long calculate(long input);
+          DOMString calculate(DOMString input);
+          CalculatableInterface calculate(CalculatableInterface input);
+        };
+    
+ + than using a [=union type=] with a [=typedef=] as + +
+        typedef (long or DOMString or CalculatableInterface) Calculatable;
+        interface A {
+          Calculatable calculate(Calculatable input);
+        };
+    
+ + which does not convey the fact that the return value is always of the same type as input. + + 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}} needs to be used with appropriate prose defining the return type, further decreasing + expressiveness. + + If the specified calculate() is a new API and does not have any + compatibility concerns, it is suggested to use different names for the overloaded operations, + perhaps as + +
+        interface A {
+          long calculateNumber(long input);
+          DOMString calculateString(DOMString input);
+          CalculatableInterface calculateCalculatableInterface(CalculatableInterface input);
+        };
+    
+ + which allows Web developers to write explicit and unambiguous code. + +* When the operation has significantly different semantics for different argument types or + lengths, [=overloaded|overloading=] is preferred. Again, in such scenarios, it is usually better + to create separate operations with distinct names, but legacy APIs sometimes follow this + pattern. + + As an example, the {{CSS/supports()}} operations of the {{CSS}} interface is defined as the + following IDL fragment [[CSS3-CONDITIONAL]] [[CSSOM]]. + +
+        partial interface CSS {
+          static boolean supports(CSSOMString property, CSSOMString value);
+          static boolean supports(CSSOMString conditionText);
+        };
+    
+ + Using [=optional arguments=] one can rewrite the IDL fragment as follows: + +
+        partial interface CSSExcerptOptional {
+          static boolean supports(CSSOMString propertyOrConditionText, optional CSSOMString value);
+        };
+    
+ + Even though the IDL is shorter in the second version, two distinctively different concepts are + conflated in the first argument. Without [=overloaded|overloads=], the question "is property or conditionText paired with value?" + is much more difficult to answer without reading the prose definition of the operation. 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. This is not the case + for [=optional arguments=]. This means that in the first case the specification author can write + the prose definition of the operations as: + +
+ + The supports(property, value) + method, when called, must run these steps: + + 1. … + + ---- + + The supports(conditionText) method, when called, + must run these steps: + + 1. … + +
+ + Yet using value as an [=optional argument=], the specification author has to + use more boilerplate-style text to effectively replicate the [=overload resolution algorithm=]. + +
+ + The supports(|propertyOrConditionText|, |value|) method, when + called, must run these steps: + + 1. If |value| is given, then: + 1. Let property be |propertyOrConditionText|. + 1. … + 1. Otherwise: + 1. Let conditionText be |propertyOrConditionText|. + 1. … + +
+ + If the two overloads have little to no shared parts, it is better to leave overload resolution + to the IDL mechanism. + +* If the operation accepts multiple types for multiple arguments with no coupling between types of + different arguments, [=union types=] can sometimes be the only viable solution. + +
+        typedef (long long or DOMString or CalculatableInterface) SupportedArgument;
+        interface A {
+          void add(SupportedArgument operand1, SupportedArgument operand2);
+        };
+    
+ + For the add() operation above, to specify it using + [=overloaded|overloads=] would require + +
+        interface A {
+          void add(long long operand1, long long operand2);
+          void add(long long operand1, DOMString operand2);
+          void add(long long operand1, CalculatableInterface operand2);
+          void add(DOMString operand1, long long operand2);
+          void add(DOMString operand1, DOMString operand2);
+          void add(DOMString operand1, CalculatableInterface operand2);
+          void add(CalculatableInterface operand1, long long operand2);
+          void add(CalculatableInterface operand1, DOMString operand2);
+          void add(CalculatableInterface operand1, CalculatableInterface operand2);
+        };
+    
+ + and nine times the corresponding prose! + +* Specification authors are encouraged to treat missing argument and undefined + argument the same way in the ECMAScript language binding. + + Given the following IDL fragment: + +
+        interface A {
+          void foo();
+          void foo(Node? arg);
+        };
+    
+ + Using the ECMAScript language binding, calling foo(undefined) and + foo(null) would both run the steps corresponding to the foo(|arg|) operation, with |arg| set to null, while foo() alone + would go to the first overload. This can be a surprising behavior for many API users. Instead, + specification authors are encouraged to use an [=optional argument=], which would categorize + both foo() and foo(undefined) as "|arg| is [=not + present=]". + +
+        interface A {
+          void foo(optional Node? arg);
+        };
+    
+ + In general, optionality is best expressed using the optional 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 conversion algorithms of +[=union types=] and [=optional arguments=] are simpler to implement and reason about than [=overload +resolution algorithm|those=] of [=overloaded|overloads=], and usually result in more idiomatic APIs +in the ECMAScript language binding. Thus, unless any other considerations apply, [=union types=] +(and/or [=optional arguments=]) are the default choice. + +Specifications are also free to mix and match union types and overloads, if the author finds it +appropriate and convenient. + +
+ +

Iterable declarations

An [=interface=] can be declared to be