In a sentence, Typecast solves all the simple problems, so you can focus on the big ones.
Typecast fixes what's wrong with Javascript by creating a complete platform for strongly-typed variables in Javascript. It works both in a browser and on Node.js servers. Typecast pairs well with jQuery by providing strongly typed variables without altering Javascript's native prototypes or requiring a custom compiler such as CoffeeScript, TypeScript or Clojure.
Typecast is typecasting for underscore.js. While underscore provides 60 helper functions, Typecast provides all the same functions and over 500 more. Typecast speeds development by reducing the length and variety of code required to complete common programming tasks, reducing both the number of bugs and the troubleshooting time to hunt them down.
( questions || comments || ideas || type-related jokes )
? github repository's issues page
: eduatx [at] gmail [dot] com
code
code
code
code
code
code
code
code
code
code
Return an example value of type.
Type.a('false') // false
Type.a('number') // 0
Type.a('array') // []
Is this value of this type? Specifically, returns true if this value be made into a typed object without changing? Strict comparison, returns true or false.
Type.is('number', 123) // true
Type.is('number', 'abc') // false
With all the comparison functions either property can also be a Typed object. The first property will use the type of the Typed object. The second property will use the value of the Typed object.
var num1 = Type('number', 456);
Type.is(num1, 123) // true, 123 is a number
Type.is('integer', num1); // true, 123 is an integer
Can this value be made into this type. Returns true or false.
Type.can('number', 123) // true
Type.can('number', '123') // true
Type.can('number', 'abc') // false, 'abc' cannot be made into a number
var num1 = Type('float', 123.1);
Type.can('integer', num1); // true, 123.1 can be made into an integer
Make a value into a type. Returns a raw value, rather than a Typed object.
Type.make('number', 123) // 123
Type.make('number', '123') // 123
Type.make('number', 'abc') // false
var num1 = Type('float', 123.1)
Type.make('integer', num1); // 123
Returns a typed object, performing a specific type tranformation.
Type.to('object', ['a']) // Typed object with the value of {0:'a'}
Almost exactly the same as the native Javascript typeof operator, except it distriguishes null, NaN, arguments and arrays.
Type.detect([]) // "array" Type.detect(123) // "number"
Each Type also offers a set of class methods which do not operate on a typed object, but return values related to that type:
Type.number.random(123) // returns a random number between 0 and 123 Type.array.range(0, 4) // returns [0,1,2,3,4]
Type() is a function that creates Typed objects:
Type('array', []) // returns a Typed object
// of type 'array' and with a value of []
To transform a Typed object into a new type, pass it back to Type() as the second argument:
var myArray = Type('array', [1,2,3]);
var myString = Type('string', myArray);
myString.type() // returns "string"
myString.a() // returns the original array value as a string "[1,2,3]"
Calling Type() with just a value creates Typed object using the type given by Type.detect() called on the same value. This of course is slower, but good for cases when you are unsure what type of value you have:
Type([]) // returns an array Typed object
Also, the type name itself is a shorthand for making a Typed object with Type( name, value ). If a value is provided, it is used.
Type.number() // returns a Typed object with a value of 0
Type.number('123') // returns a Typed object with a value of 123
Typed objects come with several functions:
var myArray = Type('array', [1,2,3]);
myArray.a() // returns the value of the Typed object, [1,2,3]
myArray.val() // alias of a() for jQuery people
myArray.get() // alias of a() for getter/setter people
myArray.set([4,5,6]) // changes the value of the Typed object
myArray.type() // returns 'array'
Additionally, all typed objects inherit methods from the type that they become. Strings get string methods and arrays get array methods. All object methods are wrapped to provide two features. All arguments can be Typed objects rather than values:
var myArray = Type.array([1,2,3]);
var interator = Type.fn(function(a){ return ++a; });
myArray.map(iterator).a() // returns [2,3,4]
And all methods associated with the Typed object's type are chainable:
var myArray = Type.array([1,2,3]); myArray.pop().union([3,4,5]).unique().without(4).a() // returns [2,3,5]
Typed objects have an is() function that checks if the value of the object is also of the supplied type.
var num1 = Type('number', 123);
num1.is('positive'); // true
num1.is('negative'); // false
The ifis() function can be used in two ways. If object is of type, then call this function on the Typed object, or if first property is truthy, then call this function on the Typed object.
var myArray = Type('array', [1,2,3]);
myArray.ifis('array', function(){
return this.a().splice(1); // returns [2,3]
});
myArray.ifis(1 === 1, function(){
return this.a().length; // returns 3
});
The .to() function does a specific type-to-type transformation, if one is available. To's list of specific transformation are broader than just the list of types. For instance there are many ways to change an array into an object. Each one is a transformation from a Typed array to a Typed object. If no transformation is available type uses the more generic make function for that class.
var num = Type.number(123);
num.to('bool').a() // false
num.to('array').a() // [false]
num.to('str').a() // "false"
num = num.to('array').to.first();
num.a() // true
num.type() // boolean
Typed objects inherit special helpers methods from their Type definition. The list of methods for each type are listed in the documentation. Any property passed to these methods may be the value or a Typed object containing the value. Typed object methods return a new Typed object with the same type as the original. Because of this, Typed object methods are chainable.
var num = Type.number(123);
num.plus(5).a() // 128
num.a() // 123
var fn = Type.fn(function(v){ return ++v; });
var list = Type.arr([1,2,3]);
list.map(fn).a() // [2,3,4]
list.map(fn.after(2)).a() // [1,2,4]
list.push(4).shift().a() // [2,3,4]
list.a() // [1,2,3] original value is preserved
list.set(list.push(4)) // .set() modifies the value
list.a() // [1,2,3,4]
list = list.push(5) // or just overwrite with the return object
list.a() // [1,2,3,4,5]
Returns an array of all the registered types.
Returns true if name is a registered type.
Registers a new type. Returns true if successful. Pass in a type name, default value, and hash of functions defined as { property : function } in the definition object.
Type.extend({
name : "negative integer",
a : -1,
alias : [ '-int', '-integer', 'ni' ],
inherits : [ 'integer', 'negative' ],
is : function(value){ return Type.is('integer', value) && value < 0; },
make : function(value){
if (Type.can('integer', value)) {
if (value < 0) { return value; };
if (value !== 0) { return -value; };
};
return this.a(); // -1
},
random: function(a){ return -Math.abs(Type.number.random(a).toFixed(0)); },
methods: {
decByTwo: function(){ return this.a()-2; }
},
transforms: {
"array" : function(){ return [this.a()]; }
}
});
Type.ni.random(4) // returns a negative integer between 0 and -4
var nint = Type('-int', 123);
nint.a() // -123
nint.decByTwo().a() // -125
nint.to.array().a() // [-125]
The class definition provided to Type.extend() cannot override any of the built-in Typecast methods except a(), get(), val(), type() and typeof(). Alias and Inherits are also reserved words and can not be used as class method names. All new types should declare an is() and make() function, and can() if there is a deference between is() and can(). The methods object is a list of methods that are wrapped and added to any new Typed object of the new type.
Run Typecast in *noConflict* mode, returning the "Type" variable to its previous owner. The function returns a reference to the Type object, so it can be placed elsewhere in namespace. Pass true as a property to noConflict to also relenquish window._ to it's previous owner.
var Typecast = Type.noConflict();
The main value statement of Typecast is that it "speeds development by reducing the length and variety of code required to complete common programming tasks, reducing both bugs and troubleshooting time." Typecast smooths out many of the eccentricities of Javascript and how Javascript is implemented across browsers. Below are a few examples of how Typecast does this.
Typecast doesn't remove access to the native prototypes, and does this without trapping you into a custom compiler that could quickly fall out of date. Converting CoffeeScript back to Javascript requires a complete re-write. Typecast on the other hand, enables you to use strongly typed variables while keeping the code base in native Javascript syntax, regardless of the environment.
Frequently, it is convenient to use Javascript shorthand to detect for the existence of a variable:
if (someVar) {
// do stuff with someVar
}
var newVar = (oldVar) ? oldVar : defaultValue;
If someVar is undefined or null then these statements work, but if the value of the variable has a value of 0 then these statements evaluate false even though the value is defined. If this is the behavior you want, then great, but each of these shorthand statements are potential bugs that may not show up until a test case where the variable exists, but doesn't have a truthy value. Typecast solves this by differentiating True from Truthy and False from Falsey.
Type.is('false', 0); // false
Type.is('falsey', 0); // true
Type.is('false', ''); // false
Type.is('falsey', ''); // true
Type.is('false', []); // false
Type.is('falsey', []); // true
0 == "0" // true
true == "true" // false ?!?
Type.number.make(0) == Type.number.make("0") // true
Type.bool.make(true) == Type.bool.make("true") // true
One problem with numbers in Javascript is that they are not particularly easy to parse becuae there are so many types of them. Typecast aims to parse all of the conventional number patterns in Javascript. These include:
Another problem that numbers have is that floating point comparison is way off. See this StackOverflow article.
0.1 + 0.2 == 0.3 // returns false 0.1 + 0.2 // returns 0.30000000000000004 !?! "" + 1 + 2; // "12" "" + ( 1 + 2 ); // "3" "" + 0.0000001; // "1e-7" parseInt( 0.0000001 ); // 1 !?!
Typecast resolves this by providing the eq method for float objects.
var value = Type.float(0.3); Type.float.eq(0.1 + 0.2, value); // true Type.float.eq(0.1 + 0.2, value, 5); // true to 5 decimal places
Or how about what isn't a Number. Not a Number, or NaN, is the only Number which is not equal to itself. Making it an intersting puzzle, an oddity, but not very practical as a return value for Number casting.
NaN == NaN // false ?!?
typeof NaN === 'number' // true, Not A Number is a Number ?!?
Type.nan.is(NaN) // true
Type.number.is(NaN) // false
Number('abc') // NaN
parseInt('abc') // NaN
parseFloat('abc') // NaN
Type.number.make('abc') // 0
Type.nan.is(NaN); // true
Type.nan.is(1); // false
Type.number.is(NaN); // false
Type.number.is(1); // true
Here is another problem. Not sure how to solve this one yet. Have an idea?
100 + 010 = 108
Most Javascript programmers are aware of the inconsistancies in Javascript's native typeof and instanceof operators. For example:
typeof {} // object
typeof [] // object, not array ?!?
typeof null // object ?!?
[] instanceof Array // true
[] instanceof Object // true ?!?
{} instanceof Object // true again!
Because of this, the typeof operator can be used on most value types, except array. Rather than trying to remember which type comparison should be preformed on each value type, Typecast provides Type.is() as a complete interface for all value types. Type.object.is() works the same way as jQuery.isPlainObject() only detecting for objects that are not any of Javascript's 'special' objects like arrays, null, functions, or Infinity.
Type.object.is({}) // true
Type.object.is([]) // false
Type.array.is([]) // true
Type.array.is({}) // false
For novice and intermediate Javascript programmers, this can prevent hours of troubleshooting bugs that come from Javascript's strange behavior. Advanced programmers may at first feel that they have already learned to side-step many of the these problems, however remembering the correct way to parse an Integer or the slight differences between typeof 'array' and instanceof Array, takes up room in your brain that could be used in accomplishing higher level tasks.
Typecast uses a functional version of the Null Object Pattern and makes extensive use of the K-Combinator from SKI calculus which is also called const in Haskell Prelude. Major props to the underscore.js and lodash.js developers and community. Almost all of this code came from Stack Overflow.
( questions || comments || ideas || type-related jokes )
? github repository's issues page
: eduatx [at] gmail [dot] com
Typecast is distributed under the MIT licence. Feel free to reuse as you see fit. Please do credit this site in any derivative works. Official version distribution on Github.