diff --git a/doc/interop.texi b/doc/interop.texi index 07b4754..8952e0c 100644 --- a/doc/interop.texi +++ b/doc/interop.texi @@ -20,6 +20,7 @@ have adopted ease.js for your own projects. @menu * Using GNU ease.js Classes Outside of ease.js:: * Prototypally Extending Classes:: +* Interoperable Polymorphism:: @end menu @@ -128,3 +129,239 @@ chain: ease.js normally prevents this from happening automatically. @end itemize + +@node Interoperable Polymorphism +@section Interoperable Polymorphism +GNU ease.js encourages polymorphism through type checking. In the case of +@ref{Prototypally Extending Classes,,prototypal subtyping}, type checks will +work as expected: + +@float Figure, f:typecheck-protosub +@verbatim + var Foo = Class( {} ); + + function SubFoo() {}; + SubFoo.prototype = Foo.asPrototype(); + SubFoo.constructor = Foo; + + var SubSubFoo = Class.extend( SubFoo, {} ); + + // vanilla ECMAScript + ( new Foo() ) instanceof Foo; // true + ( new Subfoo() ) instanceof Foo; // true + ( new SubSubFoo() ) instanceof Foo; // true + ( new SubSubFoo() ) instanceof SubFoo; // true + + // GNU ease.js + Class.isA( Foo, ( new Foo() ) ); // true + Class.isA( Foo, ( new SubFoo() ) ); // true + Class.isA( Foo, ( new SubSubFoo() ) ); // true + Class.isA( SubFoo, ( new SubSubFoo() ) ); // true +@end verbatim +@caption{Type checking with prototypal subtypes of GNU ease.js classes} +@end float + +Plainly---this means that prototypes that perform type checking for +polymorphism will accept GNU ease.js classes and vice versa. But this is not +the only form of type checking that ease.js supports. + +This is the simplest type of polymorphism and is directly compatible with +ECMAScript's prototypal mode. However, GNU ease.js offers other features +that are alien to ECMAScript on its own. + +@menu +* Interface Interop:: Using GNU ease.js interfaces in conjunction with + vanilla ECMAScript +@end menu + + +@node Interface Interop +@subsection Interface Interop +@ref{Interfaces}, when used within the bounds of GNU ease.js, allow for +strong typing of objects. Further, two interfaces that share the same API +are not equivalent; this permits conveying intent: Consider two interfaces +@code{Enemy} and @code{Toad}, each defining a method @code{croak}. The +method for @code{Enemy} results in its death, whereas the method for +@code{Toad} produces a bellowing call. Clearly classes implementing these +interfaces will have different actions associated with them; we would +probably not want an invincible enemy that croaks like a toad any time you +try to kill it (although that'd make for amusing gameplay). + +@float figure, f:interface-croak +@verbatim + var Enemy = Interface( { croak: [] } ), + Toad = Interface( { croak: [] } ), + + AnEnemy = Class.implement( Enemy ).extend( /*...*/ ), + AToad = Class.implement( Toad ).extend( /*...*/ ); + + // GNU ease.js does not consider these interfaces to be equivalent + Class.isA( Enemy, AnEnemy() ); // true + Class.isA( Toad, AnEnemy() ); // false + Class.isA( Enemy, AToad() ); // false + Class.isA( Toad, AToad() ); // true + + defeatEnemy( AnEnemy() ); // okay; is an enemy + defeatEnemy( AToad() ); // error; is a toad + + function defeatEnemy( enemy ) + { + if ( !( Class.isA( Enemy, enemy ) ) ) { + throw TypeError( "Expecting enemy" ); + } + + enemy.croak(); + } +@end verbatim +@caption{Croak like an enemy or a toad?} +@end float + +In JavaScript, it is common convention to instead use @emph{duck typing}, +which does not care what the intent of the interface is---it merely cares +whether the method being invoked actually exists.@footnote{``When I see a +bird that walks like a duck and swims like a duck and quacks like a duck, I +call that bird a duck.'' (James Whitcomb Riley).} So, in the case of the +above example, it is not a problem that an toad may be used in place of an +enemy---they both implement @code{croak} and so @emph{something} will +happen. This is most often exemplified by the use of object literals to +create ad-hoc instances of sorts: + +@float figure, f:interface-objlit +@verbatim + var enemy = { croak: function() { /* ... */ ) }, + toad = { croak: function() { /* ... */ ) }; + + defeatEnemy( enemy ); // okay; duck typing + defeatEnemy( toad ); // okay; duck typing + + // TypeError: object has no method 'croak' + defeatEnemy( { moo: function() { /*...*/ } } ); + + function defeatEnemy( enemy ) + { + enemy.croak(); + } +@end verbatim +@caption{Duck typing with object literals} +@end float + +Duck typing has the benefit of being ad-hoc and concise, but places the onus +on the developer to realize the interface and ensure that it is properly +implemented. Therefore, there are two situations to address for GNU ease.js +users that prefer strongly typed interfaces: + +@enumerate + @item + Ensure that non-ease.js users can create objects acceptable to the + strongly-typed API; and + + @item + Allow ease.js classes to require a strong API for existing objects. +@end enumerate + +These two are closely related and rely on the same underlying concepts. + +@menu +* Object Interface Compatibility:: Using vanilla ECMAScript objects where + type checking is performed on GNU ease.js + interfaces +* Building Interfaces Around Objects:: Using interfaces to validate APIs of + ECMAScript objects +@end menu + + +@node Object Interface Compatibility +@subsubsection Object Interface Compatibility +It is clear that GNU ease.js' distinction between two separate interfaces +that share the same API is not useful for vanilla ECMAScript objects, +because those objects do not have an API for implementing interfaces (and if +they did, they wouldn't be ease.js' interfaces). Therefore, in order to +design a transparently interoperable system, this distinction must be +removed (but will be @emph{retained} within ease.js' system). + +The core purpose of an interface is to declare an expected API, providing +preemptive warnings and reducing the risk of runtime error. This is in +contrast with duck typing, which favors recovering from errors when (and if) +they occur. Since an ECMAScript object cannot implement an ease.js interface +(if it did, it'd be using ease.js), the conclusion is that ease.js should +fall back to scanning the object to ensure that it is compatible with a +given interface. + +A vanilla ECMAScript object is compatible with an ease.js interface if it +defines all interface members and meets the parameter count requirements of +those members. + +@float Figure, f:interface-compat +@verbatim + var Duck = Interface( { + quack: [ 'str' ], + waddle: [], + } ); + + // false; no quack + Class.isA( Duck, { waddle: function() {} } ); + + // false; quack requires one parameter + Class.isA( Duck, { + quack: function() {}, + waddle: function() {}, + } ); + + // true + Class.isA( Duck, { + quack: function( str ) {}, + waddle: function() {}, + } ); + + // true + function ADuck() {}; + ADuck.prototype = { + quack: function( str ) {}, + waddle: function() {}, + }; + Class.isA( Duck, ( new ADuck() ) ); +@end verbatim +@caption{Vanilla ECMAScript object interface compatibility} +@end float + + +@node Building Interfaces Around Objects +@subsubsection Building Interfaces Around Objects +A consequence of @ref{Object Interface Compatibility,,the previous section} +is that users of GNU ease.js can continue to use strongly typed interfaces +even if the objects they are interfacing with do not support ease.js' +interfaces. Consider, for example, a system that uses @code{XMLHttpRequest}: + +@float Figure, f:interface-xmlhttp +@verbatim + // modeled around XMLHttpRequest + var HttpRequest = Interface( + { + abort: [], + open: [ 'method', 'url', 'async', 'user', 'password' ], + send: [], + } ); + + var FooApi = Class( + { + __construct: function( httpreq ) + { + if ( !( Class.isA( HttpRequest, httpreq ) ) ) + { + throw TypeError( "Expecting HttpRequest" ); + } + + // ... + } + } ); + + FooApi( new XMLHttpRequest() ); // okay +@end verbatim +@caption{Building an interface around needed functionality of +XMLHttpRequest} +@end float + +This feature permits runtime polymorphism with preemptive failure instead of +inconsistently requiring duck typing for external objects, but interfaces for +objects handled through ease.js. +