Solid Type Checking Functions

Now Available in YourJS

John Resig definitely has some awesome code inside of jQuery. One of the things that I think is cool is how jQuery does type checking. After looking through the code, I developed my own flavor of functions using the same principle. Here is the code that I was able to come up with:

(function() {
  var types = "Object null undefined Array Boolean Date Error Function Number RegExp String".split(" ");
  // Defines the getTypeOf() function on the local and global level.
  // Local variable also declared to prevent errors that would occur
  // from externally redefining getTypeOf().
  var getTypeOf = this.getTypeOf = function(arg) {
    return arg == null
      ? arg + ""
      : types[Object.prototype.toString.call(arg)] || "Object";
  };
  // Defines isNull(), isUndefined(), isArray(), isBoolean(), isDate(),
  // isError(), isFunction(), isNumber(), isObject(), isRegExp(), and
  // isString() on the global level.
  for(var i = types.length - 1; i >= 0; i--) {
    types[i > 1 ? "[object " + types[i] + "]" : types[i]] = types[i];
    (function(type) {
      this["is" + type.charAt(0).toUpperCase() + type.substring(1)] = function(arg) {
        return getTypeOf(arg) == type;
      };
    })(types[i]);
  }
  i = this.isObject;
  this.isObject = function(arg, allowAnyType) {
    return allowAnyType ? arg != null : i(arg);
  };
})();

Basically, my getTypeOf() function is just like jQuery’s jQuery.type() function with one exception. All of the types that are returned, with the exception of undefined and null, are returned as in the same casing as their constructor. In other words, where jQuery.type(/re/) would return "regexp", getTypeOf(/re/) would return "RegExp".

The other function that are created are isNull(), isUndefined(), isArray(), isBoolean(), isDate(), isError(), isFunction(), isNumber(), isObject(), isRegExp(), and isString(). The isObject() function is can take one or two parameters. If one parameter is passed in or two are passed in and the second one evaluates to false, the boolean returned will indicate whether or not the specified argument was an object that is not an array, boolean, date, error, function, number, regular expression or string. If two parameters are passed and the second evaluates to true, the function will return true if the argument is not null or undefined, otherwise it will return false. All of the other functions take one parameter and return a boolean indicating whether or not the type is as specified by the function name.

The following is how I tested the functions:

