@c This document is part of the GNU ease.js manual. @c Copyright (C) 2014 Free Software Foundation, Inc. @c Permission is granted to copy, distribute and/or modify this document @c under the terms of the GNU Free Documentation License, Version 1.3 or @c any later version published by the Free Software Foundation; with no @c Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. @c A copy of the license is included in the section entitled ``GNU Free @c Documentation License''. @node Interoperability @chapter Interoperability GNU ease.js is not for everyone, so it is important to play nicely with vanilla ECMAScript so that prototypes and objects can be integrated with the strict restrictions of ease.js (imposed by classical OOP). In general, you should not have to worry about this: everything is designed to work fairly transparently. This chapter will go over what ease.js intentionally supports and some interesting concepts that may even be useful even if you 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 @node Using GNU ease.js Classes Outside of ease.js @section Using GNU ease.js Classes Outside of ease.js GNU ease.js is a prototype generator---it takes the class definition, applies its validations and conveniences, and generates a prototype and constructor that can be instantiated and used just as any other ECMAScript constructor/prototype. One thing to note immediately, as mentioned in the section @ref{Defining Classes,,Defining Classes}, is that constructors generated by ease.js may be instantiated either with or without the @code{new} keyword: @float Figure, f:interop-new @verbatim const Foo = Class( { /*...*/ } ); // both of these are equivalent Foo(); new Foo(); @end verbatim @caption{Constructors generated by ease.js may omit the @code{new} keyword} @end float ease.js convention is to omit the keyword for more concise code that is more easily chained, but you should follow the coding conventions of the project that you are working on. @node Prototypally Extending Classes @section Prototypally Extending Classes Since @ref{Classes,,classes} are also constructors with prototypes, they may be used as part of a prototype chain. There are, however, some important considerations when using any sort of constructor as part of a prototype chain. Conventionally, prototypes are subtyped by using a new instance as the prototype of the subtype's constructor, as so: @float Figure, f:interop-protochain-incorrect @verbatim const Foo = Class( { /*...*/ } ); // extending class as a prototype function SubFoo() {}; SubFoo.prototype = Foo(); // INCORRECT SubFoo.prototype.constructor = SubFoo; @end verbatim @caption{Incorrectly prototypally extending GNU ease.js classes} @end float The problem with this approach is that constructors may perform validations on their arguments to ensure that the instance is in a consistent state. GNU ease.js solves this problem by introducing an @code{asPrototype} method on all classes: @float Figure, f:interop-protochain @verbatim const Foo = Class( { /*...*/ } ); // extending class as a prototype function SubFoo() { // it is important to call the constructor ourselves; this is a // generic method that should work for all subtypes, even if SubFoo // implements its own __construct method this.constructor.prototype.__construct.apply( this, arguments ); // OR, if SubFoo does not define its own __construct method, you can // alternatively do this: this.__construct(); }; SubFoo.prototype = Foo.asPrototype(); // Correct SubFoo.prototype.constructor = SubFoo; @end verbatim @caption{Correctly prototypally extending GNU ease.js classes} @end float The @code{asPrototype} method instantiates the class, but does not execute the constructor. This allows it to be used as the prototype without any issues, but it is important that the constructor of the subtype invokes the constructor of the class, as in @ref{f:interop-protochain}. Otherwise, the state of the subtype is undefined. Keep in mind the following when using classes as part of the prototype chain: @itemize @item GNU ease.js member validations are not enforced; you will not be warned if an abstract method remains unimplemented or if you override a non-virtual method, for example. Please exercise diligence. @item It is not wise to override non-@ref{Inheritance,,virtual} methods, because the class designer may not have exposed a proper API for accessing and manipulating internal state, and may not provide proper protections to ensure consistent state after the method call. @item Note the @ref{Private Member Dilemma} to ensure that your prototype works properly in pre-ES5 environments and with potential future ease.js optimizations for production environments: you should not define or manipulate properties on the prototype that would conflict with private members of the subtype. This is an awkward situation, since private members are unlikely to be included in API documentation for a class; 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 const Foo = Class( {} ); function SubFoo() {}; SubFoo.prototype = Foo.asPrototype(); SubFoo.constructor = Foo; const 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 const Enemy = Interface( { croak: [] } ); const Toad = Interface( { croak: [] } ); const AnEnemy = Class.implement( Enemy ).extend( /*...*/ ); const 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 const enemy = { croak() { /* ... */ ) }; const toad = { croak() { /* ... */ ) }; defeatEnemy( enemy ); // okay; duck typing defeatEnemy( toad ); // okay; duck typing // TypeError: object has no method 'croak' defeatEnemy( { moo() { /*...*/ } } ); 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 const Duck = Interface( { quack: [ 'str' ], waddle: [], } ); // false; no quack Class.isA( Duck, { waddle() {} } ); // false; quack requires one parameter Class.isA( Duck, { quack() {}, waddle() {}, } ); // true Class.isA( Duck, { quack( str ) {}, waddle() {}, } ); // true function ADuck() {}; ADuck.prototype = { quack( str ) {}, waddle() {}, }; 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 const HttpRequest = Interface( { abort: [], open: [ 'method', 'url', 'async', 'user', 'password' ], send: [], } ); const FooApi = Class( { constructor( httpreq ) { this.assertIsA( HttpRequest, httpreq ); // ... } } ); 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.