Hello and welcome to our community! Is this your first visit?
Register
Enjoy an ad free experience by logging in. Not a member yet? Register.
Results 1 to 8 of 8
  1. #1
    Regular Coder
    Join Date
    Jun 2007
    Location
    USA
    Posts
    527
    Thanks
    26
    Thanked 74 Times in 72 Posts

    Function Overloading

    I wrote some code to deal with general function (and method) overloading in Javascript. You can overload based on arity, types, optional arguments, and variable length arguments.

    Overload Syntax:
    Code:
    var foo = overload (
        [Type1, Type2, Type3]
      , function (x, y, z) { ... }
      , [Type4]
      , function (x) { ... }
      /* etc */
    )
    Type Syntax:
    Code:
    Type // match the type
    [Type, defaultValue] // try matching Type. Else use default value.
    [Type] // variable amount of Type
    nullable (Type) // accepts Type or null
    [[function (x) { return x.length > 5; }]] // Custom predicate.
    All matching is done greedily.

    Object will not match primitive numbers, primitive booleans, primitive strings, null, or undefined.

    String, Number, Boolean, and Function will match against primitives and objects of the corresponding types.

    Varargs are condensed into array format before tossed to corresponding functions.

    Custom predicates are executed in try-catch blocks, allowing you to not fuss over whether or not your predicate pulling non-existent attributes and other things.

    ANYTYPE will match anything. It will even match null.

    overload.js:
    Code:
    var nullable, ANYTYPE;
    
    var overload = (function () {
    
      ANYTYPE = {};
    
      var extend = function (child, parent) {
        child.prototype = new parent ();
        child.prototype.constructor = child;
      };
    
      var combine = function (x, xs) {
        if (xs) {
          xs.push (x);
        }
        return xs;
      };
    
      var Type = function (constr, nextType) {
        this.constr = constr;
        this.nextType = nextType;
      };
    
      Type.prototype = {
          constructor: Type
        , match: function (arg) {
            if (arg === undefined || arg === null) {
              return false;
            }
            return arg instanceof this.constr;
          }
        , /* Builds arglist in REVERSE if compatable!!! Else returns false. */
          unify: function (args, i) {
            return this.match (args [i])
              ? combine (args [i], this.nextType.unify (args, i + 1))
              : null
              ;
          }
      };
    
    
      var EndType = function () {
        Type.call (this, null, this);
      };
    
      extend (EndType, Type);
      EndType.prototype.match = function (arg) {
        return false;
      };
      EndType.prototype.unify = function (args, i) {
        return args.length === i
          ? []
          : null
          ;
      };
    
      var ENDTYPE = new EndType ();
    
    
      var Nullable = function (constr, nextType) {
        Type.call (this, constr, nextType);
      };
    
      extend (Nullable, Type);
      Nullable.prototype.match = function (arg) {
        return arg === null || Type.prototype.match.call (this, arg);
      };
    
      nullable = function (constr) {
        return new Nullable (constr);
      };
    
    
      var AnyType = function (nextType) {
        Type.call (this, null, nextType);
      }
    
      extend (AnyType, Type);
      AnyType.prototype.match = function (arg) {
        return true;
      };
    
    
      var Primitive = function (constr, nextType) {
        Type.call (this, constr, nextType);
        switch (constr) {
          case String:
            this.typeOf = "string";
            break;
          case Number: 
            this.typeOf = "number";
            break;
          case Boolean: 
            this.typeOf = "boolean";
            break;
          case Function: 
            this.typeOf = "function";
            break;
        }
      };
    
      extend (Primitive, Type);
      Primitive.prototype.match = function (arg) {
        return typeof arg === this.typeOf || Type.prototype.match.call (this, arg);
      };
    
    
      var Predicate = function (pred, nextType) {
        Type.call (this, null, nextType);
        this.pred = pred;
      }
    
      extend (Predicate, Type);
      Predicate.prototype.match = function (arg) {
        try {
          return this.pred (arg);
        }
        catch (e) {
          return false;
        }
      };
    
    
      var TypeWrapper = function (type, nextType) {
        Type.call (this, null, nextType);
        this.type = type;
      };
    
      extend (TypeWrapper, Type);
      TypeWrapper.prototype.match = function (arg) {
        return this.type.match (arg);
      };
    
    
      var Optional = function (type, nextType, defaultValue) {
        TypeWrapper.call (this, type, nextType);
        this.defaultValue = defaultValue;
      };
    
      extend (Optional, TypeWrapper);
      Optional.prototype.unify = function (args, i) {
        if (this.match (args [i])) {
          return combine (args [i], this.nextType.unify (args, i + 1));
        }
        return combine (this.defaultValue, this.nextType.unify (args, i));
      };
    
    
      var Variadic = function (type, nextType) {
        TypeWrapper.call (this, type, nextType);
      };
    
      extend (Variadic, TypeWrapper);
      Variadic.prototype.unify = function (args, i) {
        var result;
        var maxJ = args.length;
        do {
          var j = i;
          var argGroup = [];
          while (j < maxJ && this.type.match (args [j])) {
            j = argGroup.push (args [j]);
          }
          maxJ = j - 1;
          result = combine (argGroup, this.nextType.unify (args, j));
          if (result) {
            return result;
          }
        } while (maxJ >= i);
        return result;
      };
    
    
      var parseType = function (sig, nextType) {
        if (sig instanceof Array) {
          if (sig [0] instanceof Array) {
            return new Predicate (sig [0] [0], nextType);
          }
          else if (sig.length === 1) {
            return new Variadic (parseType (sig [0]), nextType);
          }
          else if (sig.length === 2) {
            return new Optional (parseType (sig [0]), nextType, sig [1]);
          }
        }
        else if (sig === ANYTYPE) {
          return new AnyType (nextType);
        }
        else if (sig.constructor === Nullable) {
          return new Nullable (sig.constr, nextType);
        }
        else if (sig === String || sig === Number || sig === Boolean || sig === Function) {
          return new Primitive (sig, nextType);
        }
        else {
          return new Type (sig, nextType);
        }
      };
    
      var mkTypeChain = function (sigs) {
        var chain = ENDTYPE;
        for (var i = sigs.length - 1; i >= 0; --i) {
          chain = parseType (sigs [i], chain);
        }
        return chain;
      };
    
      return function () {
        if (arguments.length % 2 !== 0) {
          throw new Error ("overload: Invalid number of arguments.");
        }
        var typeChains = [];
        var funcs = [];
        for (var i = 0; i < arguments.length; i += 2) {
          typeChains.push (mkTypeChain (arguments [i]));
          funcs.push (arguments [i + 1]);
        }
        return function () {
          for (var i = 0; i < typeChains.length; ++i) {
            var result = typeChains [i].unify (arguments, 0);
            if (result) {
              result.reverse ();
              return funcs [i].apply (this, result);
            }
          }
        };
      };
    }) ();
    Example:
    Code:
    var foo = overload (
        [[[function (x) { return x.length == 1; }]]]
      , function (chr) {
          return [1, chr];
        }
      , [[Number, 777], String]
      , function (num, str) {
          return [2, num, str];
        }
      , [String, String, String]
      , function (str1, str2, str3) {
          return [3, str1, str2, str3];
        }
      , [String, String, Number]
      , function (str1, str2, num) {
          return [4, str1, str2, num];
        }
      , [[Number]]
      , function (nums) {
          return [5, nums.toSource ()];
        }
      , [[Number], Number, String]
      , function (nums, num, str) {
          return [6, nums.toSource (), num, str];
        }
      , [Object, Object]
      , function (obj1, obj2) {
          return [7, obj1, obj2];
        }
      , [nullable (Object)]
      , function (obj) {
          return [8, String (obj)];
        }
      , [ANYTYPE, Object]
      , function (obj1, obj2) {
          return [9, obj1, obj2];
        }
    );
    
    document.write (foo ("x"), "<br />");		// 1,x
    document.write (foo ("hello"), "<br />");	// 2,777,hello
    document.write (foo (888, "hello"), "<br />");	// 2,888,hello
    document.write (foo ("a", "b", "c"), "<br />");	// 3,a,b,c
    document.write (foo ("a", "b", 666), "<br />");	// 4,a,b,666
    document.write (foo (), "<br />");		// 5,[]
    document.write (foo (222), "<br />");		// 5,[222]
    document.write (foo (22, 33, 44, 55), "<br />");// 5,[22,33,44,55]
    document.write (foo (11, 22, 33, 44, "a"), "<br />");//6,[11,22,33],44,a
    document.write (foo ({}, /regex/), "<br />");	// 7,[object Object],/regex/
    document.write (foo (/regex/), "<br />");	// 8,/regex/
    document.write (foo (null), "<br />");		// 8,null
    document.write (foo (true, /regex/), "<br />");	// 9,[object Object],/regex/
    Last edited by Trinithis; 12-27-2009 at 09:25 AM. Reason: refactoring
    Trinithis

  • #2
    Senior Coder rnd me's Avatar
    Join Date
    Jun 2007
    Location
    Urbana
    Posts
    4,296
    Thanks
    10
    Thanked 583 Times in 564 Posts
    very interesting.

    i think some real-world examples of this code in action would prove instructional to most of our readers.
    my site (updated 13/9/26)
    BROWSER STATS [% share] (2014/5/28) IE7:0.1, IE8:5.3, IE11:8.4, IE9:3.2, IE10:3.2, FF:18.2, CH:46, SF:7.9, NON-MOUSE:32%

  • #3
    Regular Coder
    Join Date
    Jun 2007
    Location
    USA
    Posts
    527
    Thanks
    26
    Thanked 74 Times in 72 Posts
    I fixed a bug in the code, and here are some better examples.

    Example 1:
    Code:
    var clip = overload (
        [[Number, 0], Array, [Number, 0]]
      , function (fromStart, xs, fromEnd) {
          var max = xs.length - fromEnd;
          var ys = []
          for (var i = fromStart; i < max; ++i) {
            ys.push (xs [i]);
          }
          return ys;
        }
    );
    
    var xs = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
    clip (xs);
    clip (4, xs);
    clip (xs, 7);
    clip (2, xs, 3);
    Results 1:
    Code:
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9
    4,5,6,7,8,9
    0,1,2
    2,3,4,5,6
    Example 2:
    Code:
    var serialize = overload (
        [Number]
      , function (n) {
          return n.toString (16).toUpperCase ();
        }
      , [String]
      , function (str) {
          var es = "nrtf\\\"";
          for (var i = 0; i < es.length; ++i) {
            var e = es.charAt (i);
            str = str.replace (new RegExp ("\\" + e, "g"), "\\" + e);
          }
          return '"' + str + '"';
        }
      , [Boolean]
      , function (bool) {
          return bool ? 1 : 0;
        }
      , [Function]
      , function (func) {
          return "{FUNCTION}";
        }
      , [Array]
      , function (xs) {
          var ys = [];
          for (var i = 0; i < xs.length; ++i) {
            ys.push (serialize (xs [i]));
          }
          return '[' + ys.join (',') + ']';
        }
      , [Object]
      , function (obj) {
          return obj.toString ();
        }
      , [ANYTYPE]
      , function (x) {
          return String (x);
        }
      , [Number, ANYTYPE]
      , function (n, x) {
          return serialize (x).substring (n);
        }
      , [[ANYTYPE]]
      , function (xs) {
          return serialize (xs);
        }
    );
    
    serialize (7894456548774);
    serialize (4, 7894456548774);
    serialize (666, "NAME:\t\"Gerald\"", true, serialize, [1, 2, 3], {}, null, undefined);
    Results 2:
    Code:
    72E12473DA6
    2473DA6
    [29A,"NAME:\\t\"Gerald\"",1,{FUNCTION},[1,2,3],[object Object],null,undefined]
    Last edited by Trinithis; 07-22-2009 at 02:20 AM.
    Trinithis

  • Users who have thanked Trinithis for this post:

    rnd me (07-22-2009)

  • #4
    Regular Coder
    Join Date
    Jun 2007
    Location
    USA
    Posts
    527
    Thanks
    26
    Thanked 74 Times in 72 Posts
    Example 3:

    While I wouldn't bother using overload for the following, here it is anyway just for comparison. I guess it's a tradeoff between speed (which I doubt most scripts care about) and elegance.

    By hand:
    Code:
    Function.bundle = function (context, f, args) {
      if (typeof f == "string") {
        f = context [f];
      }
      if (arguments.length < 3) {
        args = [];
      }
      else if (!(args instanceof Array)) {
        args = Array.prototype.slice.call (arguments, 2);
       }
      return function () {
        return f.apply (context, args);
      };
    };
    Using overload:
    Code:
    Function.bundle = overload (
        [ANYTYPE, Function, Array]
      , function (context, f, args) {
          return function () {
            return f.apply (context, args);
          };
        }
      , [ANYTYPE, String, Array]
      , function (context, f, args) {
          return Function.bundle (context, context [f], args);
        }
      , [ANYTYPE, ANYTYPE, [ANYTYPE]]
      , function () {
          return Function.bundle.apply (this, arguments);
        }
    );
    BTW Function.bundle is an awesome tool .
    Last edited by Trinithis; 12-27-2009 at 07:35 AM. Reason: added missing return statement
    Trinithis

  • #5
    Regular Coder
    Join Date
    Jun 2007
    Location
    USA
    Posts
    527
    Thanks
    26
    Thanked 74 Times in 72 Posts
    I've done a major rehaul of the code. I redid everything OO style. New code is now shown in the original post, replacing the old code.

    For those who want the old code, here it is:
    Code:
    function Nullable (type) {
      this.type = type;
    }
    
    function nullable (type) {
      return new Nullable (type);
    }
    
    var ANYTYPE = {};
    
    var overload = (function () {
    
      var combine = function (x, xs) {
        if (xs === false) {
          return false;
        }
        xs.push (x);
        return xs;
      };
    
      var toTypeof = function (type) {
        switch (type) {
          case Boolean: return "boolean";
          case Function: return "function";
          case Number: return "number";
          case String: return "string";
          default: return null;
        }
      };
    
      var isPredicate = function (type) {
        return type instanceof Array && type [0] instanceof Array;
      };
    
      var getPredicate = function (type) {
        return type [0][0];
      };
    
      var isNullable = function (type) {
        return type instanceof Nullable;
      };
    
      var isOptional = function (type) {
        return type instanceof Array && type.length == 2 && !isPredicate (type);
      };
    
      var isVariadic = function (type) {
        return type instanceof Array && type.length == 1 && !isPredicate (type);
      };
    
      var getType = function (typeWrapper) {
        return typeWrapper [0];
      };
    
      var defaultValue = function (optionalType) {
        return optionalType [1];
      };
    
      var match = function (arg, type) {
        if (isPredicate (type)) {
          try {
            return getPredicate (type) (arg);
          }
          catch (e) {
            return false;
          }
        }
        if (type == ANYTYPE) {
          return true;
        }
        if (isNullable (type)) {
          return arg === null || match (arg, type.type);
        }
        if (arg === null || arg === undefined || type === null || type === undefined) {
          return false;
        }
        if (isOptional (type) || isVariadic (type)) {
          type = getType (type);
        }
        return arg instanceof type || typeof arg == toTypeof (type);
      };
    
      /* Builds arglist in REVERSE if compatable!!! Else returns false */
      var unify = function (args, types, i, j) {
        if (i == args.length && j == types.length) {
          return [];
        }
        var arg = args [i];
        var type = types [j];
        if (isOptional (type)) {
          return unifyOptional (args, types, i, j);
        }
        else if (isVariadic (type)) {
          return unifyVariadic (args, types, i, j);
        }
        else {
          return match (arg, type)
            ? combine (arg, unify (args, types, i + 1, j + 1)) 
            : false
            ;
        }
      };
    
      var unifyOptional = function (args, types, i, j) {
        var arg = args [i];
        var type = types [j];
        if (match (arg, type)) {
          var result = combine (arg, unify (args, types, i + 1, j + 1));
          if (result) {
            return result;
          }
        }
        return combine (defaultValue (type), unify (args, types, i, j + 1));
      };
    
      var unifyVariadic = function (args, types, i, j) {
        var type = types [j];
        var maxK = Number.POSITIVE_INFINITY;
        do {
          var argGroup = [];
          var k = 0;
          while (k < maxK && (i + k) < args.length && match (args [i + k], type)) {
            k = argGroup.push (args [i + k]);
          }
          maxK = k - 1;
          var result = combine (argGroup, unify (args, types, i + k, j + 1));
          if (result) {
            return result;
          }
        } while (maxK > 0);
        return combine ([], unify (args, types, i, j + 1));
      };
    
      return function () {
        var defns = arguments;
        return function () {
          for (var i = 0; i < defns.length; i += 2) {
            var types = defns [i];
            var func = defns [i + 1];
            var result = unify (arguments, types, 0, 0);
            if (result) {
              result.reverse ();
              return func.apply (this, result);
            }
          }
        };
      };
    }) ();
    Trinithis

  • #6
    Regular Coder
    Join Date
    Jun 2007
    Location
    USA
    Posts
    527
    Thanks
    26
    Thanked 74 Times in 72 Posts
    I just tested the speed differences between my old and new code (Linux Firefox 3). The new code is significantly faster. It always runs faster than the old code and often runs about twice as fast.
    Trinithis

  • #7
    Senior Coder rnd me's Avatar
    Join Date
    Jun 2007
    Location
    Urbana
    Posts
    4,296
    Thanks
    10
    Thanked 583 Times in 564 Posts
    I just want to say that this code is really cool.

    I don't come across much javascript like this, and it's nice to see novel designs instead of spaghetti. The declarative style of the overload versions is very refreshing and intuitive. I think it will come in quite handy for tokenizing code and transforming data.

    I think it also allows more portable code to be authored.

    You would only need to update overload() to play nice on new implementations while your program code remains unchanged. You can then optimize overload() for ecma5, or asp js, or whatever, and your whole app speeds up.

    once again, thanks for putting this out there, i appreciate it.
    my site (updated 13/9/26)
    BROWSER STATS [% share] (2014/5/28) IE7:0.1, IE8:5.3, IE11:8.4, IE9:3.2, IE10:3.2, FF:18.2, CH:46, SF:7.9, NON-MOUSE:32%

  • #8
    Regular Coder Element's Avatar
    Join Date
    Jul 2004
    Location
    Lynnwood, Washington, US
    Posts
    855
    Thanks
    2
    Thanked 2 Times in 2 Posts
    Wow, this is awesome, thanks so much!


  •  

    Posting Permissions

    • You may not post new threads
    • You may not post replies
    • You may not post attachments
    • You may not edit your posts
    •