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. +
+ 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 + 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: + +
supports(property, value)
+ method, when called, must run these steps:
+
+ 1. …
+
+ ----
+
+ The supports(conditionText)
method, when called,
+ must run these steps:
+
+ 1. …
+
+ 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. …
+
+ + 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
+ 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