2014-01-21 22:57:04 -05:00
|
|
|
/**
|
|
|
|
* Provides system for code reuse via traits
|
|
|
|
*
|
2016-12-29 03:11:49 -05:00
|
|
|
* Copyright (C) 2014, 2015, 2016 Free Software Foundation, Inc.
|
2014-01-21 22:57:04 -05:00
|
|
|
*
|
|
|
|
* This file is part of GNU ease.js.
|
|
|
|
*
|
|
|
|
* ease.js is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2014-04-20 21:11:30 -04:00
|
|
|
var AbstractClass = require( './class_abstract' ),
|
2014-04-22 00:24:21 -04:00
|
|
|
ClassBuilder = require( './ClassBuilder' ),
|
|
|
|
Interface = require( './interface' );
|
2014-01-21 22:57:04 -05:00
|
|
|
|
2014-06-05 23:35:03 -04:00
|
|
|
|
|
|
|
function _fvoid() {};
|
|
|
|
|
|
|
|
|
2014-03-11 06:46:12 -04:00
|
|
|
/**
|
|
|
|
* Trait constructor / base object
|
|
|
|
*
|
|
|
|
* The interpretation of the argument list varies by number. Further,
|
|
|
|
* various trait methods may be used as an alternative to invoking this
|
|
|
|
* constructor.
|
|
|
|
*
|
|
|
|
* @return {Function} trait
|
|
|
|
*/
|
2014-01-21 22:57:04 -05:00
|
|
|
function Trait()
|
|
|
|
{
|
|
|
|
switch ( arguments.length )
|
|
|
|
{
|
2014-04-26 00:42:28 -04:00
|
|
|
case 0:
|
|
|
|
throw Error( "Missing trait name or definition" );
|
|
|
|
|
2014-01-21 22:57:04 -05:00
|
|
|
case 1:
|
2014-04-26 00:42:28 -04:00
|
|
|
return ( typeof arguments[ 0 ] === 'string' )
|
|
|
|
? _createStaging.apply( this, arguments )
|
|
|
|
: Trait.extend.apply( this, arguments );
|
2014-02-13 05:21:26 -05:00
|
|
|
|
|
|
|
case 2:
|
|
|
|
return createNamedTrait.apply( this, arguments );
|
2014-01-21 22:57:04 -05:00
|
|
|
}
|
2014-04-26 00:42:28 -04:00
|
|
|
|
|
|
|
throw Error(
|
|
|
|
"Expecting at most two arguments for definition of named " +
|
|
|
|
"Trait " + name + "'; " + arguments.length + " given"
|
|
|
|
);
|
2014-01-21 22:57:04 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2014-02-13 05:21:26 -05:00
|
|
|
/**
|
|
|
|
* Create a named trait
|
|
|
|
*
|
|
|
|
* @param {string} name trait name
|
|
|
|
* @param {Object} def trait definition
|
|
|
|
*
|
|
|
|
* @return {Function} named trait
|
|
|
|
*/
|
|
|
|
function createNamedTrait( name, dfn )
|
|
|
|
{
|
|
|
|
if ( typeof name !== 'string' )
|
|
|
|
{
|
|
|
|
throw Error(
|
|
|
|
"First argument of named class definition must be a string"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
dfn.__name = name;
|
|
|
|
return Trait.extend( dfn );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-04-26 00:42:28 -04:00
|
|
|
function _createStaging( name )
|
|
|
|
{
|
|
|
|
return {
|
|
|
|
extend: function( dfn )
|
|
|
|
{
|
|
|
|
return createNamedTrait( name, dfn );
|
|
|
|
},
|
|
|
|
|
|
|
|
implement: function()
|
|
|
|
{
|
|
|
|
return createImplement( arguments, name );
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-05-27 23:03:11 -04:00
|
|
|
Trait.extend = function( /* ... */ )
|
2014-01-21 22:57:04 -05:00
|
|
|
{
|
2015-05-27 23:03:11 -04:00
|
|
|
var an = arguments.length,
|
|
|
|
dfn = arguments[ an - 1 ],
|
|
|
|
has_ext_base = ( an > 1 ),
|
|
|
|
ext_base = ( has_ext_base ) ? arguments[ 0 ] : null;
|
|
|
|
|
|
|
|
if ( an > 2 )
|
|
|
|
{
|
|
|
|
throw Error(
|
|
|
|
"Unexpected number of arguments to Trait.extend"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( has_ext_base )
|
|
|
|
{
|
|
|
|
var basetype = typeof ext_base;
|
|
|
|
|
|
|
|
if ( ( ext_base === null )
|
|
|
|
|| !( ( basetype === 'object' )
|
|
|
|
|| ( basetype === 'function' )
|
|
|
|
) )
|
|
|
|
{
|
|
|
|
throw TypeError(
|
|
|
|
"Trait cannot extend base of type '" + basetype + "'"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// prevent extending final classes (TODO: abstract this check; see
|
|
|
|
// also ClassBuilder)
|
|
|
|
if ( ext_base.___$$final$$ === true )
|
|
|
|
{
|
|
|
|
throw TypeError(
|
|
|
|
"Trait cannot extend final class"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: this is intended to be temporary; see Trait/ClassExtendTest
|
|
|
|
if ( module.exports.isTrait( ext_base ) )
|
|
|
|
{
|
|
|
|
throw TypeError( "Traits cannot extend other traits" );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-28 23:55:24 -05:00
|
|
|
// we may have been passed some additional metadata
|
2014-05-04 22:17:23 -04:00
|
|
|
var meta = ( this || {} ).__$$meta || {};
|
2014-02-28 23:55:24 -05:00
|
|
|
|
2014-02-13 05:21:26 -05:00
|
|
|
// store any provided name, since we'll be clobbering it (the definition
|
|
|
|
// object will be used to define the hidden abstract class)
|
Initial implementation of parameterized traits
This is an important feature to permit trait reuse without excessive
subtyping---composition over inheritance. For example, consider that you
have a `HttpPlainAuth` trait that adds authentication support to some
transport layer. Without parameterized traits, you have two options:
1. Expose setters for credentials
2. Trait closure
3. Extend the trait (not yet supported)
The first option seems like a simple solution:
```javascript
Transport.use( HttpPlainAuth )()
.setUser( 'username', 'password' )
.send( ... );
```
But we are now in the unfortunate situation that our initialization
procedure has changed. This, for one, means that authentication logic must
be added to anything that instantiates classes that mix in `HttpPlainAuth`.
We'll explore that in more detail momentarily.
More concerning with this first method is that, not only have we prohibited
immutability, but we have also made our code reliant on *invocation order*;
`setUser` must be called before `send`. What if we have other traits mixed
in that have similar conventions? Normally, this is the type of problem that
would be solved with a builder, but would we want every configurable trait
to return a new `Transport` instance? All that on top of littering the
API---what a mess!
The second option is to store configuration data outside of the Trait acting
as a closure:
```javascript
var _user, _pass;
function setCredentials( user, pass ) { _user = user; _pass = pass; }
Trait( 'HttpPlainAuth', { /* use _user and _pass */ } )
```
There are a number of problems with this; the most apparent is that, in this
case, the variables `_user` and `_pass` act in place of static fields---all
instances will share that data, and if the data is modified, it will affect
all instances; you are therefore relying on external state, and mutability
is forced upon you. You are also left with an awkward `setCredentials` call
that is disjoint from `HttpPlainAuth`.
The other notable issue arises if you did want to support instance-specific
credentials. You would have to use ease.js' internal identifiers (which is
undocumented and subject to change in future versions), and would likely
accumulate garbage data as mixin instances are deallocated, since ECMAScript
does not have destructor support.
To recover from memory leaks, you could instead create a trait generator:
```javascript
function createHttpPlainAuth( user, pass )
{
return Trait( { /* ... */ } );
}
```
This uses the same closure concept, but generates new traits at runtime.
This has various implications depending on your engine, and may thwart
future ease.js optimization attempts.
The third (which will be supported in the near future) is prohibitive: we'll
add many unnecessary traits that are a nightmare to develop and maintain.
Parameterized traits are similar in spirit to option three, but without
creating new traits each call: traits now support being passed configuration
data at the time of mixin that will be passed to every new instance:
```javascript
Transport.use( HttpPlainAuth( user, pass ) )()
.send( ... );
```
Notice now how the authentication configuration is isolated to the actual
mixin, *prior to* instantiation; the caller performing instantiation need
not be aware of this mixin, and so the construction logic can remain wholly
generic for all `Transport` types.
It further allows for a convenient means of providing useful, reusable
exports:
```javascript
module.exports = {
ServerFooAuth: HttpPlainAuth( userfoo, passfoo ),
ServerBarAuth: HttpPlainAuth( userbar, passbar ),
ServerFooTransport: Transport.use( module.exports.ServerFooAuth ),
// ...
};
var module = require( 'foo' );
// dynamic auth
Transport.use( foo.ServerFooAuth )().send( ... );
// or predefined classes
module.ServerFooTransport().send( ... );
```
Note that, in all of the above cases, the initialization logic is
unchanged---the caller does not need to be aware of any authentication
mechanism, nor should the caller care of its existence.
So how do you create parameterized traits? You need only define a `__mixin`
method:
Trait( 'HttpPlainAuth', { __mixin: function( user, pass ) { ... } } );
The method `__mixin` will be invoked upon instantiation of the class into
which a particular configuration of `HttpPlainAuth` is mixed into; it was
named differently from `__construct` to make clear that (a) traits cannot be
instantiated and (b) the constructor cannot be overridden by traits.
A configured parameterized trait is said to be an *argument trait*; each
argument trait's configuration is discrete, as was demonstrated by
`ServerFooAuth` and `ServerBarAuth` above. Once a parameterized trait is
configured, its arguments are stored within the argument trait and those
arguments are passed, by reference, to `__mixin`. Since any mixed in trait
can have its own `__mixin` method, this permits traits to have their own
initialization logic without the need for awkward overrides or explicit
method calls.
2014-05-28 23:37:36 -04:00
|
|
|
var name = dfn.__name || '(Trait)',
|
|
|
|
type = _getTraitType( dfn ),
|
|
|
|
isparam = ( type === 'param' );
|
2014-02-13 05:21:26 -05:00
|
|
|
|
2014-03-11 06:36:45 -04:00
|
|
|
// augment the parser to handle our own oddities
|
|
|
|
dfn.___$$parser$$ = {
|
|
|
|
each: _parseMember,
|
|
|
|
property: _parseProps,
|
2014-03-11 06:58:39 -04:00
|
|
|
getset: _parseGetSet,
|
2014-03-11 06:36:45 -04:00
|
|
|
};
|
|
|
|
|
2014-03-15 01:47:31 -04:00
|
|
|
// automatically mark ourselves as abstract if an abstract method is
|
|
|
|
// provided
|
|
|
|
dfn.___$$auto$abstract$$ = true;
|
|
|
|
|
2014-01-26 03:30:52 -05:00
|
|
|
// give the abstract trait class a distinctive name for debugging
|
|
|
|
dfn.__name = '#AbstractTrait#';
|
|
|
|
|
Initial implementation of parameterized traits
This is an important feature to permit trait reuse without excessive
subtyping---composition over inheritance. For example, consider that you
have a `HttpPlainAuth` trait that adds authentication support to some
transport layer. Without parameterized traits, you have two options:
1. Expose setters for credentials
2. Trait closure
3. Extend the trait (not yet supported)
The first option seems like a simple solution:
```javascript
Transport.use( HttpPlainAuth )()
.setUser( 'username', 'password' )
.send( ... );
```
But we are now in the unfortunate situation that our initialization
procedure has changed. This, for one, means that authentication logic must
be added to anything that instantiates classes that mix in `HttpPlainAuth`.
We'll explore that in more detail momentarily.
More concerning with this first method is that, not only have we prohibited
immutability, but we have also made our code reliant on *invocation order*;
`setUser` must be called before `send`. What if we have other traits mixed
in that have similar conventions? Normally, this is the type of problem that
would be solved with a builder, but would we want every configurable trait
to return a new `Transport` instance? All that on top of littering the
API---what a mess!
The second option is to store configuration data outside of the Trait acting
as a closure:
```javascript
var _user, _pass;
function setCredentials( user, pass ) { _user = user; _pass = pass; }
Trait( 'HttpPlainAuth', { /* use _user and _pass */ } )
```
There are a number of problems with this; the most apparent is that, in this
case, the variables `_user` and `_pass` act in place of static fields---all
instances will share that data, and if the data is modified, it will affect
all instances; you are therefore relying on external state, and mutability
is forced upon you. You are also left with an awkward `setCredentials` call
that is disjoint from `HttpPlainAuth`.
The other notable issue arises if you did want to support instance-specific
credentials. You would have to use ease.js' internal identifiers (which is
undocumented and subject to change in future versions), and would likely
accumulate garbage data as mixin instances are deallocated, since ECMAScript
does not have destructor support.
To recover from memory leaks, you could instead create a trait generator:
```javascript
function createHttpPlainAuth( user, pass )
{
return Trait( { /* ... */ } );
}
```
This uses the same closure concept, but generates new traits at runtime.
This has various implications depending on your engine, and may thwart
future ease.js optimization attempts.
The third (which will be supported in the near future) is prohibitive: we'll
add many unnecessary traits that are a nightmare to develop and maintain.
Parameterized traits are similar in spirit to option three, but without
creating new traits each call: traits now support being passed configuration
data at the time of mixin that will be passed to every new instance:
```javascript
Transport.use( HttpPlainAuth( user, pass ) )()
.send( ... );
```
Notice now how the authentication configuration is isolated to the actual
mixin, *prior to* instantiation; the caller performing instantiation need
not be aware of this mixin, and so the construction logic can remain wholly
generic for all `Transport` types.
It further allows for a convenient means of providing useful, reusable
exports:
```javascript
module.exports = {
ServerFooAuth: HttpPlainAuth( userfoo, passfoo ),
ServerBarAuth: HttpPlainAuth( userbar, passbar ),
ServerFooTransport: Transport.use( module.exports.ServerFooAuth ),
// ...
};
var module = require( 'foo' );
// dynamic auth
Transport.use( foo.ServerFooAuth )().send( ... );
// or predefined classes
module.ServerFooTransport().send( ... );
```
Note that, in all of the above cases, the initialization logic is
unchanged---the caller does not need to be aware of any authentication
mechanism, nor should the caller care of its existence.
So how do you create parameterized traits? You need only define a `__mixin`
method:
Trait( 'HttpPlainAuth', { __mixin: function( user, pass ) { ... } } );
The method `__mixin` will be invoked upon instantiation of the class into
which a particular configuration of `HttpPlainAuth` is mixed into; it was
named differently from `__construct` to make clear that (a) traits cannot be
instantiated and (b) the constructor cannot be overridden by traits.
A configured parameterized trait is said to be an *argument trait*; each
argument trait's configuration is discrete, as was demonstrated by
`ServerFooAuth` and `ServerBarAuth` above. Once a parameterized trait is
configured, its arguments are stored within the argument trait and those
arguments are passed, by reference, to `__mixin`. Since any mixed in trait
can have its own `__mixin` method, this permits traits to have their own
initialization logic without the need for awkward overrides or explicit
method calls.
2014-05-28 23:37:36 -04:00
|
|
|
// if __mixin was provided,in the definition, then we should crate a
|
|
|
|
// paramaterized trait
|
|
|
|
var Trait = ( isparam )
|
|
|
|
? function ParameterTraitType()
|
|
|
|
{
|
|
|
|
// duplicate ars in a way that v8 can optimize
|
|
|
|
var args = [], i = arguments.length;
|
|
|
|
while ( i-- ) args[ i ] = arguments[ i ];
|
|
|
|
|
|
|
|
var AT = function ArgumentTrait()
|
|
|
|
{
|
|
|
|
throw Error( "Cannot re-configure argument trait" );
|
|
|
|
};
|
|
|
|
|
|
|
|
// TODO: mess!
|
|
|
|
AT.___$$mixinargs = args;
|
|
|
|
AT.__trait = 'arg';
|
|
|
|
AT.__acls = Trait.__acls;
|
|
|
|
AT.__ccls = Trait.__ccls;
|
|
|
|
AT.toString = Trait.toString;
|
|
|
|
AT.__mixinImpl = Trait.__mixinImpl;
|
|
|
|
AT.__isInstanceOf = Trait.__isInstanceOf;
|
|
|
|
|
|
|
|
// mix in the argument trait instead of the original
|
|
|
|
AT.__mixin = function( dfn, tc, base )
|
|
|
|
{
|
|
|
|
mixin( AT, dfn, tc, base );
|
|
|
|
};
|
|
|
|
|
|
|
|
return AT;
|
|
|
|
}
|
|
|
|
: function TraitType()
|
|
|
|
{
|
|
|
|
throw Error( "Cannot instantiate non-parameterized trait" );
|
|
|
|
};
|
2014-01-21 22:57:04 -05:00
|
|
|
|
2014-02-28 23:55:24 -05:00
|
|
|
// implement interfaces if indicated
|
|
|
|
var base = AbstractClass;
|
|
|
|
if ( meta.ifaces )
|
|
|
|
{
|
|
|
|
base = base.implement.apply( null, meta.ifaces );
|
|
|
|
}
|
|
|
|
|
2014-01-23 00:34:15 -05:00
|
|
|
// and here we can see that traits are quite literally abstract classes
|
Support for trait class supertype overrides
Traits can now override methods of their class supertypes. Previously, in
order to override a method of some class `C` by mixing in some trait `T`,
both had to implement a common interface. This had two notable downsides:
1. A trait that was only compatible with details of `C` could only work
with `C#M` if it implemented an interface `I` that declared `I#M`.
This required the author of `C` to create interfaces where they would
otherwise not be necessary.
2. As a corollary of #1---since methods of interfaces must be public, it
was not possible for `T` to override any protected method of `C`; this
meant that `C` would have to declare such methods public, which may
break encapsulation or expose unnecessary concerns to the outside
world.
Until documentation is available---hopefully in the near future---the test
cases provide detailed documentation of the behavior. Stackable traits work
as you would expect:
```javascript
var C = Class(
{
'virtual foo': function()
{
return 'C';
},
} );
var T1 = Trait.extend( C,
{
'virtual abstract override foo': function()
{
return 'T1' + this.__super();
},
} );
var T2 = Trait.extend( C,
{
'virtual abstract override foo': function()
{
return 'T2' + this.__super();
},
} );
C.use( T1 )
.use( T1 )
.use( T2 )
.use( T2 )
.foo();
// result: "T2T2T1T1C"
```
If the `override` keyword is used without `abstract`, then the super method
is statically bound to the supertype, rather than being resolved at runtime:
```javascript
var C = Class(
{
'virtual foo': function()
{
return 'C';
},
} );
var T1 = Trait.extend( C,
{
'virtual abstract override foo': function()
{
return 'T1' + this.__super();
},
} );
var T2 = Trait.extend( C,
{
// static override
'virtual override foo': function()
{
return 'T2' + this.__super();
},
} );
C.use( T1 )
.use( T1 )
.use( T2 )
.use( T2 )
.foo();
// result: "T2C"
```
This latter form should be discouraged in most circumstances (as it prevents
stackable traits), but the behavior is consistent with the rest of the
system.
Happy hacking.
2015-10-22 23:44:28 -04:00
|
|
|
var tclass = ( ext_base )
|
|
|
|
? base.extend( ext_base, dfn )
|
|
|
|
: base.extend( dfn );
|
2014-01-23 00:34:15 -05:00
|
|
|
|
2015-05-27 23:03:11 -04:00
|
|
|
Trait.__trait = type;
|
|
|
|
Trait.__acls = tclass;
|
|
|
|
Trait.__ccls = null;
|
|
|
|
Trait.__extbase = ext_base;
|
|
|
|
Trait.toString = function()
|
2014-02-13 05:21:26 -05:00
|
|
|
{
|
|
|
|
return ''+name;
|
|
|
|
};
|
2014-01-23 00:34:15 -05:00
|
|
|
|
2014-08-07 22:53:10 -04:00
|
|
|
// we're not a param trait, but we want consistent data
|
|
|
|
Trait.___$$mixinargs = [];
|
|
|
|
|
2014-01-23 00:34:15 -05:00
|
|
|
// invoked to trigger mixin
|
2014-06-03 23:10:55 -04:00
|
|
|
Trait.__mixin = function( dfn, tc, base )
|
|
|
|
{
|
|
|
|
mixin( Trait, dfn, tc, base );
|
|
|
|
};
|
2014-01-21 22:57:04 -05:00
|
|
|
|
2014-02-28 23:55:24 -05:00
|
|
|
// mixes in implemented types
|
Initial implementation of parameterized traits
This is an important feature to permit trait reuse without excessive
subtyping---composition over inheritance. For example, consider that you
have a `HttpPlainAuth` trait that adds authentication support to some
transport layer. Without parameterized traits, you have two options:
1. Expose setters for credentials
2. Trait closure
3. Extend the trait (not yet supported)
The first option seems like a simple solution:
```javascript
Transport.use( HttpPlainAuth )()
.setUser( 'username', 'password' )
.send( ... );
```
But we are now in the unfortunate situation that our initialization
procedure has changed. This, for one, means that authentication logic must
be added to anything that instantiates classes that mix in `HttpPlainAuth`.
We'll explore that in more detail momentarily.
More concerning with this first method is that, not only have we prohibited
immutability, but we have also made our code reliant on *invocation order*;
`setUser` must be called before `send`. What if we have other traits mixed
in that have similar conventions? Normally, this is the type of problem that
would be solved with a builder, but would we want every configurable trait
to return a new `Transport` instance? All that on top of littering the
API---what a mess!
The second option is to store configuration data outside of the Trait acting
as a closure:
```javascript
var _user, _pass;
function setCredentials( user, pass ) { _user = user; _pass = pass; }
Trait( 'HttpPlainAuth', { /* use _user and _pass */ } )
```
There are a number of problems with this; the most apparent is that, in this
case, the variables `_user` and `_pass` act in place of static fields---all
instances will share that data, and if the data is modified, it will affect
all instances; you are therefore relying on external state, and mutability
is forced upon you. You are also left with an awkward `setCredentials` call
that is disjoint from `HttpPlainAuth`.
The other notable issue arises if you did want to support instance-specific
credentials. You would have to use ease.js' internal identifiers (which is
undocumented and subject to change in future versions), and would likely
accumulate garbage data as mixin instances are deallocated, since ECMAScript
does not have destructor support.
To recover from memory leaks, you could instead create a trait generator:
```javascript
function createHttpPlainAuth( user, pass )
{
return Trait( { /* ... */ } );
}
```
This uses the same closure concept, but generates new traits at runtime.
This has various implications depending on your engine, and may thwart
future ease.js optimization attempts.
The third (which will be supported in the near future) is prohibitive: we'll
add many unnecessary traits that are a nightmare to develop and maintain.
Parameterized traits are similar in spirit to option three, but without
creating new traits each call: traits now support being passed configuration
data at the time of mixin that will be passed to every new instance:
```javascript
Transport.use( HttpPlainAuth( user, pass ) )()
.send( ... );
```
Notice now how the authentication configuration is isolated to the actual
mixin, *prior to* instantiation; the caller performing instantiation need
not be aware of this mixin, and so the construction logic can remain wholly
generic for all `Transport` types.
It further allows for a convenient means of providing useful, reusable
exports:
```javascript
module.exports = {
ServerFooAuth: HttpPlainAuth( userfoo, passfoo ),
ServerBarAuth: HttpPlainAuth( userbar, passbar ),
ServerFooTransport: Transport.use( module.exports.ServerFooAuth ),
// ...
};
var module = require( 'foo' );
// dynamic auth
Transport.use( foo.ServerFooAuth )().send( ... );
// or predefined classes
module.ServerFooTransport().send( ... );
```
Note that, in all of the above cases, the initialization logic is
unchanged---the caller does not need to be aware of any authentication
mechanism, nor should the caller care of its existence.
So how do you create parameterized traits? You need only define a `__mixin`
method:
Trait( 'HttpPlainAuth', { __mixin: function( user, pass ) { ... } } );
The method `__mixin` will be invoked upon instantiation of the class into
which a particular configuration of `HttpPlainAuth` is mixed into; it was
named differently from `__construct` to make clear that (a) traits cannot be
instantiated and (b) the constructor cannot be overridden by traits.
A configured parameterized trait is said to be an *argument trait*; each
argument trait's configuration is discrete, as was demonstrated by
`ServerFooAuth` and `ServerBarAuth` above. Once a parameterized trait is
configured, its arguments are stored within the argument trait and those
arguments are passed, by reference, to `__mixin`. Since any mixed in trait
can have its own `__mixin` method, this permits traits to have their own
initialization logic without the need for awkward overrides or explicit
method calls.
2014-05-28 23:37:36 -04:00
|
|
|
Trait.__mixinImpl = function( dest_meta )
|
2014-02-28 23:55:24 -05:00
|
|
|
{
|
|
|
|
mixinImpl( tclass, dest_meta );
|
|
|
|
};
|
|
|
|
|
2014-04-22 00:24:21 -04:00
|
|
|
// TODO: this and the above should use util.defineSecureProp
|
Initial implementation of parameterized traits
This is an important feature to permit trait reuse without excessive
subtyping---composition over inheritance. For example, consider that you
have a `HttpPlainAuth` trait that adds authentication support to some
transport layer. Without parameterized traits, you have two options:
1. Expose setters for credentials
2. Trait closure
3. Extend the trait (not yet supported)
The first option seems like a simple solution:
```javascript
Transport.use( HttpPlainAuth )()
.setUser( 'username', 'password' )
.send( ... );
```
But we are now in the unfortunate situation that our initialization
procedure has changed. This, for one, means that authentication logic must
be added to anything that instantiates classes that mix in `HttpPlainAuth`.
We'll explore that in more detail momentarily.
More concerning with this first method is that, not only have we prohibited
immutability, but we have also made our code reliant on *invocation order*;
`setUser` must be called before `send`. What if we have other traits mixed
in that have similar conventions? Normally, this is the type of problem that
would be solved with a builder, but would we want every configurable trait
to return a new `Transport` instance? All that on top of littering the
API---what a mess!
The second option is to store configuration data outside of the Trait acting
as a closure:
```javascript
var _user, _pass;
function setCredentials( user, pass ) { _user = user; _pass = pass; }
Trait( 'HttpPlainAuth', { /* use _user and _pass */ } )
```
There are a number of problems with this; the most apparent is that, in this
case, the variables `_user` and `_pass` act in place of static fields---all
instances will share that data, and if the data is modified, it will affect
all instances; you are therefore relying on external state, and mutability
is forced upon you. You are also left with an awkward `setCredentials` call
that is disjoint from `HttpPlainAuth`.
The other notable issue arises if you did want to support instance-specific
credentials. You would have to use ease.js' internal identifiers (which is
undocumented and subject to change in future versions), and would likely
accumulate garbage data as mixin instances are deallocated, since ECMAScript
does not have destructor support.
To recover from memory leaks, you could instead create a trait generator:
```javascript
function createHttpPlainAuth( user, pass )
{
return Trait( { /* ... */ } );
}
```
This uses the same closure concept, but generates new traits at runtime.
This has various implications depending on your engine, and may thwart
future ease.js optimization attempts.
The third (which will be supported in the near future) is prohibitive: we'll
add many unnecessary traits that are a nightmare to develop and maintain.
Parameterized traits are similar in spirit to option three, but without
creating new traits each call: traits now support being passed configuration
data at the time of mixin that will be passed to every new instance:
```javascript
Transport.use( HttpPlainAuth( user, pass ) )()
.send( ... );
```
Notice now how the authentication configuration is isolated to the actual
mixin, *prior to* instantiation; the caller performing instantiation need
not be aware of this mixin, and so the construction logic can remain wholly
generic for all `Transport` types.
It further allows for a convenient means of providing useful, reusable
exports:
```javascript
module.exports = {
ServerFooAuth: HttpPlainAuth( userfoo, passfoo ),
ServerBarAuth: HttpPlainAuth( userbar, passbar ),
ServerFooTransport: Transport.use( module.exports.ServerFooAuth ),
// ...
};
var module = require( 'foo' );
// dynamic auth
Transport.use( foo.ServerFooAuth )().send( ... );
// or predefined classes
module.ServerFooTransport().send( ... );
```
Note that, in all of the above cases, the initialization logic is
unchanged---the caller does not need to be aware of any authentication
mechanism, nor should the caller care of its existence.
So how do you create parameterized traits? You need only define a `__mixin`
method:
Trait( 'HttpPlainAuth', { __mixin: function( user, pass ) { ... } } );
The method `__mixin` will be invoked upon instantiation of the class into
which a particular configuration of `HttpPlainAuth` is mixed into; it was
named differently from `__construct` to make clear that (a) traits cannot be
instantiated and (b) the constructor cannot be overridden by traits.
A configured parameterized trait is said to be an *argument trait*; each
argument trait's configuration is discrete, as was demonstrated by
`ServerFooAuth` and `ServerBarAuth` above. Once a parameterized trait is
configured, its arguments are stored within the argument trait and those
arguments are passed, by reference, to `__mixin`. Since any mixed in trait
can have its own `__mixin` method, this permits traits to have their own
initialization logic without the need for awkward overrides or explicit
method calls.
2014-05-28 23:37:36 -04:00
|
|
|
Trait.__isInstanceOf = Interface.isInstanceOf;
|
2014-04-22 00:24:21 -04:00
|
|
|
|
Initial implementation of parameterized traits
This is an important feature to permit trait reuse without excessive
subtyping---composition over inheritance. For example, consider that you
have a `HttpPlainAuth` trait that adds authentication support to some
transport layer. Without parameterized traits, you have two options:
1. Expose setters for credentials
2. Trait closure
3. Extend the trait (not yet supported)
The first option seems like a simple solution:
```javascript
Transport.use( HttpPlainAuth )()
.setUser( 'username', 'password' )
.send( ... );
```
But we are now in the unfortunate situation that our initialization
procedure has changed. This, for one, means that authentication logic must
be added to anything that instantiates classes that mix in `HttpPlainAuth`.
We'll explore that in more detail momentarily.
More concerning with this first method is that, not only have we prohibited
immutability, but we have also made our code reliant on *invocation order*;
`setUser` must be called before `send`. What if we have other traits mixed
in that have similar conventions? Normally, this is the type of problem that
would be solved with a builder, but would we want every configurable trait
to return a new `Transport` instance? All that on top of littering the
API---what a mess!
The second option is to store configuration data outside of the Trait acting
as a closure:
```javascript
var _user, _pass;
function setCredentials( user, pass ) { _user = user; _pass = pass; }
Trait( 'HttpPlainAuth', { /* use _user and _pass */ } )
```
There are a number of problems with this; the most apparent is that, in this
case, the variables `_user` and `_pass` act in place of static fields---all
instances will share that data, and if the data is modified, it will affect
all instances; you are therefore relying on external state, and mutability
is forced upon you. You are also left with an awkward `setCredentials` call
that is disjoint from `HttpPlainAuth`.
The other notable issue arises if you did want to support instance-specific
credentials. You would have to use ease.js' internal identifiers (which is
undocumented and subject to change in future versions), and would likely
accumulate garbage data as mixin instances are deallocated, since ECMAScript
does not have destructor support.
To recover from memory leaks, you could instead create a trait generator:
```javascript
function createHttpPlainAuth( user, pass )
{
return Trait( { /* ... */ } );
}
```
This uses the same closure concept, but generates new traits at runtime.
This has various implications depending on your engine, and may thwart
future ease.js optimization attempts.
The third (which will be supported in the near future) is prohibitive: we'll
add many unnecessary traits that are a nightmare to develop and maintain.
Parameterized traits are similar in spirit to option three, but without
creating new traits each call: traits now support being passed configuration
data at the time of mixin that will be passed to every new instance:
```javascript
Transport.use( HttpPlainAuth( user, pass ) )()
.send( ... );
```
Notice now how the authentication configuration is isolated to the actual
mixin, *prior to* instantiation; the caller performing instantiation need
not be aware of this mixin, and so the construction logic can remain wholly
generic for all `Transport` types.
It further allows for a convenient means of providing useful, reusable
exports:
```javascript
module.exports = {
ServerFooAuth: HttpPlainAuth( userfoo, passfoo ),
ServerBarAuth: HttpPlainAuth( userbar, passbar ),
ServerFooTransport: Transport.use( module.exports.ServerFooAuth ),
// ...
};
var module = require( 'foo' );
// dynamic auth
Transport.use( foo.ServerFooAuth )().send( ... );
// or predefined classes
module.ServerFooTransport().send( ... );
```
Note that, in all of the above cases, the initialization logic is
unchanged---the caller does not need to be aware of any authentication
mechanism, nor should the caller care of its existence.
So how do you create parameterized traits? You need only define a `__mixin`
method:
Trait( 'HttpPlainAuth', { __mixin: function( user, pass ) { ... } } );
The method `__mixin` will be invoked upon instantiation of the class into
which a particular configuration of `HttpPlainAuth` is mixed into; it was
named differently from `__construct` to make clear that (a) traits cannot be
instantiated and (b) the constructor cannot be overridden by traits.
A configured parameterized trait is said to be an *argument trait*; each
argument trait's configuration is discrete, as was demonstrated by
`ServerFooAuth` and `ServerBarAuth` above. Once a parameterized trait is
configured, its arguments are stored within the argument trait and those
arguments are passed, by reference, to `__mixin`. Since any mixed in trait
can have its own `__mixin` method, this permits traits to have their own
initialization logic without the need for awkward overrides or explicit
method calls.
2014-05-28 23:37:36 -04:00
|
|
|
return Trait;
|
2014-01-21 22:57:04 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2015-05-27 23:03:11 -04:00
|
|
|
/**
|
|
|
|
* Validate whether mixin is permitted
|
|
|
|
*
|
|
|
|
* If a mixee (the trait being mixed in) extends some type S, then a
|
|
|
|
* contract has been created mandating that that trait may only be mixed
|
|
|
|
* into something of type S; a `TypeError` will be thrown if this contract
|
|
|
|
* is violated.
|
|
|
|
*
|
2016-12-28 03:07:16 -05:00
|
|
|
* @param {Class} base mixer (target of mixin)
|
2015-05-27 23:03:11 -04:00
|
|
|
* @param {Trait} T mixee (trait being mixed in)
|
|
|
|
*
|
|
|
|
* @return {undefined}
|
|
|
|
*
|
|
|
|
* @throws {TypeError} on type contract violation
|
|
|
|
*/
|
|
|
|
function _validateMixin( base, T )
|
|
|
|
{
|
|
|
|
if ( !T.__extbase )
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: isSubtypeOf
|
|
|
|
if ( !( ( T.__extbase === base )
|
|
|
|
|| ClassBuilder.isInstanceOf( T.__extbase, base.asPrototype() )
|
|
|
|
) )
|
|
|
|
{
|
|
|
|
throw TypeError(
|
|
|
|
"Cannot mix trait " + T.toString() + " into " + base.toString() +
|
2016-12-28 03:07:16 -05:00
|
|
|
"; mixer must be of type " + T.__extbase.toString()
|
2015-05-27 23:03:11 -04:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
Initial implementation of parameterized traits
This is an important feature to permit trait reuse without excessive
subtyping---composition over inheritance. For example, consider that you
have a `HttpPlainAuth` trait that adds authentication support to some
transport layer. Without parameterized traits, you have two options:
1. Expose setters for credentials
2. Trait closure
3. Extend the trait (not yet supported)
The first option seems like a simple solution:
```javascript
Transport.use( HttpPlainAuth )()
.setUser( 'username', 'password' )
.send( ... );
```
But we are now in the unfortunate situation that our initialization
procedure has changed. This, for one, means that authentication logic must
be added to anything that instantiates classes that mix in `HttpPlainAuth`.
We'll explore that in more detail momentarily.
More concerning with this first method is that, not only have we prohibited
immutability, but we have also made our code reliant on *invocation order*;
`setUser` must be called before `send`. What if we have other traits mixed
in that have similar conventions? Normally, this is the type of problem that
would be solved with a builder, but would we want every configurable trait
to return a new `Transport` instance? All that on top of littering the
API---what a mess!
The second option is to store configuration data outside of the Trait acting
as a closure:
```javascript
var _user, _pass;
function setCredentials( user, pass ) { _user = user; _pass = pass; }
Trait( 'HttpPlainAuth', { /* use _user and _pass */ } )
```
There are a number of problems with this; the most apparent is that, in this
case, the variables `_user` and `_pass` act in place of static fields---all
instances will share that data, and if the data is modified, it will affect
all instances; you are therefore relying on external state, and mutability
is forced upon you. You are also left with an awkward `setCredentials` call
that is disjoint from `HttpPlainAuth`.
The other notable issue arises if you did want to support instance-specific
credentials. You would have to use ease.js' internal identifiers (which is
undocumented and subject to change in future versions), and would likely
accumulate garbage data as mixin instances are deallocated, since ECMAScript
does not have destructor support.
To recover from memory leaks, you could instead create a trait generator:
```javascript
function createHttpPlainAuth( user, pass )
{
return Trait( { /* ... */ } );
}
```
This uses the same closure concept, but generates new traits at runtime.
This has various implications depending on your engine, and may thwart
future ease.js optimization attempts.
The third (which will be supported in the near future) is prohibitive: we'll
add many unnecessary traits that are a nightmare to develop and maintain.
Parameterized traits are similar in spirit to option three, but without
creating new traits each call: traits now support being passed configuration
data at the time of mixin that will be passed to every new instance:
```javascript
Transport.use( HttpPlainAuth( user, pass ) )()
.send( ... );
```
Notice now how the authentication configuration is isolated to the actual
mixin, *prior to* instantiation; the caller performing instantiation need
not be aware of this mixin, and so the construction logic can remain wholly
generic for all `Transport` types.
It further allows for a convenient means of providing useful, reusable
exports:
```javascript
module.exports = {
ServerFooAuth: HttpPlainAuth( userfoo, passfoo ),
ServerBarAuth: HttpPlainAuth( userbar, passbar ),
ServerFooTransport: Transport.use( module.exports.ServerFooAuth ),
// ...
};
var module = require( 'foo' );
// dynamic auth
Transport.use( foo.ServerFooAuth )().send( ... );
// or predefined classes
module.ServerFooTransport().send( ... );
```
Note that, in all of the above cases, the initialization logic is
unchanged---the caller does not need to be aware of any authentication
mechanism, nor should the caller care of its existence.
So how do you create parameterized traits? You need only define a `__mixin`
method:
Trait( 'HttpPlainAuth', { __mixin: function( user, pass ) { ... } } );
The method `__mixin` will be invoked upon instantiation of the class into
which a particular configuration of `HttpPlainAuth` is mixed into; it was
named differently from `__construct` to make clear that (a) traits cannot be
instantiated and (b) the constructor cannot be overridden by traits.
A configured parameterized trait is said to be an *argument trait*; each
argument trait's configuration is discrete, as was demonstrated by
`ServerFooAuth` and `ServerBarAuth` above. Once a parameterized trait is
configured, its arguments are stored within the argument trait and those
arguments are passed, by reference, to `__mixin`. Since any mixed in trait
can have its own `__mixin` method, this permits traits to have their own
initialization logic without the need for awkward overrides or explicit
method calls.
2014-05-28 23:37:36 -04:00
|
|
|
/**
|
|
|
|
* Retrieve a string representation of the trait type
|
|
|
|
*
|
|
|
|
* A trait is parameterized if it has a __mixin method; otherwise, it is
|
|
|
|
* standard.
|
|
|
|
*
|
|
|
|
* @param {Object} dfn trait definition object
|
|
|
|
* @return {string} trait type
|
|
|
|
*/
|
|
|
|
function _getTraitType( dfn )
|
|
|
|
{
|
|
|
|
return ( typeof dfn.__mixin === 'function' )
|
|
|
|
? 'param'
|
|
|
|
: 'std';
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-03-11 06:36:45 -04:00
|
|
|
/**
|
|
|
|
* Verifies trait member restrictions
|
|
|
|
*
|
|
|
|
* @param {string} name property name
|
|
|
|
* @param {*} value property value
|
|
|
|
* @param {Object} keywords property keywords
|
|
|
|
* @param {Function} h original handler that we replaced
|
|
|
|
*
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function _parseMember( name, value, keywords, h )
|
|
|
|
{
|
|
|
|
// traits are not permitted to define constructors
|
|
|
|
if ( name === '__construct' )
|
|
|
|
{
|
|
|
|
throw Error( "Traits may not define __construct" );
|
|
|
|
}
|
|
|
|
|
2014-03-11 06:37:16 -04:00
|
|
|
// will be supported in future versions
|
|
|
|
if ( keywords['static'] )
|
|
|
|
{
|
|
|
|
throw Error(
|
|
|
|
"Cannot define member `" + name + "'; static trait " +
|
|
|
|
"members are currently unsupported"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2014-03-11 06:36:45 -04:00
|
|
|
// apply original handler
|
|
|
|
h.apply( this, arguments );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-03-07 01:07:46 -05:00
|
|
|
/**
|
|
|
|
* Throws error if non-internal property is found within PROPS
|
|
|
|
*
|
|
|
|
* For details and rationale, see the Trait/PropertyTest case.
|
|
|
|
*
|
2014-03-11 06:36:45 -04:00
|
|
|
* @param {string} name property name
|
|
|
|
* @param {*} value property value
|
|
|
|
* @param {Object} keywords property keywords
|
|
|
|
* @param {Function} h original handler that we replaced
|
2014-03-07 01:07:46 -05:00
|
|
|
*
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
2014-03-11 06:36:45 -04:00
|
|
|
function _parseProps( name, value, keywords, h )
|
2014-03-07 01:07:46 -05:00
|
|
|
{
|
2014-03-11 06:36:45 -04:00
|
|
|
// ignore internal properties
|
|
|
|
if ( name.substr( 0, 3 ) === '___' )
|
2014-03-07 01:07:46 -05:00
|
|
|
{
|
2014-03-11 06:36:45 -04:00
|
|
|
return;
|
|
|
|
}
|
2014-03-07 01:07:46 -05:00
|
|
|
|
2014-03-11 06:36:45 -04:00
|
|
|
if ( !( keywords['private'] ) )
|
|
|
|
{
|
2014-03-07 01:07:46 -05:00
|
|
|
throw Error(
|
2014-03-11 06:36:45 -04:00
|
|
|
"Cannot define property `" + name + "'; only private " +
|
2014-03-07 01:07:46 -05:00
|
|
|
"properties are permitted within Trait definitions"
|
|
|
|
);
|
|
|
|
}
|
2014-03-11 06:36:45 -04:00
|
|
|
|
|
|
|
// apply original handler
|
|
|
|
h.apply( this, arguments );
|
2014-03-07 01:07:46 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-03-11 06:58:39 -04:00
|
|
|
/**
|
|
|
|
* Immediately throws an exception, as getters/setters are unsupported
|
|
|
|
*
|
|
|
|
* This is a temporary restriction; they will be supported in future
|
|
|
|
* releases.
|
|
|
|
*
|
|
|
|
* @param {string} name property name
|
|
|
|
* @param {*} value property value
|
|
|
|
* @param {Object} keywords property keywords
|
|
|
|
* @param {Function} h original handler that we replaced
|
|
|
|
*
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function _parseGetSet( name, value, keywords, h )
|
|
|
|
{
|
|
|
|
throw Error(
|
|
|
|
"Cannot define property `" + name + "'; getters/setters are " +
|
|
|
|
"currently unsupported"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-03-11 06:46:12 -04:00
|
|
|
/**
|
|
|
|
* Implement one or more interfaces
|
|
|
|
*
|
|
|
|
* Implementing an interface into a trait has the same effect as it does
|
|
|
|
* within classes in that it will automatically define abstract methods
|
|
|
|
* unless a concrete method is provided. Further, the class that the trait
|
|
|
|
* is mixed into will act as though it implemented the interfaces.
|
|
|
|
*
|
|
|
|
* @param {...Function} interfaces interfaces to implement
|
|
|
|
*
|
|
|
|
* @return {Object} staged trait object
|
|
|
|
*/
|
2014-02-28 23:55:24 -05:00
|
|
|
Trait.implement = function()
|
|
|
|
{
|
2014-04-26 00:42:28 -04:00
|
|
|
return createImplement( arguments );
|
|
|
|
};
|
2014-02-28 23:55:24 -05:00
|
|
|
|
2014-04-26 00:42:28 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a staging object from which a trait implementing a set of
|
|
|
|
* interfaces may be defined
|
|
|
|
*
|
|
|
|
* @param {...Function} interfaces interfaces to implement
|
|
|
|
* @param {string=} name optional trait name
|
|
|
|
*
|
|
|
|
* @return {Object} staged trait object
|
|
|
|
*/
|
|
|
|
function createImplement( ifaces, name )
|
|
|
|
{
|
2014-02-28 23:55:24 -05:00
|
|
|
return {
|
2014-04-26 00:42:28 -04:00
|
|
|
extend: function( dfn )
|
2014-02-28 23:55:24 -05:00
|
|
|
{
|
2014-04-26 00:42:28 -04:00
|
|
|
if ( name )
|
|
|
|
{
|
|
|
|
dfn.__name = name;
|
|
|
|
}
|
|
|
|
|
2014-02-28 23:55:24 -05:00
|
|
|
// pass our interface metadata as the invocation context
|
2014-04-26 00:42:28 -04:00
|
|
|
return Trait.extend.call(
|
2014-02-28 23:55:24 -05:00
|
|
|
{ __$$meta: { ifaces: ifaces } },
|
2014-04-26 00:42:28 -04:00
|
|
|
dfn
|
2014-02-28 23:55:24 -05:00
|
|
|
);
|
|
|
|
},
|
|
|
|
};
|
2014-04-26 00:42:28 -04:00
|
|
|
}
|
2014-02-28 23:55:24 -05:00
|
|
|
|
|
|
|
|
2014-03-11 06:46:12 -04:00
|
|
|
/**
|
|
|
|
* Determines if the provided value references a trait
|
|
|
|
*
|
Initial implementation of parameterized traits
This is an important feature to permit trait reuse without excessive
subtyping---composition over inheritance. For example, consider that you
have a `HttpPlainAuth` trait that adds authentication support to some
transport layer. Without parameterized traits, you have two options:
1. Expose setters for credentials
2. Trait closure
3. Extend the trait (not yet supported)
The first option seems like a simple solution:
```javascript
Transport.use( HttpPlainAuth )()
.setUser( 'username', 'password' )
.send( ... );
```
But we are now in the unfortunate situation that our initialization
procedure has changed. This, for one, means that authentication logic must
be added to anything that instantiates classes that mix in `HttpPlainAuth`.
We'll explore that in more detail momentarily.
More concerning with this first method is that, not only have we prohibited
immutability, but we have also made our code reliant on *invocation order*;
`setUser` must be called before `send`. What if we have other traits mixed
in that have similar conventions? Normally, this is the type of problem that
would be solved with a builder, but would we want every configurable trait
to return a new `Transport` instance? All that on top of littering the
API---what a mess!
The second option is to store configuration data outside of the Trait acting
as a closure:
```javascript
var _user, _pass;
function setCredentials( user, pass ) { _user = user; _pass = pass; }
Trait( 'HttpPlainAuth', { /* use _user and _pass */ } )
```
There are a number of problems with this; the most apparent is that, in this
case, the variables `_user` and `_pass` act in place of static fields---all
instances will share that data, and if the data is modified, it will affect
all instances; you are therefore relying on external state, and mutability
is forced upon you. You are also left with an awkward `setCredentials` call
that is disjoint from `HttpPlainAuth`.
The other notable issue arises if you did want to support instance-specific
credentials. You would have to use ease.js' internal identifiers (which is
undocumented and subject to change in future versions), and would likely
accumulate garbage data as mixin instances are deallocated, since ECMAScript
does not have destructor support.
To recover from memory leaks, you could instead create a trait generator:
```javascript
function createHttpPlainAuth( user, pass )
{
return Trait( { /* ... */ } );
}
```
This uses the same closure concept, but generates new traits at runtime.
This has various implications depending on your engine, and may thwart
future ease.js optimization attempts.
The third (which will be supported in the near future) is prohibitive: we'll
add many unnecessary traits that are a nightmare to develop and maintain.
Parameterized traits are similar in spirit to option three, but without
creating new traits each call: traits now support being passed configuration
data at the time of mixin that will be passed to every new instance:
```javascript
Transport.use( HttpPlainAuth( user, pass ) )()
.send( ... );
```
Notice now how the authentication configuration is isolated to the actual
mixin, *prior to* instantiation; the caller performing instantiation need
not be aware of this mixin, and so the construction logic can remain wholly
generic for all `Transport` types.
It further allows for a convenient means of providing useful, reusable
exports:
```javascript
module.exports = {
ServerFooAuth: HttpPlainAuth( userfoo, passfoo ),
ServerBarAuth: HttpPlainAuth( userbar, passbar ),
ServerFooTransport: Transport.use( module.exports.ServerFooAuth ),
// ...
};
var module = require( 'foo' );
// dynamic auth
Transport.use( foo.ServerFooAuth )().send( ... );
// or predefined classes
module.ServerFooTransport().send( ... );
```
Note that, in all of the above cases, the initialization logic is
unchanged---the caller does not need to be aware of any authentication
mechanism, nor should the caller care of its existence.
So how do you create parameterized traits? You need only define a `__mixin`
method:
Trait( 'HttpPlainAuth', { __mixin: function( user, pass ) { ... } } );
The method `__mixin` will be invoked upon instantiation of the class into
which a particular configuration of `HttpPlainAuth` is mixed into; it was
named differently from `__construct` to make clear that (a) traits cannot be
instantiated and (b) the constructor cannot be overridden by traits.
A configured parameterized trait is said to be an *argument trait*; each
argument trait's configuration is discrete, as was demonstrated by
`ServerFooAuth` and `ServerBarAuth` above. Once a parameterized trait is
configured, its arguments are stored within the argument trait and those
arguments are passed, by reference, to `__mixin`. Since any mixed in trait
can have its own `__mixin` method, this permits traits to have their own
initialization logic without the need for awkward overrides or explicit
method calls.
2014-05-28 23:37:36 -04:00
|
|
|
* @param {*} trait value to check
|
2014-03-11 06:46:12 -04:00
|
|
|
* @return {boolean} whether the provided value references a trait
|
|
|
|
*/
|
2014-01-21 22:57:04 -05:00
|
|
|
Trait.isTrait = function( trait )
|
|
|
|
{
|
|
|
|
return !!( trait || {} ).__trait;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
Initial implementation of parameterized traits
This is an important feature to permit trait reuse without excessive
subtyping---composition over inheritance. For example, consider that you
have a `HttpPlainAuth` trait that adds authentication support to some
transport layer. Without parameterized traits, you have two options:
1. Expose setters for credentials
2. Trait closure
3. Extend the trait (not yet supported)
The first option seems like a simple solution:
```javascript
Transport.use( HttpPlainAuth )()
.setUser( 'username', 'password' )
.send( ... );
```
But we are now in the unfortunate situation that our initialization
procedure has changed. This, for one, means that authentication logic must
be added to anything that instantiates classes that mix in `HttpPlainAuth`.
We'll explore that in more detail momentarily.
More concerning with this first method is that, not only have we prohibited
immutability, but we have also made our code reliant on *invocation order*;
`setUser` must be called before `send`. What if we have other traits mixed
in that have similar conventions? Normally, this is the type of problem that
would be solved with a builder, but would we want every configurable trait
to return a new `Transport` instance? All that on top of littering the
API---what a mess!
The second option is to store configuration data outside of the Trait acting
as a closure:
```javascript
var _user, _pass;
function setCredentials( user, pass ) { _user = user; _pass = pass; }
Trait( 'HttpPlainAuth', { /* use _user and _pass */ } )
```
There are a number of problems with this; the most apparent is that, in this
case, the variables `_user` and `_pass` act in place of static fields---all
instances will share that data, and if the data is modified, it will affect
all instances; you are therefore relying on external state, and mutability
is forced upon you. You are also left with an awkward `setCredentials` call
that is disjoint from `HttpPlainAuth`.
The other notable issue arises if you did want to support instance-specific
credentials. You would have to use ease.js' internal identifiers (which is
undocumented and subject to change in future versions), and would likely
accumulate garbage data as mixin instances are deallocated, since ECMAScript
does not have destructor support.
To recover from memory leaks, you could instead create a trait generator:
```javascript
function createHttpPlainAuth( user, pass )
{
return Trait( { /* ... */ } );
}
```
This uses the same closure concept, but generates new traits at runtime.
This has various implications depending on your engine, and may thwart
future ease.js optimization attempts.
The third (which will be supported in the near future) is prohibitive: we'll
add many unnecessary traits that are a nightmare to develop and maintain.
Parameterized traits are similar in spirit to option three, but without
creating new traits each call: traits now support being passed configuration
data at the time of mixin that will be passed to every new instance:
```javascript
Transport.use( HttpPlainAuth( user, pass ) )()
.send( ... );
```
Notice now how the authentication configuration is isolated to the actual
mixin, *prior to* instantiation; the caller performing instantiation need
not be aware of this mixin, and so the construction logic can remain wholly
generic for all `Transport` types.
It further allows for a convenient means of providing useful, reusable
exports:
```javascript
module.exports = {
ServerFooAuth: HttpPlainAuth( userfoo, passfoo ),
ServerBarAuth: HttpPlainAuth( userbar, passbar ),
ServerFooTransport: Transport.use( module.exports.ServerFooAuth ),
// ...
};
var module = require( 'foo' );
// dynamic auth
Transport.use( foo.ServerFooAuth )().send( ... );
// or predefined classes
module.ServerFooTransport().send( ... );
```
Note that, in all of the above cases, the initialization logic is
unchanged---the caller does not need to be aware of any authentication
mechanism, nor should the caller care of its existence.
So how do you create parameterized traits? You need only define a `__mixin`
method:
Trait( 'HttpPlainAuth', { __mixin: function( user, pass ) { ... } } );
The method `__mixin` will be invoked upon instantiation of the class into
which a particular configuration of `HttpPlainAuth` is mixed into; it was
named differently from `__construct` to make clear that (a) traits cannot be
instantiated and (b) the constructor cannot be overridden by traits.
A configured parameterized trait is said to be an *argument trait*; each
argument trait's configuration is discrete, as was demonstrated by
`ServerFooAuth` and `ServerBarAuth` above. Once a parameterized trait is
configured, its arguments are stored within the argument trait and those
arguments are passed, by reference, to `__mixin`. Since any mixed in trait
can have its own `__mixin` method, this permits traits to have their own
initialization logic without the need for awkward overrides or explicit
method calls.
2014-05-28 23:37:36 -04:00
|
|
|
/**
|
|
|
|
* Determines if the provided value references a parameterized trait
|
|
|
|
*
|
|
|
|
* @param {*} trait value to check
|
|
|
|
* @return {boolean} whether the provided value references a param trait
|
|
|
|
*/
|
|
|
|
Trait.isParameterTrait = function( trait )
|
|
|
|
{
|
|
|
|
return !!( ( trait || {} ).__trait === 'param' );
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determines if the provided value references an argument trait
|
|
|
|
*
|
|
|
|
* An argument trait is a configured parameter trait.
|
|
|
|
*
|
|
|
|
* @param {*} trait value to check
|
|
|
|
* @return {boolean} whether the provided value references an arg trait
|
|
|
|
*/
|
|
|
|
Trait.isArgumentTrait = function( trait )
|
|
|
|
{
|
|
|
|
return !!( ( trait || {} ).__trait === 'arg' );
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2014-01-23 00:34:15 -05:00
|
|
|
/**
|
|
|
|
* Create a concrete class from the abstract trait class
|
|
|
|
*
|
|
|
|
* This class is the one that will be instantiated by classes that mix in
|
|
|
|
* the trait.
|
|
|
|
*
|
|
|
|
* @param {AbstractClass} acls abstract trait class
|
|
|
|
*
|
|
|
|
* @return {Class} concrete trait class for instantiation
|
|
|
|
*/
|
|
|
|
function createConcrete( acls )
|
|
|
|
{
|
2014-01-26 03:26:15 -05:00
|
|
|
// start by providing a concrete implementation for our dummy method and
|
|
|
|
// a constructor that accepts the protected member object of the
|
|
|
|
// containing class
|
2014-01-23 00:34:15 -05:00
|
|
|
var dfn = {
|
2014-03-04 00:19:39 -05:00
|
|
|
// protected member object (we define this as protected so that the
|
|
|
|
// parent ACLS has access to it (!), which is not prohibited since
|
|
|
|
// JS does not provide a strict typing mechanism...this is a kluge)
|
Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until
now, mixins could not be stacked without some ugly errors. This also allows
mixins to be stacked atop of themselves, duplicating their effect. This
would naturally have limited use, but it's there.
This differs slightly from Scala. For example, consider this ease.js mixin:
C.use( T ).use( T )()
This is perfectly valid---it has the effect of stacking T twice. In reality,
ease.js is doing this:
- C' = C.use( T );
- new C'.use( T );
That is, it each call to `use' creates another class with T mixed in.
Scala, on the other hand, complains in this situation:
new C with T with T
will produce an error stating that "trait T is inherited twice". You can
work around this, however, by doing this:
class Ca extends T
new Ca with T
In fact, this is precisely what ease.js is doing, as mentioned above; the
"use.use" syntax is merely shorthand for this:
new C.use( T ).extend( {} ).use( T )
Just keep that in mind.
2014-03-06 01:51:49 -05:00
|
|
|
// and target supertype---that is, what __super calls should
|
|
|
|
// referene
|
2014-03-04 00:19:39 -05:00
|
|
|
'protected ___$$pmo$$': null,
|
Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until
now, mixins could not be stacked without some ugly errors. This also allows
mixins to be stacked atop of themselves, duplicating their effect. This
would naturally have limited use, but it's there.
This differs slightly from Scala. For example, consider this ease.js mixin:
C.use( T ).use( T )()
This is perfectly valid---it has the effect of stacking T twice. In reality,
ease.js is doing this:
- C' = C.use( T );
- new C'.use( T );
That is, it each call to `use' creates another class with T mixed in.
Scala, on the other hand, complains in this situation:
new C with T with T
will produce an error stating that "trait T is inherited twice". You can
work around this, however, by doing this:
class Ca extends T
new Ca with T
In fact, this is precisely what ease.js is doing, as mentioned above; the
"use.use" syntax is merely shorthand for this:
new C.use( T ).extend( {} ).use( T )
Just keep that in mind.
2014-03-06 01:51:49 -05:00
|
|
|
'protected ___$$super$$': null,
|
|
|
|
__construct: function( base, pmo )
|
2014-01-26 03:26:15 -05:00
|
|
|
{
|
Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until
now, mixins could not be stacked without some ugly errors. This also allows
mixins to be stacked atop of themselves, duplicating their effect. This
would naturally have limited use, but it's there.
This differs slightly from Scala. For example, consider this ease.js mixin:
C.use( T ).use( T )()
This is perfectly valid---it has the effect of stacking T twice. In reality,
ease.js is doing this:
- C' = C.use( T );
- new C'.use( T );
That is, it each call to `use' creates another class with T mixed in.
Scala, on the other hand, complains in this situation:
new C with T with T
will produce an error stating that "trait T is inherited twice". You can
work around this, however, by doing this:
class Ca extends T
new Ca with T
In fact, this is precisely what ease.js is doing, as mentioned above; the
"use.use" syntax is merely shorthand for this:
new C.use( T ).extend( {} ).use( T )
Just keep that in mind.
2014-03-06 01:51:49 -05:00
|
|
|
this.___$$super$$ = base;
|
|
|
|
this.___$$pmo$$ = pmo;
|
2014-01-26 03:26:15 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
// mainly for debugging; should really never see this.
|
|
|
|
__name: '#ConcreteTrait#',
|
2014-01-23 00:34:15 -05:00
|
|
|
};
|
|
|
|
|
2014-01-26 03:26:15 -05:00
|
|
|
// every abstract method should be overridden with a proxy to the
|
|
|
|
// protected member object that will be passed in via the ctor
|
|
|
|
var amethods = ClassBuilder.getMeta( acls ).abstractMethods;
|
|
|
|
for ( var f in amethods )
|
|
|
|
{
|
|
|
|
// TODO: would be nice if this check could be for '___'; need to
|
|
|
|
// replace amethods.__length with something else, then
|
|
|
|
if ( !( Object.hasOwnProperty.call( amethods, f ) )
|
|
|
|
|| ( f.substr( 0, 2 ) === '__' )
|
|
|
|
)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2014-01-26 03:30:52 -05:00
|
|
|
// we know that if it's not public, then it must be protected
|
|
|
|
var vis = ( acls.___$$methods$$['public'][ f ] !== undefined )
|
|
|
|
? 'public'
|
|
|
|
: 'protected';
|
|
|
|
|
|
|
|
// setting the correct visibility modified is important to prevent
|
|
|
|
// visibility de-escalation errors if a protected concrete method is
|
|
|
|
// provided
|
|
|
|
dfn[ vis + ' proxy ' + f ] = '___$$pmo$$';
|
2014-01-26 03:26:15 -05:00
|
|
|
}
|
2014-01-23 00:34:15 -05:00
|
|
|
|
2014-02-03 23:54:21 -05:00
|
|
|
// virtual methods need to be handled with care to ensure that we invoke
|
|
|
|
// any overrides
|
|
|
|
createVirtProxy( acls, dfn );
|
|
|
|
|
2014-01-23 00:34:15 -05:00
|
|
|
return acls.extend( dfn );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-02-03 23:54:21 -05:00
|
|
|
/**
|
|
|
|
* Create virtual method proxies for all virtual members
|
|
|
|
*
|
|
|
|
* Virtual methods are a bit of hassle with traits: we are in a situation
|
|
|
|
* where we do not know at the time that the trait is created whether or not
|
|
|
|
* the virtual method has been overridden, since the class that the trait is
|
|
|
|
* mixed into may do the overriding. Therefore, we must check if an override
|
|
|
|
* has occured *when the method is invoked*; there is room for optimization
|
|
|
|
* there (by making such a determination at the time of mixin), but we'll
|
|
|
|
* leave that for later.
|
|
|
|
*
|
|
|
|
* @param {AbstractClass} acls abstract trait class
|
|
|
|
* @param {Object} dfn destination definition object
|
|
|
|
*
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function createVirtProxy( acls, dfn )
|
|
|
|
{
|
|
|
|
var vmembers = ClassBuilder.getMeta( acls ).virtualMembers;
|
|
|
|
|
|
|
|
// f = `field'
|
|
|
|
for ( var f in vmembers )
|
|
|
|
{
|
|
|
|
var vis = ( acls.___$$methods$$['public'][ f ] !== undefined )
|
|
|
|
? 'public'
|
|
|
|
: 'protected';
|
|
|
|
|
2014-05-02 02:47:42 -04:00
|
|
|
var srcmethod = acls.___$$methods$$[ vis ][ f ],
|
|
|
|
plen = srcmethod.__length;
|
2014-05-02 00:17:53 -04:00
|
|
|
|
2014-02-19 00:26:57 -05:00
|
|
|
// this is the aforementioned proxy method; see the docblock for
|
|
|
|
// more information
|
2014-05-02 02:47:42 -04:00
|
|
|
dfn[ vis + ' virtual override ' + f ] = ( function( f )
|
2014-02-03 23:54:21 -05:00
|
|
|
{
|
2014-05-02 00:17:53 -04:00
|
|
|
var retf = function()
|
2014-02-03 23:54:21 -05:00
|
|
|
{
|
|
|
|
var pmo = this.___$$pmo$$,
|
2014-02-19 00:26:57 -05:00
|
|
|
o = pmo[ f ];
|
|
|
|
|
|
|
|
// proxy to virtual override from the class we are mixed
|
|
|
|
// into, if found; otherwise, proxy to our supertype
|
|
|
|
return ( o )
|
|
|
|
? o.apply( pmo, arguments )
|
2014-02-03 23:54:21 -05:00
|
|
|
: this.__super.apply( this, arguments );
|
2014-02-19 00:26:57 -05:00
|
|
|
};
|
2014-05-02 00:17:53 -04:00
|
|
|
|
|
|
|
retf.__length = plen;
|
|
|
|
return retf;
|
2014-02-19 00:26:57 -05:00
|
|
|
} )( f );
|
|
|
|
|
|
|
|
// this guy bypasses the above virtual override check, which is
|
|
|
|
// necessary in certain cases to prevent infinte recursion
|
2014-05-02 02:47:42 -04:00
|
|
|
dfn[ vis + ' virtual __$$' + f ] = ( function( method )
|
2014-02-19 00:26:57 -05:00
|
|
|
{
|
2014-05-02 00:17:53 -04:00
|
|
|
var retf = function()
|
2014-02-19 00:26:57 -05:00
|
|
|
{
|
2014-05-02 02:47:42 -04:00
|
|
|
return method.apply( this, arguments );
|
2014-02-19 00:26:57 -05:00
|
|
|
};
|
2014-05-02 00:17:53 -04:00
|
|
|
|
|
|
|
retf.__length = plen;
|
|
|
|
return retf;
|
2014-05-02 02:47:42 -04:00
|
|
|
} )( srcmethod );
|
2014-02-03 23:54:21 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-01-21 22:57:04 -05:00
|
|
|
/**
|
|
|
|
* Mix trait into the given definition
|
|
|
|
*
|
2014-02-16 23:23:11 -05:00
|
|
|
* The original object DFN is modified; it is not cloned. TC should be
|
|
|
|
* initialized to an empty array; it is used to store context data for
|
|
|
|
* mixing in traits and will be encapsulated within a ctor closure (and thus
|
|
|
|
* will remain in memory).
|
2014-01-21 22:57:04 -05:00
|
|
|
*
|
|
|
|
* @param {Trait} trait trait to mix in
|
|
|
|
* @param {Object} dfn definition object to merge into
|
2014-02-16 23:23:11 -05:00
|
|
|
* @param {Array} tc trait class context
|
2015-05-27 23:03:11 -04:00
|
|
|
* @param {Class} base target supertype
|
2014-01-21 22:57:04 -05:00
|
|
|
*
|
|
|
|
* @return {Object} dfn
|
|
|
|
*/
|
Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until
now, mixins could not be stacked without some ugly errors. This also allows
mixins to be stacked atop of themselves, duplicating their effect. This
would naturally have limited use, but it's there.
This differs slightly from Scala. For example, consider this ease.js mixin:
C.use( T ).use( T )()
This is perfectly valid---it has the effect of stacking T twice. In reality,
ease.js is doing this:
- C' = C.use( T );
- new C'.use( T );
That is, it each call to `use' creates another class with T mixed in.
Scala, on the other hand, complains in this situation:
new C with T with T
will produce an error stating that "trait T is inherited twice". You can
work around this, however, by doing this:
class Ca extends T
new Ca with T
In fact, this is precisely what ease.js is doing, as mentioned above; the
"use.use" syntax is merely shorthand for this:
new C.use( T ).extend( {} ).use( T )
Just keep that in mind.
2014-03-06 01:51:49 -05:00
|
|
|
function mixin( trait, dfn, tc, base )
|
2014-01-21 22:57:04 -05:00
|
|
|
{
|
2015-05-27 23:03:11 -04:00
|
|
|
_validateMixin( base, trait );
|
|
|
|
|
2014-01-23 00:34:15 -05:00
|
|
|
// the abstract class hidden within the trait
|
2014-02-28 23:55:24 -05:00
|
|
|
var acls = trait.__acls;
|
2014-01-23 00:34:15 -05:00
|
|
|
|
|
|
|
// retrieve the private member name that will contain this trait object
|
Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until
now, mixins could not be stacked without some ugly errors. This also allows
mixins to be stacked atop of themselves, duplicating their effect. This
would naturally have limited use, but it's there.
This differs slightly from Scala. For example, consider this ease.js mixin:
C.use( T ).use( T )()
This is perfectly valid---it has the effect of stacking T twice. In reality,
ease.js is doing this:
- C' = C.use( T );
- new C'.use( T );
That is, it each call to `use' creates another class with T mixed in.
Scala, on the other hand, complains in this situation:
new C with T with T
will produce an error stating that "trait T is inherited twice". You can
work around this, however, by doing this:
class Ca extends T
new Ca with T
In fact, this is precisely what ease.js is doing, as mentioned above; the
"use.use" syntax is merely shorthand for this:
new C.use( T ).extend( {} ).use( T )
Just keep that in mind.
2014-03-06 01:51:49 -05:00
|
|
|
var iname = addTraitInst( trait, dfn, tc, base );
|
2014-01-23 00:34:15 -05:00
|
|
|
|
2014-06-05 23:35:03 -04:00
|
|
|
// TODO: this should not be necessary for dfn metadata
|
|
|
|
dfn[ 'weak virtual ___$$ctor$pre$$' ] = _fvoid;
|
|
|
|
dfn[ 'weak virtual ___$$ctor$post$$' ] = _fvoid;
|
|
|
|
|
|
|
|
// TODO: this is a kluge; generalize and move
|
|
|
|
// this ensures __construct is called before __mixin when mixing into
|
|
|
|
// the base class
|
|
|
|
if ( base === ClassBuilder.ClassBase )
|
|
|
|
{
|
|
|
|
dfn[ 'virtual override ___$$ctor$post$$' ] = _tctorApply;
|
|
|
|
dfn[ 'virtual override ___$$ctor$pre$$' ] = _fvoid;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
dfn[ 'virtual override ___$$ctor$post$$' ] = _fvoid;
|
|
|
|
dfn[ 'virtual override ___$$ctor$pre$$' ] = _tctorApply;
|
|
|
|
}
|
|
|
|
|
2014-02-28 23:55:24 -05:00
|
|
|
// recursively mix in trait's underlying abstract class (ensuring that
|
|
|
|
// anything that the trait inherits from is also properly mixed in)
|
|
|
|
mixinCls( acls, dfn, iname );
|
|
|
|
return dfn;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Recursively mix in class methods
|
|
|
|
*
|
|
|
|
* If CLS extends another class, its methods will be recursively processed
|
|
|
|
* to ensure that the entire prototype chain is properly proxied.
|
|
|
|
*
|
|
|
|
* For an explanation of the iname parameter, see the mixin function.
|
|
|
|
*
|
|
|
|
* @param {Class} cls class to mix in
|
|
|
|
* @param {Object} dfn definition object to merge into
|
|
|
|
* @param {string} iname trait object private member instance name
|
|
|
|
*
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
Support for trait class supertype overrides
Traits can now override methods of their class supertypes. Previously, in
order to override a method of some class `C` by mixing in some trait `T`,
both had to implement a common interface. This had two notable downsides:
1. A trait that was only compatible with details of `C` could only work
with `C#M` if it implemented an interface `I` that declared `I#M`.
This required the author of `C` to create interfaces where they would
otherwise not be necessary.
2. As a corollary of #1---since methods of interfaces must be public, it
was not possible for `T` to override any protected method of `C`; this
meant that `C` would have to declare such methods public, which may
break encapsulation or expose unnecessary concerns to the outside
world.
Until documentation is available---hopefully in the near future---the test
cases provide detailed documentation of the behavior. Stackable traits work
as you would expect:
```javascript
var C = Class(
{
'virtual foo': function()
{
return 'C';
},
} );
var T1 = Trait.extend( C,
{
'virtual abstract override foo': function()
{
return 'T1' + this.__super();
},
} );
var T2 = Trait.extend( C,
{
'virtual abstract override foo': function()
{
return 'T2' + this.__super();
},
} );
C.use( T1 )
.use( T1 )
.use( T2 )
.use( T2 )
.foo();
// result: "T2T2T1T1C"
```
If the `override` keyword is used without `abstract`, then the super method
is statically bound to the supertype, rather than being resolved at runtime:
```javascript
var C = Class(
{
'virtual foo': function()
{
return 'C';
},
} );
var T1 = Trait.extend( C,
{
'virtual abstract override foo': function()
{
return 'T1' + this.__super();
},
} );
var T2 = Trait.extend( C,
{
// static override
'virtual override foo': function()
{
return 'T2' + this.__super();
},
} );
C.use( T1 )
.use( T1 )
.use( T2 )
.use( T2 )
.foo();
// result: "T2C"
```
This latter form should be discouraged in most circumstances (as it prevents
stackable traits), but the behavior is consistent with the rest of the
system.
Happy hacking.
2015-10-22 23:44:28 -04:00
|
|
|
function mixinCls( cls, dfn, iname, inparent )
|
2014-02-28 23:55:24 -05:00
|
|
|
{
|
|
|
|
var methods = cls.___$$methods$$;
|
|
|
|
|
Support for trait class supertype overrides
Traits can now override methods of their class supertypes. Previously, in
order to override a method of some class `C` by mixing in some trait `T`,
both had to implement a common interface. This had two notable downsides:
1. A trait that was only compatible with details of `C` could only work
with `C#M` if it implemented an interface `I` that declared `I#M`.
This required the author of `C` to create interfaces where they would
otherwise not be necessary.
2. As a corollary of #1---since methods of interfaces must be public, it
was not possible for `T` to override any protected method of `C`; this
meant that `C` would have to declare such methods public, which may
break encapsulation or expose unnecessary concerns to the outside
world.
Until documentation is available---hopefully in the near future---the test
cases provide detailed documentation of the behavior. Stackable traits work
as you would expect:
```javascript
var C = Class(
{
'virtual foo': function()
{
return 'C';
},
} );
var T1 = Trait.extend( C,
{
'virtual abstract override foo': function()
{
return 'T1' + this.__super();
},
} );
var T2 = Trait.extend( C,
{
'virtual abstract override foo': function()
{
return 'T2' + this.__super();
},
} );
C.use( T1 )
.use( T1 )
.use( T2 )
.use( T2 )
.foo();
// result: "T2T2T1T1C"
```
If the `override` keyword is used without `abstract`, then the super method
is statically bound to the supertype, rather than being resolved at runtime:
```javascript
var C = Class(
{
'virtual foo': function()
{
return 'C';
},
} );
var T1 = Trait.extend( C,
{
'virtual abstract override foo': function()
{
return 'T1' + this.__super();
},
} );
var T2 = Trait.extend( C,
{
// static override
'virtual override foo': function()
{
return 'T2' + this.__super();
},
} );
C.use( T1 )
.use( T1 )
.use( T2 )
.use( T2 )
.foo();
// result: "T2C"
```
This latter form should be discouraged in most circumstances (as it prevents
stackable traits), but the behavior is consistent with the rest of the
system.
Happy hacking.
2015-10-22 23:44:28 -04:00
|
|
|
mixMethods( methods['public'], dfn, 'public', iname, inparent );
|
|
|
|
mixMethods( methods['protected'], dfn, 'protected', iname, inparent );
|
2014-01-23 01:15:53 -05:00
|
|
|
|
2014-02-28 23:55:24 -05:00
|
|
|
// if this class inherits from another class that is *not* the base
|
|
|
|
// class, recursively process its methods; otherwise, we will have
|
|
|
|
// incompletely proxied the prototype chain
|
|
|
|
var parent = methods['public'].___$$parent$$;
|
|
|
|
if ( parent && ( parent.constructor !== ClassBuilder.ClassBase ) )
|
|
|
|
{
|
Support for trait class supertype overrides
Traits can now override methods of their class supertypes. Previously, in
order to override a method of some class `C` by mixing in some trait `T`,
both had to implement a common interface. This had two notable downsides:
1. A trait that was only compatible with details of `C` could only work
with `C#M` if it implemented an interface `I` that declared `I#M`.
This required the author of `C` to create interfaces where they would
otherwise not be necessary.
2. As a corollary of #1---since methods of interfaces must be public, it
was not possible for `T` to override any protected method of `C`; this
meant that `C` would have to declare such methods public, which may
break encapsulation or expose unnecessary concerns to the outside
world.
Until documentation is available---hopefully in the near future---the test
cases provide detailed documentation of the behavior. Stackable traits work
as you would expect:
```javascript
var C = Class(
{
'virtual foo': function()
{
return 'C';
},
} );
var T1 = Trait.extend( C,
{
'virtual abstract override foo': function()
{
return 'T1' + this.__super();
},
} );
var T2 = Trait.extend( C,
{
'virtual abstract override foo': function()
{
return 'T2' + this.__super();
},
} );
C.use( T1 )
.use( T1 )
.use( T2 )
.use( T2 )
.foo();
// result: "T2T2T1T1C"
```
If the `override` keyword is used without `abstract`, then the super method
is statically bound to the supertype, rather than being resolved at runtime:
```javascript
var C = Class(
{
'virtual foo': function()
{
return 'C';
},
} );
var T1 = Trait.extend( C,
{
'virtual abstract override foo': function()
{
return 'T1' + this.__super();
},
} );
var T2 = Trait.extend( C,
{
// static override
'virtual override foo': function()
{
return 'T2' + this.__super();
},
} );
C.use( T1 )
.use( T1 )
.use( T2 )
.use( T2 )
.foo();
// result: "T2C"
```
This latter form should be discouraged in most circumstances (as it prevents
stackable traits), but the behavior is consistent with the rest of the
system.
Happy hacking.
2015-10-22 23:44:28 -04:00
|
|
|
mixinCls( parent.constructor, dfn, iname, true );
|
2014-02-28 23:55:24 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Mix implemented types into destination object
|
|
|
|
*
|
|
|
|
* The provided destination object will ideally be the `implemented' array
|
|
|
|
* of the destination class's meta object.
|
|
|
|
*
|
|
|
|
* @param {Class} cls source class
|
|
|
|
* @param {Object} dest_meta destination object to copy into
|
|
|
|
*
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function mixinImpl( cls, dest_meta )
|
|
|
|
{
|
|
|
|
var impl = ClassBuilder.getMeta( cls ).implemented || [],
|
|
|
|
i = impl.length;
|
|
|
|
|
|
|
|
while ( i-- )
|
|
|
|
{
|
|
|
|
// TODO: this could potentially result in duplicates
|
|
|
|
dest_meta.push( impl[ i ] );
|
|
|
|
}
|
2014-01-23 01:15:53 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Mix methods from SRC into DEST using proxies
|
|
|
|
*
|
|
|
|
* @param {Object} src visibility object to scavenge from
|
|
|
|
* @param {Object} dest destination definition object
|
|
|
|
* @param {string} vis visibility modifier
|
2014-02-19 00:26:57 -05:00
|
|
|
* @param {string} iname proxy destination (trait instance)
|
2014-01-23 01:15:53 -05:00
|
|
|
*
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
Support for trait class supertype overrides
Traits can now override methods of their class supertypes. Previously, in
order to override a method of some class `C` by mixing in some trait `T`,
both had to implement a common interface. This had two notable downsides:
1. A trait that was only compatible with details of `C` could only work
with `C#M` if it implemented an interface `I` that declared `I#M`.
This required the author of `C` to create interfaces where they would
otherwise not be necessary.
2. As a corollary of #1---since methods of interfaces must be public, it
was not possible for `T` to override any protected method of `C`; this
meant that `C` would have to declare such methods public, which may
break encapsulation or expose unnecessary concerns to the outside
world.
Until documentation is available---hopefully in the near future---the test
cases provide detailed documentation of the behavior. Stackable traits work
as you would expect:
```javascript
var C = Class(
{
'virtual foo': function()
{
return 'C';
},
} );
var T1 = Trait.extend( C,
{
'virtual abstract override foo': function()
{
return 'T1' + this.__super();
},
} );
var T2 = Trait.extend( C,
{
'virtual abstract override foo': function()
{
return 'T2' + this.__super();
},
} );
C.use( T1 )
.use( T1 )
.use( T2 )
.use( T2 )
.foo();
// result: "T2T2T1T1C"
```
If the `override` keyword is used without `abstract`, then the super method
is statically bound to the supertype, rather than being resolved at runtime:
```javascript
var C = Class(
{
'virtual foo': function()
{
return 'C';
},
} );
var T1 = Trait.extend( C,
{
'virtual abstract override foo': function()
{
return 'T1' + this.__super();
},
} );
var T2 = Trait.extend( C,
{
// static override
'virtual override foo': function()
{
return 'T2' + this.__super();
},
} );
C.use( T1 )
.use( T1 )
.use( T2 )
.use( T2 )
.foo();
// result: "T2C"
```
This latter form should be discouraged in most circumstances (as it prevents
stackable traits), but the behavior is consistent with the rest of the
system.
Happy hacking.
2015-10-22 23:44:28 -04:00
|
|
|
function mixMethods( src, dest, vis, iname, inparent )
|
2014-01-23 01:15:53 -05:00
|
|
|
{
|
|
|
|
for ( var f in src )
|
2014-01-21 22:57:04 -05:00
|
|
|
{
|
2014-01-23 01:15:53 -05:00
|
|
|
if ( !( Object.hasOwnProperty.call( src, f ) ) )
|
2014-01-22 01:11:17 -05:00
|
|
|
{
|
2014-01-23 00:34:15 -05:00
|
|
|
continue;
|
2014-01-22 01:11:17 -05:00
|
|
|
}
|
2014-01-23 00:34:15 -05:00
|
|
|
|
2014-06-04 00:37:08 -04:00
|
|
|
// TODO: generalize
|
|
|
|
// __mixin is exclusive to the trait (private-ish, but can be
|
|
|
|
// invoked publically internally)
|
|
|
|
if ( f === '__mixin' )
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2014-01-23 00:34:15 -05:00
|
|
|
// TODO: this is a kluge; we'll use proper reflection eventually,
|
2014-01-23 01:15:53 -05:00
|
|
|
// but for now, this is how we determine if this is an actual method
|
|
|
|
// vs. something that just happens to be on the visibility object
|
2015-10-26 22:17:28 -04:00
|
|
|
if ( !( src[ f ] && src[ f ].___$$keywords$$ ) )
|
2014-01-22 01:11:17 -05:00
|
|
|
{
|
2014-01-23 00:34:15 -05:00
|
|
|
continue;
|
2014-01-22 01:11:17 -05:00
|
|
|
}
|
|
|
|
|
2015-10-26 22:34:43 -04:00
|
|
|
var keywords = src[ f ].___$$keywords$$;
|
|
|
|
|
|
|
|
// TODO: This is a kluge to handle ES3 fallbacks, which will cause
|
|
|
|
// protected members to appear on the public prototype. A more
|
|
|
|
// elegant solution is to automatically add the public keyword when
|
|
|
|
// the class is built, so we can just check if keywords[vis] exists.
|
|
|
|
if ( ( vis === 'public' ) && keywords[ 'protected' ] )
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
vis = keywords[ 'protected' ] ? 'protected' : 'public';
|
2014-01-26 03:30:52 -05:00
|
|
|
|
2014-01-26 03:26:15 -05:00
|
|
|
// if abstract, then we are expected to provide the implementation;
|
|
|
|
// otherwise, we proxy to the trait's implementation
|
2014-03-04 00:19:39 -05:00
|
|
|
if ( keywords[ 'abstract' ] && !( keywords[ 'override' ] ) )
|
2014-01-23 01:00:16 -05:00
|
|
|
{
|
2014-01-26 03:26:15 -05:00
|
|
|
// copy the abstract definition (N.B. this does not copy the
|
2014-01-26 03:30:52 -05:00
|
|
|
// param names, since that is not [yet] important); the
|
Support for trait class supertype overrides
Traits can now override methods of their class supertypes. Previously, in
order to override a method of some class `C` by mixing in some trait `T`,
both had to implement a common interface. This had two notable downsides:
1. A trait that was only compatible with details of `C` could only work
with `C#M` if it implemented an interface `I` that declared `I#M`.
This required the author of `C` to create interfaces where they would
otherwise not be necessary.
2. As a corollary of #1---since methods of interfaces must be public, it
was not possible for `T` to override any protected method of `C`; this
meant that `C` would have to declare such methods public, which may
break encapsulation or expose unnecessary concerns to the outside
world.
Until documentation is available---hopefully in the near future---the test
cases provide detailed documentation of the behavior. Stackable traits work
as you would expect:
```javascript
var C = Class(
{
'virtual foo': function()
{
return 'C';
},
} );
var T1 = Trait.extend( C,
{
'virtual abstract override foo': function()
{
return 'T1' + this.__super();
},
} );
var T2 = Trait.extend( C,
{
'virtual abstract override foo': function()
{
return 'T2' + this.__super();
},
} );
C.use( T1 )
.use( T1 )
.use( T2 )
.use( T2 )
.foo();
// result: "T2T2T1T1C"
```
If the `override` keyword is used without `abstract`, then the super method
is statically bound to the supertype, rather than being resolved at runtime:
```javascript
var C = Class(
{
'virtual foo': function()
{
return 'C';
},
} );
var T1 = Trait.extend( C,
{
'virtual abstract override foo': function()
{
return 'T1' + this.__super();
},
} );
var T2 = Trait.extend( C,
{
// static override
'virtual override foo': function()
{
return 'T2' + this.__super();
},
} );
C.use( T1 )
.use( T1 )
.use( T2 )
.use( T2 )
.foo();
// result: "T2C"
```
This latter form should be discouraged in most circumstances (as it prevents
stackable traits), but the behavior is consistent with the rest of the
system.
Happy hacking.
2015-10-22 23:44:28 -04:00
|
|
|
// visibility modifier is important to prevent de-escalation
|
2014-01-26 03:30:52 -05:00
|
|
|
// errors on override
|
|
|
|
dest[ vis + ' weak abstract ' + f ] = src[ f ].definition;
|
2014-01-26 03:26:15 -05:00
|
|
|
}
|
Support for trait class supertype overrides
Traits can now override methods of their class supertypes. Previously, in
order to override a method of some class `C` by mixing in some trait `T`,
both had to implement a common interface. This had two notable downsides:
1. A trait that was only compatible with details of `C` could only work
with `C#M` if it implemented an interface `I` that declared `I#M`.
This required the author of `C` to create interfaces where they would
otherwise not be necessary.
2. As a corollary of #1---since methods of interfaces must be public, it
was not possible for `T` to override any protected method of `C`; this
meant that `C` would have to declare such methods public, which may
break encapsulation or expose unnecessary concerns to the outside
world.
Until documentation is available---hopefully in the near future---the test
cases provide detailed documentation of the behavior. Stackable traits work
as you would expect:
```javascript
var C = Class(
{
'virtual foo': function()
{
return 'C';
},
} );
var T1 = Trait.extend( C,
{
'virtual abstract override foo': function()
{
return 'T1' + this.__super();
},
} );
var T2 = Trait.extend( C,
{
'virtual abstract override foo': function()
{
return 'T2' + this.__super();
},
} );
C.use( T1 )
.use( T1 )
.use( T2 )
.use( T2 )
.foo();
// result: "T2T2T1T1C"
```
If the `override` keyword is used without `abstract`, then the super method
is statically bound to the supertype, rather than being resolved at runtime:
```javascript
var C = Class(
{
'virtual foo': function()
{
return 'C';
},
} );
var T1 = Trait.extend( C,
{
'virtual abstract override foo': function()
{
return 'T1' + this.__super();
},
} );
var T2 = Trait.extend( C,
{
// static override
'virtual override foo': function()
{
return 'T2' + this.__super();
},
} );
C.use( T1 )
.use( T1 )
.use( T2 )
.use( T2 )
.foo();
// result: "T2C"
```
This latter form should be discouraged in most circumstances (as it prevents
stackable traits), but the behavior is consistent with the rest of the
system.
Happy hacking.
2015-10-22 23:44:28 -04:00
|
|
|
else if ( inparent && !keywords[ 'abstract' ] )
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2014-01-26 03:26:15 -05:00
|
|
|
else
|
|
|
|
{
|
2014-03-04 00:19:39 -05:00
|
|
|
var vk = keywords['virtual'],
|
2014-03-06 00:57:24 -05:00
|
|
|
virt = vk ? 'virtual ' : '',
|
2014-03-04 00:19:39 -05:00
|
|
|
ovr = ( keywords['override'] ) ? 'override ' : '',
|
|
|
|
pname = ( vk ? '' : 'proxy ' ) + virt + ovr + vis + ' ' + f;
|
2014-01-26 03:26:15 -05:00
|
|
|
|
|
|
|
// if we have already set up a proxy for a field of this name,
|
|
|
|
// then multiple traits have defined the same concrete member
|
|
|
|
if ( dest[ pname ] !== undefined )
|
|
|
|
{
|
|
|
|
// TODO: between what traits?
|
|
|
|
throw Error( "Trait member conflict: `" + f + "'" );
|
|
|
|
}
|
|
|
|
|
2014-02-19 00:26:57 -05:00
|
|
|
// if non-virtual, a normal proxy should do
|
2014-03-04 00:19:39 -05:00
|
|
|
if ( !( keywords[ 'virtual' ] ) )
|
2014-02-19 00:26:57 -05:00
|
|
|
{
|
|
|
|
dest[ pname ] = iname;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2014-01-26 03:26:15 -05:00
|
|
|
// proxy this method to what will be the encapsulated trait
|
2014-02-19 00:26:57 -05:00
|
|
|
// object (note that we do not use the proxy keyword here
|
|
|
|
// beacuse we are not proxying to a method of the same name)
|
|
|
|
dest[ pname ] = ( function( f )
|
|
|
|
{
|
2014-05-02 00:17:53 -04:00
|
|
|
var retf = function()
|
2014-02-19 00:26:57 -05:00
|
|
|
{
|
|
|
|
var pdest = this[ iname ];
|
|
|
|
|
|
|
|
// invoke the direct method on the trait instance; this
|
|
|
|
// bypasses the virtual override check on the trait
|
|
|
|
// method to ensure that it is invoked without
|
|
|
|
// additional overhead or confusion
|
|
|
|
var ret = pdest[ '__$$' + f ].apply( pdest, arguments );
|
|
|
|
|
|
|
|
// if the trait returns itself, return us instead
|
2014-02-20 23:17:04 -05:00
|
|
|
return ( ret === pdest )
|
2014-02-19 00:26:57 -05:00
|
|
|
? this
|
|
|
|
: ret;
|
|
|
|
};
|
2014-05-02 00:17:53 -04:00
|
|
|
|
|
|
|
retf.__length = src[ f ].__length;
|
|
|
|
return retf;
|
2014-02-19 00:26:57 -05:00
|
|
|
} )( f );
|
2014-01-23 01:00:16 -05:00
|
|
|
}
|
2014-01-21 22:57:04 -05:00
|
|
|
}
|
2014-01-23 00:34:15 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add concrete trait class to a class instantion list
|
|
|
|
*
|
|
|
|
* This list---which will be created if it does not already exist---will be
|
|
|
|
* used upon instantiation of the class consuming DFN to instantiate the
|
|
|
|
* concrete trait classes.
|
|
|
|
*
|
|
|
|
* Here, `tc' and `to' are understood to be, respectively, ``trait class''
|
|
|
|
* and ``trait object''.
|
|
|
|
*
|
Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until
now, mixins could not be stacked without some ugly errors. This also allows
mixins to be stacked atop of themselves, duplicating their effect. This
would naturally have limited use, but it's there.
This differs slightly from Scala. For example, consider this ease.js mixin:
C.use( T ).use( T )()
This is perfectly valid---it has the effect of stacking T twice. In reality,
ease.js is doing this:
- C' = C.use( T );
- new C'.use( T );
That is, it each call to `use' creates another class with T mixed in.
Scala, on the other hand, complains in this situation:
new C with T with T
will produce an error stating that "trait T is inherited twice". You can
work around this, however, by doing this:
class Ca extends T
new Ca with T
In fact, this is precisely what ease.js is doing, as mentioned above; the
"use.use" syntax is merely shorthand for this:
new C.use( T ).extend( {} ).use( T )
Just keep that in mind.
2014-03-06 01:51:49 -05:00
|
|
|
* @param {Class} T trait
|
|
|
|
* @param {Object} dfn definition object of class being mixed into
|
|
|
|
* @param {Array} tc trait class object
|
Support for trait class supertype overrides
Traits can now override methods of their class supertypes. Previously, in
order to override a method of some class `C` by mixing in some trait `T`,
both had to implement a common interface. This had two notable downsides:
1. A trait that was only compatible with details of `C` could only work
with `C#M` if it implemented an interface `I` that declared `I#M`.
This required the author of `C` to create interfaces where they would
otherwise not be necessary.
2. As a corollary of #1---since methods of interfaces must be public, it
was not possible for `T` to override any protected method of `C`; this
meant that `C` would have to declare such methods public, which may
break encapsulation or expose unnecessary concerns to the outside
world.
Until documentation is available---hopefully in the near future---the test
cases provide detailed documentation of the behavior. Stackable traits work
as you would expect:
```javascript
var C = Class(
{
'virtual foo': function()
{
return 'C';
},
} );
var T1 = Trait.extend( C,
{
'virtual abstract override foo': function()
{
return 'T1' + this.__super();
},
} );
var T2 = Trait.extend( C,
{
'virtual abstract override foo': function()
{
return 'T2' + this.__super();
},
} );
C.use( T1 )
.use( T1 )
.use( T2 )
.use( T2 )
.foo();
// result: "T2T2T1T1C"
```
If the `override` keyword is used without `abstract`, then the super method
is statically bound to the supertype, rather than being resolved at runtime:
```javascript
var C = Class(
{
'virtual foo': function()
{
return 'C';
},
} );
var T1 = Trait.extend( C,
{
'virtual abstract override foo': function()
{
return 'T1' + this.__super();
},
} );
var T2 = Trait.extend( C,
{
// static override
'virtual override foo': function()
{
return 'T2' + this.__super();
},
} );
C.use( T1 )
.use( T1 )
.use( T2 )
.use( T2 )
.foo();
// result: "T2C"
```
This latter form should be discouraged in most circumstances (as it prevents
stackable traits), but the behavior is consistent with the rest of the
system.
Happy hacking.
2015-10-22 23:44:28 -04:00
|
|
|
* @param {Class} base target supertype
|
2014-01-23 00:34:15 -05:00
|
|
|
*
|
|
|
|
* @return {string} private member into which C instance shall be stored
|
|
|
|
*/
|
Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until
now, mixins could not be stacked without some ugly errors. This also allows
mixins to be stacked atop of themselves, duplicating their effect. This
would naturally have limited use, but it's there.
This differs slightly from Scala. For example, consider this ease.js mixin:
C.use( T ).use( T )()
This is perfectly valid---it has the effect of stacking T twice. In reality,
ease.js is doing this:
- C' = C.use( T );
- new C'.use( T );
That is, it each call to `use' creates another class with T mixed in.
Scala, on the other hand, complains in this situation:
new C with T with T
will produce an error stating that "trait T is inherited twice". You can
work around this, however, by doing this:
class Ca extends T
new Ca with T
In fact, this is precisely what ease.js is doing, as mentioned above; the
"use.use" syntax is merely shorthand for this:
new C.use( T ).extend( {} ).use( T )
Just keep that in mind.
2014-03-06 01:51:49 -05:00
|
|
|
function addTraitInst( T, dfn, tc, base )
|
2014-01-23 00:34:15 -05:00
|
|
|
{
|
Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until
now, mixins could not be stacked without some ugly errors. This also allows
mixins to be stacked atop of themselves, duplicating their effect. This
would naturally have limited use, but it's there.
This differs slightly from Scala. For example, consider this ease.js mixin:
C.use( T ).use( T )()
This is perfectly valid---it has the effect of stacking T twice. In reality,
ease.js is doing this:
- C' = C.use( T );
- new C'.use( T );
That is, it each call to `use' creates another class with T mixed in.
Scala, on the other hand, complains in this situation:
new C with T with T
will produce an error stating that "trait T is inherited twice". You can
work around this, however, by doing this:
class Ca extends T
new Ca with T
In fact, this is precisely what ease.js is doing, as mentioned above; the
"use.use" syntax is merely shorthand for this:
new C.use( T ).extend( {} ).use( T )
Just keep that in mind.
2014-03-06 01:51:49 -05:00
|
|
|
var base_cid = base.__cid;
|
|
|
|
|
|
|
|
// creates a property of the form ___$to$N$M to hold the trait object
|
|
|
|
// reference; M is required because of the private member restrictions
|
|
|
|
// imposed to be consistent with pre-ES5 fallback
|
|
|
|
var iname = '___$to$' + T.__acls.__cid + '$' + base_cid;
|
2014-01-23 00:34:15 -05:00
|
|
|
|
|
|
|
// the trait object array will contain two values: the destination field
|
2014-01-23 22:24:27 -05:00
|
|
|
// and the trait to instantiate
|
|
|
|
tc.push( [ iname, T ] );
|
2014-01-23 00:34:15 -05:00
|
|
|
|
|
|
|
// we must also add the private field to the definition object to
|
|
|
|
// support the object assignment indicated by TC
|
|
|
|
dfn[ 'private ' + iname ] = null;
|
|
|
|
|
|
|
|
// create internal trait ctor if not available
|
|
|
|
if ( dfn.___$$tctor$$ === undefined )
|
|
|
|
{
|
2014-02-10 00:37:25 -05:00
|
|
|
// TODO: let's check for inheritance or something to avoid this weak
|
|
|
|
// definition (this prevents warnings if there is not a supertype
|
|
|
|
// that defines the trait ctor)
|
|
|
|
dfn[ 'weak virtual ___$$tctor$$' ] = function() {};
|
Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until
now, mixins could not be stacked without some ugly errors. This also allows
mixins to be stacked atop of themselves, duplicating their effect. This
would naturally have limited use, but it's there.
This differs slightly from Scala. For example, consider this ease.js mixin:
C.use( T ).use( T )()
This is perfectly valid---it has the effect of stacking T twice. In reality,
ease.js is doing this:
- C' = C.use( T );
- new C'.use( T );
That is, it each call to `use' creates another class with T mixed in.
Scala, on the other hand, complains in this situation:
new C with T with T
will produce an error stating that "trait T is inherited twice". You can
work around this, however, by doing this:
class Ca extends T
new Ca with T
In fact, this is precisely what ease.js is doing, as mentioned above; the
"use.use" syntax is merely shorthand for this:
new C.use( T ).extend( {} ).use( T )
Just keep that in mind.
2014-03-06 01:51:49 -05:00
|
|
|
dfn[ 'virtual override ___$$tctor$$' ] = createTctor( tc, base );
|
2014-01-23 00:34:15 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return iname;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Trait initialization constructor
|
|
|
|
*
|
|
|
|
* May be used to initialize all traits mixed into the class that invokes
|
|
|
|
* this function. All concrete trait classes are instantiated and their
|
|
|
|
* resulting objects assigned to their rsepective pre-determined field
|
|
|
|
* names.
|
|
|
|
*
|
Initial implementation of parameterized traits
This is an important feature to permit trait reuse without excessive
subtyping---composition over inheritance. For example, consider that you
have a `HttpPlainAuth` trait that adds authentication support to some
transport layer. Without parameterized traits, you have two options:
1. Expose setters for credentials
2. Trait closure
3. Extend the trait (not yet supported)
The first option seems like a simple solution:
```javascript
Transport.use( HttpPlainAuth )()
.setUser( 'username', 'password' )
.send( ... );
```
But we are now in the unfortunate situation that our initialization
procedure has changed. This, for one, means that authentication logic must
be added to anything that instantiates classes that mix in `HttpPlainAuth`.
We'll explore that in more detail momentarily.
More concerning with this first method is that, not only have we prohibited
immutability, but we have also made our code reliant on *invocation order*;
`setUser` must be called before `send`. What if we have other traits mixed
in that have similar conventions? Normally, this is the type of problem that
would be solved with a builder, but would we want every configurable trait
to return a new `Transport` instance? All that on top of littering the
API---what a mess!
The second option is to store configuration data outside of the Trait acting
as a closure:
```javascript
var _user, _pass;
function setCredentials( user, pass ) { _user = user; _pass = pass; }
Trait( 'HttpPlainAuth', { /* use _user and _pass */ } )
```
There are a number of problems with this; the most apparent is that, in this
case, the variables `_user` and `_pass` act in place of static fields---all
instances will share that data, and if the data is modified, it will affect
all instances; you are therefore relying on external state, and mutability
is forced upon you. You are also left with an awkward `setCredentials` call
that is disjoint from `HttpPlainAuth`.
The other notable issue arises if you did want to support instance-specific
credentials. You would have to use ease.js' internal identifiers (which is
undocumented and subject to change in future versions), and would likely
accumulate garbage data as mixin instances are deallocated, since ECMAScript
does not have destructor support.
To recover from memory leaks, you could instead create a trait generator:
```javascript
function createHttpPlainAuth( user, pass )
{
return Trait( { /* ... */ } );
}
```
This uses the same closure concept, but generates new traits at runtime.
This has various implications depending on your engine, and may thwart
future ease.js optimization attempts.
The third (which will be supported in the near future) is prohibitive: we'll
add many unnecessary traits that are a nightmare to develop and maintain.
Parameterized traits are similar in spirit to option three, but without
creating new traits each call: traits now support being passed configuration
data at the time of mixin that will be passed to every new instance:
```javascript
Transport.use( HttpPlainAuth( user, pass ) )()
.send( ... );
```
Notice now how the authentication configuration is isolated to the actual
mixin, *prior to* instantiation; the caller performing instantiation need
not be aware of this mixin, and so the construction logic can remain wholly
generic for all `Transport` types.
It further allows for a convenient means of providing useful, reusable
exports:
```javascript
module.exports = {
ServerFooAuth: HttpPlainAuth( userfoo, passfoo ),
ServerBarAuth: HttpPlainAuth( userbar, passbar ),
ServerFooTransport: Transport.use( module.exports.ServerFooAuth ),
// ...
};
var module = require( 'foo' );
// dynamic auth
Transport.use( foo.ServerFooAuth )().send( ... );
// or predefined classes
module.ServerFooTransport().send( ... );
```
Note that, in all of the above cases, the initialization logic is
unchanged---the caller does not need to be aware of any authentication
mechanism, nor should the caller care of its existence.
So how do you create parameterized traits? You need only define a `__mixin`
method:
Trait( 'HttpPlainAuth', { __mixin: function( user, pass ) { ... } } );
The method `__mixin` will be invoked upon instantiation of the class into
which a particular configuration of `HttpPlainAuth` is mixed into; it was
named differently from `__construct` to make clear that (a) traits cannot be
instantiated and (b) the constructor cannot be overridden by traits.
A configured parameterized trait is said to be an *argument trait*; each
argument trait's configuration is discrete, as was demonstrated by
`ServerFooAuth` and `ServerBarAuth` above. Once a parameterized trait is
configured, its arguments are stored within the argument trait and those
arguments are passed, by reference, to `__mixin`. Since any mixed in trait
can have its own `__mixin` method, this permits traits to have their own
initialization logic without the need for awkward overrides or explicit
method calls.
2014-05-28 23:37:36 -04:00
|
|
|
* The MIXINARGS are only useful in the case of parameterized trait.
|
|
|
|
*
|
2014-01-23 22:24:27 -05:00
|
|
|
* This will lazily create the concrete trait class if it does not already
|
|
|
|
* exist, which saves work if the trait is never used.
|
|
|
|
*
|
2014-07-07 00:20:37 -04:00
|
|
|
* Note that the private symbol used to encapsulate class data must be
|
|
|
|
* passed to this function to provide us access to implementation details
|
|
|
|
* that we really shouldn't be messing around with. :) In particular, we
|
|
|
|
* need access to the protected visibility object, and there is [currently]
|
|
|
|
* no API for doing so.
|
|
|
|
*
|
|
|
|
* @param {Object} tc trait class list
|
|
|
|
* @param {Class} base target supertype
|
|
|
|
* @param {Symbol} privsym symbol used as key for encapsulated data
|
Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until
now, mixins could not be stacked without some ugly errors. This also allows
mixins to be stacked atop of themselves, duplicating their effect. This
would naturally have limited use, but it's there.
This differs slightly from Scala. For example, consider this ease.js mixin:
C.use( T ).use( T )()
This is perfectly valid---it has the effect of stacking T twice. In reality,
ease.js is doing this:
- C' = C.use( T );
- new C'.use( T );
That is, it each call to `use' creates another class with T mixed in.
Scala, on the other hand, complains in this situation:
new C with T with T
will produce an error stating that "trait T is inherited twice". You can
work around this, however, by doing this:
class Ca extends T
new Ca with T
In fact, this is precisely what ease.js is doing, as mentioned above; the
"use.use" syntax is merely shorthand for this:
new C.use( T ).extend( {} ).use( T )
Just keep that in mind.
2014-03-06 01:51:49 -05:00
|
|
|
*
|
2014-01-23 00:34:15 -05:00
|
|
|
* @return {undefined}
|
|
|
|
*/
|
2014-07-07 00:20:37 -04:00
|
|
|
function tctor( tc, base, privsym )
|
2014-01-23 00:34:15 -05:00
|
|
|
{
|
|
|
|
// instantiate all traits and assign the object to their
|
|
|
|
// respective fields
|
|
|
|
for ( var t in tc )
|
|
|
|
{
|
|
|
|
var f = tc[ t ][ 0 ],
|
2014-01-23 22:24:27 -05:00
|
|
|
T = tc[ t ][ 1 ],
|
|
|
|
C = T.__ccls || ( T.__ccls = createConcrete( T.__acls ) );
|
2014-01-23 00:34:15 -05:00
|
|
|
|
2014-01-26 03:28:36 -05:00
|
|
|
// instantiate the trait, providing it with our protected visibility
|
|
|
|
// object so that it has access to our public and protected members
|
|
|
|
// (but not private); in return, we will use its own protected
|
|
|
|
// visibility object to gain access to its protected members...quite
|
|
|
|
// the intimate relationship
|
2014-07-07 00:20:37 -04:00
|
|
|
this[ f ] = C( base, this[ privsym ].vis )[ privsym ].vis;
|
2014-02-10 00:37:25 -05:00
|
|
|
|
2016-12-29 02:33:01 -05:00
|
|
|
// rebind trait's supertype context (if any) to our own, causing us
|
|
|
|
// to share state
|
|
|
|
bindSuperCtx( this[ f ], this, privsym );
|
|
|
|
|
2014-06-04 00:37:08 -04:00
|
|
|
// this has been previously validated to ensure that it is a
|
|
|
|
// function
|
|
|
|
this[ f ].__mixin && this[ f ].__mixin.apply(
|
|
|
|
this[ f ], T.___$$mixinargs
|
|
|
|
);
|
|
|
|
}
|
Initial implementation of parameterized traits
This is an important feature to permit trait reuse without excessive
subtyping---composition over inheritance. For example, consider that you
have a `HttpPlainAuth` trait that adds authentication support to some
transport layer. Without parameterized traits, you have two options:
1. Expose setters for credentials
2. Trait closure
3. Extend the trait (not yet supported)
The first option seems like a simple solution:
```javascript
Transport.use( HttpPlainAuth )()
.setUser( 'username', 'password' )
.send( ... );
```
But we are now in the unfortunate situation that our initialization
procedure has changed. This, for one, means that authentication logic must
be added to anything that instantiates classes that mix in `HttpPlainAuth`.
We'll explore that in more detail momentarily.
More concerning with this first method is that, not only have we prohibited
immutability, but we have also made our code reliant on *invocation order*;
`setUser` must be called before `send`. What if we have other traits mixed
in that have similar conventions? Normally, this is the type of problem that
would be solved with a builder, but would we want every configurable trait
to return a new `Transport` instance? All that on top of littering the
API---what a mess!
The second option is to store configuration data outside of the Trait acting
as a closure:
```javascript
var _user, _pass;
function setCredentials( user, pass ) { _user = user; _pass = pass; }
Trait( 'HttpPlainAuth', { /* use _user and _pass */ } )
```
There are a number of problems with this; the most apparent is that, in this
case, the variables `_user` and `_pass` act in place of static fields---all
instances will share that data, and if the data is modified, it will affect
all instances; you are therefore relying on external state, and mutability
is forced upon you. You are also left with an awkward `setCredentials` call
that is disjoint from `HttpPlainAuth`.
The other notable issue arises if you did want to support instance-specific
credentials. You would have to use ease.js' internal identifiers (which is
undocumented and subject to change in future versions), and would likely
accumulate garbage data as mixin instances are deallocated, since ECMAScript
does not have destructor support.
To recover from memory leaks, you could instead create a trait generator:
```javascript
function createHttpPlainAuth( user, pass )
{
return Trait( { /* ... */ } );
}
```
This uses the same closure concept, but generates new traits at runtime.
This has various implications depending on your engine, and may thwart
future ease.js optimization attempts.
The third (which will be supported in the near future) is prohibitive: we'll
add many unnecessary traits that are a nightmare to develop and maintain.
Parameterized traits are similar in spirit to option three, but without
creating new traits each call: traits now support being passed configuration
data at the time of mixin that will be passed to every new instance:
```javascript
Transport.use( HttpPlainAuth( user, pass ) )()
.send( ... );
```
Notice now how the authentication configuration is isolated to the actual
mixin, *prior to* instantiation; the caller performing instantiation need
not be aware of this mixin, and so the construction logic can remain wholly
generic for all `Transport` types.
It further allows for a convenient means of providing useful, reusable
exports:
```javascript
module.exports = {
ServerFooAuth: HttpPlainAuth( userfoo, passfoo ),
ServerBarAuth: HttpPlainAuth( userbar, passbar ),
ServerFooTransport: Transport.use( module.exports.ServerFooAuth ),
// ...
};
var module = require( 'foo' );
// dynamic auth
Transport.use( foo.ServerFooAuth )().send( ... );
// or predefined classes
module.ServerFooTransport().send( ... );
```
Note that, in all of the above cases, the initialization logic is
unchanged---the caller does not need to be aware of any authentication
mechanism, nor should the caller care of its existence.
So how do you create parameterized traits? You need only define a `__mixin`
method:
Trait( 'HttpPlainAuth', { __mixin: function( user, pass ) { ... } } );
The method `__mixin` will be invoked upon instantiation of the class into
which a particular configuration of `HttpPlainAuth` is mixed into; it was
named differently from `__construct` to make clear that (a) traits cannot be
instantiated and (b) the constructor cannot be overridden by traits.
A configured parameterized trait is said to be an *argument trait*; each
argument trait's configuration is discrete, as was demonstrated by
`ServerFooAuth` and `ServerBarAuth` above. Once a parameterized trait is
configured, its arguments are stored within the argument trait and those
arguments are passed, by reference, to `__mixin`. Since any mixed in trait
can have its own `__mixin` method, this permits traits to have their own
initialization logic without the need for awkward overrides or explicit
method calls.
2014-05-28 23:37:36 -04:00
|
|
|
|
2014-02-10 00:37:25 -05:00
|
|
|
// if we are a subtype, be sure to initialize our parent's traits
|
2014-07-07 00:20:37 -04:00
|
|
|
this.__super && this.__super( privsym );
|
2014-01-21 22:57:04 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2016-12-29 02:33:01 -05:00
|
|
|
/**
|
|
|
|
* Bind the supertype context to that of another instance
|
|
|
|
*
|
|
|
|
* The source instance FROM is expected to have a `___$$super$$' property
|
|
|
|
* holding a reference to the supertype. The context associated with the
|
|
|
|
* supertype's class id X in FROM will then be reassigned by reference to
|
|
|
|
* the context X of instance TO.
|
|
|
|
*
|
|
|
|
* This has the effect of ensuring that, whenever a call enters the
|
|
|
|
* supertype's context in FROM, it will use all state from the context of
|
|
|
|
* TO. For mixins, this means that---despite the trait itself inheriting
|
|
|
|
* from the class---we will use the private state of the _mixer_, not our
|
|
|
|
* own.
|
|
|
|
*
|
|
|
|
* This is all done without any loss in performance, since calling context
|
|
|
|
* is immediately bound rather than resolved during each method call.
|
|
|
|
*
|
|
|
|
* TODO: make into a standard API for class manipulation
|
|
|
|
*/
|
|
|
|
function bindSuperCtx( from, to, privsym )
|
|
|
|
{
|
|
|
|
var ctx = from[ privsym ].vis,
|
|
|
|
cid = ctx.___$$super$$.__cid;
|
|
|
|
|
|
|
|
ctx[ privsym ].vis[ cid ] = to[ privsym ].vis[ cid ];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-02-10 00:37:25 -05:00
|
|
|
/**
|
|
|
|
* Create trait constructor
|
|
|
|
*
|
|
|
|
* This binds the generic trait constructor to a reference to the provided
|
|
|
|
* trait class list.
|
|
|
|
*
|
Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until
now, mixins could not be stacked without some ugly errors. This also allows
mixins to be stacked atop of themselves, duplicating their effect. This
would naturally have limited use, but it's there.
This differs slightly from Scala. For example, consider this ease.js mixin:
C.use( T ).use( T )()
This is perfectly valid---it has the effect of stacking T twice. In reality,
ease.js is doing this:
- C' = C.use( T );
- new C'.use( T );
That is, it each call to `use' creates another class with T mixed in.
Scala, on the other hand, complains in this situation:
new C with T with T
will produce an error stating that "trait T is inherited twice". You can
work around this, however, by doing this:
class Ca extends T
new Ca with T
In fact, this is precisely what ease.js is doing, as mentioned above; the
"use.use" syntax is merely shorthand for this:
new C.use( T ).extend( {} ).use( T )
Just keep that in mind.
2014-03-06 01:51:49 -05:00
|
|
|
* @param {Object} tc trait class list
|
|
|
|
* @param {Class} base target supertype
|
2014-02-10 00:37:25 -05:00
|
|
|
*
|
|
|
|
* @return {function()} trait constructor
|
|
|
|
*/
|
Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until
now, mixins could not be stacked without some ugly errors. This also allows
mixins to be stacked atop of themselves, duplicating their effect. This
would naturally have limited use, but it's there.
This differs slightly from Scala. For example, consider this ease.js mixin:
C.use( T ).use( T )()
This is perfectly valid---it has the effect of stacking T twice. In reality,
ease.js is doing this:
- C' = C.use( T );
- new C'.use( T );
That is, it each call to `use' creates another class with T mixed in.
Scala, on the other hand, complains in this situation:
new C with T with T
will produce an error stating that "trait T is inherited twice". You can
work around this, however, by doing this:
class Ca extends T
new Ca with T
In fact, this is precisely what ease.js is doing, as mentioned above; the
"use.use" syntax is merely shorthand for this:
new C.use( T ).extend( {} ).use( T )
Just keep that in mind.
2014-03-06 01:51:49 -05:00
|
|
|
function createTctor( tc, base )
|
2014-02-10 00:37:25 -05:00
|
|
|
{
|
2014-07-07 00:20:37 -04:00
|
|
|
return function( privsym )
|
2014-02-10 00:37:25 -05:00
|
|
|
{
|
2014-07-07 00:20:37 -04:00
|
|
|
return tctor.call( this, tc, base, privsym );
|
2014-02-10 00:37:25 -05:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-06-05 23:35:03 -04:00
|
|
|
function _tctorApply()
|
|
|
|
{
|
|
|
|
this.___$$tctor$$.apply( this, arguments );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-01-21 22:57:04 -05:00
|
|
|
module.exports = Trait;
|
2014-03-04 00:19:39 -05:00
|
|
|
|