// Test the specified object on all of the new functions.
function testOnAll(obj, strObj) {
  var arr = "Array Boolean Error Date Function Null Number Object RegExp String Undefined".split(" ");
  for(var i = arr.length - 1; i >= 0; i--) {
    if(arr[i] == "Object") {
      arr.splice(i + 1, 0, eval("is" + arr[i])(obj, true) + " <= isObject(" + strObj + ", true)");
    }
    arr[i] = eval("is" + arr[i])(obj) + " <= is" + arr[i] + "(" + strObj + ")";
  }
  arr.unshift('"' + getTypeOf(obj) + '" <= getTypeOf(' + strObj + ")");
  alert(arr.join("\n"));
}
// All of the tests which display the type of the object and
// the evaluation after using all of the new is functions.
testOnAll("Hello", '"Hello"');
testOnAll([1,2,"Three",[4,[[5]]]], '[1,2,"Three",[4,[[5]]]]');
testOnAll(false, 'false');
testOnAll(null, 'null');
testOnAll(undefined, 'undefined');
testOnAll(window, 'window');
testOnAll(alert, 'alert');
testOnAll(function(){}, 'function(){}');
testOnAll(/#/, '/#/');
testOnAll(new Date(), 'new Date()');
testOnAll(2342, '2342');
testOnAll(3981.3491, '3981.3491');
testOnAll(NaN, 'NaN');
testOnAll(-Infinity, '-Infinity');
testOnAll({}, '{}');
testOnAll(new Object(), 'new Object()');
testOnAll(new (function(){}), 'new (function(){})');
testOnAll(new Error("an error"), 'new Error("an error")');
testOnAll(new EvalError("an error"), 'new EvalError("an error")');
testOnAll(new SyntaxError("an error"), 'new SyntaxError("an error")');
testOnAll(new TypeError("an error"), 'new TypeError("an error")');
testOnAll(new RangeError("an error"), 'new RangeError("an error")');
testOnAll(new ReferenceError("an error"), 'new ReferenceError("an error")');
testOnAll(new URIError("an error"), 'new URIError("an error")');

I think it is important to note that using getTypeOf(NaN) will result in "Number" because NaN is a number object believe it or not. This is the same reason that isNumber(NaN) returns true. As far as I can tell, all of the other tests evaluate as expected.

Function Overloading in JavaScript

Method overloading is one of the nice things that exists in real programming languages such as Java. Even though JavaScript starts with “Java”, there are very few similarities between the two languages. Still, due to the power of the scripting language, there is a way to overload functions. The most commonly used method is to simply write some if statements to examine the arguments. Still, I thought it would be nice to be able to overload functions in a more natural way. Therefore, I wrote the following prototype function for functions which will be included in the next version of jPaq:

// Function.prototype.overload - By Chris West - MIT Licensed
Function.prototype.overload = function(fnToAdd, objFilters) {
  var fnOriginal = this;
  return function() {
    var fnToUse = fnToAdd, args = arguments, len = args.length;
    if(objFilters) {
      for(var key in objFilters) {
        if((key == "all" && !objFilters[key].call(this, args))
           || (key == "length" && len != objFilters[key])
           || (/^\d+$/.test(key) && !objFilters[key](args[key]))) {
          fnToUse = fnOriginal;
          break;
        }
      }
    }
    else if(fnToAdd.length !== len) {
      fnToUse = fnOriginal;
    }
    return fnToUse.apply(this, args);
  };
};

First Parameter (fnToAdd)

The first parameter that you pass in must be the function that you want to overload the current function.

Second Parameter (objFilters)

The second parameter is optional. If it is not specified, the only way that fnToAdd will be used is if the amount of arguments specified in the header of that function matches the number of arguments passed in. If the second parameter is specified, it must be an object literal which will be used to determine whether or not to use this function instead of the original. To filter based on a specific parameter, you should make the key be the index of the parameter and the value should be the function that will check that parameter. This filter function will only receive the specified parameter as its only argument. If you want to filter by the amount of parameters passed, you can define the length of the object literal.  If you want use an all-purpose filter function, you can define the all property of the object literal as a function which will receive all of the parameters just as the function would.  In addition, this all-purpose filter function will also receive the this object of the function.  Just as in the case of the other filter functions, if the return value evaluates to false, the original function will be used, otherwise the new function will be used.

Test Code

Now, instead of trying to clarify the above description with more words, let’s let the code do the talking. Assuming that the definition for Function.prototype.overload() appears before this code, the following will test and prove that function overloading can be quite easy:

// Returns a boolean indicating whether or not the argument is an array.
function isArray(arg) {
  return Object.prototype.toString.call(arg) == "[object Array]";
}

// Returns a boolean indicating whether or not the argument is a number.
function isNumber(arg) {
  return Object.prototype.toString.call(arg) == "[object Number]";
}

// The default callAny() function.
function callAny() {
  alert("Default callAny() function was called.");
};

// The callAny() function that will be used if the first parameter is an array.
function callArray(arr) {
  alert("callArray() called with [" + arr.join(", ") + "]");
}

// The callAny() function that will be used if two numbers are passed.
function callNum(num1, num2) {
  alert("callNum() called with " + num1 + " and " + num2);
}

// The callAny function that will be used if no arguments are passed.
function callNone() {
  alert("callNone() called without arguments as expected.");
}

// Easily overload the callAny() function.
callAny = callAny
  .overload(callNone)
  .overload(callArray, {0 : isArray})
  .overload(callNum, {0 : isNumber, length : 2});

/********** Example calls the callAny() **********/
// Calls the default function.
callAny();

// Calls the callNum() function.
callAny(1, 2);

// Calls the default function.
callAny(3);

// Calls the callArray() function.
callAny(["Hello", "World!!!"]);

The first thing to note is that the isNumber() and isArray() functions are defined to filter the arguments. Both callNone(), callArray() and callNum() will overload callAny(). If callAny() is called without any parameters, it acts as if callNone() was invoked. If callAny() is called with the first parameter being an array, it acts as if callArray() was called. If callAny() is called with the first parameter being a number and is called with exactly two parameters, it acts as if callNum() was called. In all other cases the default version of callAny() is called.

Future

I personally think that this may become useful for other JavaScript programmers. Therefore, I plan on incorporating this function into jPaq. Unfortunately, I don’t have an exact date for the next release, therefore, I am fine with you using this code as long as you give me credit for my code. 8)

JavaScript – Cube Root & Beyond

Lately, I have been in a math oriented mood.  Yesterday, I was thinking about how the fact that you can take the square root of a number in JavaScript, but not the cube root, 4th root, etc..  Naturally, I looked to Wikipedia to find the algorithm to find the nth root of a number.  I developed the following unnecessary function:

function bad_nthroot(x, n) {
  // If an error occurs, don't return anything.
  try {
    // If an error occurs
    // Loop through the nth root algorithm until no difference is found.
    var x2, A = x, n1 = n - 1, itersLeft = 99;
    do {
      x2 = x;
      x = (n1 * x2 + A / Math.pow(x2, n1)) / n;
    } while(x != x2 && itersLeft--);
    
    // If less than 100 iterations were done or if the difference between the
    // previous approximation and the current one is acceptable, return the
    // current one.
    if((itersLeft || (Math.abs(x - x2) < 1 && (x < 0) == (x2 < 0))) && isFinite(x))
      return x;
  } catch(e){}
}

After testing out the function a bit and taking a look at an online example, I realized that I could have done it with the Math.pow(). After all, the cube root of a number is the same thing as that number to the power of 1/3. With this in mind, I redeveloped the function and came up with the following, more accurate function:

function nthroot(x, n) {
  try {
    var negate = n % 2 == 1 && x < 0;
    if(negate)
      x = -x;
    var possible = Math.pow(x, 1 / n);
    n = Math.pow(possible, n);
    if(Math.abs(x - n) < 1 && (x > 0 == n > 0))
      return negate ? -possible : possible;
  } catch(e){}
}

One notable difference is the fact that calling bad_nthroot(8, 1/2) results in 63.99999999999999. On the other hand, nthroot(8, 1/2) results in 64. There are many other differences as well.

The reason I created this function instead of using Math.pow(x, 1 / n) is because of examples such as Math.pow(-8, 1 / 3). Even though the cube root of -8 is -2, Math.pow(-8, 1 / 3) results in NaN. On the other hand, nthroot(-8, 3) results in the expected value of -2.

If you find something wrong with my nthroot function, please let me know.