From b0f4c7133b085278dd5a77db45b72a43134177c7 Mon Sep 17 00:00:00 2001 From: Minori Yamashita Date: Wed, 20 Feb 2013 01:09:33 +0900 Subject: [PATCH 01/12] Add `CLOS.defClass` which returns a constructor function. Modify `CLOS.isA` to check with `instanceof` instead of `===`. Fix `CLOS.call` by giving the `apply` method a scope. Fix type in tests.js. All tests passes! Yaahoo --- .gitignore | 3 ++ README.md | 43 ++++++++++++++++++++ clos.js | 115 ++++++++++++++++++++++++++++++++--------------------- test.js | 79 ++++++++++++++++++++---------------- 4 files changed, 160 insertions(+), 80 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6072182 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +\.#* +node_modules +*~ diff --git a/README.md b/README.md index 96ebece..9cffa1d 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,46 @@ JS-CLOS ======= A CLOS-like framework sketch on JavaScript. + + +Usage +----- + +```javascript +//define a bunch of classes +var floor = CLOS.defClass("floor"); +var carpet = CLOS.defClass("carpet"); +var ball = CLOS.defClass("ball"); +var glass = CLOS.defClass("glass"); +var stick = CLOS.defClass("stick"); + +//function to display the result +var bumpOutput = function(x, y, result){ + console.log(x + ' + ' + y + ' bump = ' + result); +}; + +//define a generic function `bump` +CLOS.defGeneric('bump'); + +//define methods +CLOS.defMethod('bump', [ball, floor], function(x, y){ + bumpOutput(x, y, 'bounce'); +}); +CLOS.defMethod('bump', [glass, floor], function(x, y){ + bumpOutput(x, y, 'crash'); +}); +CLOS.defMethod('bump', [stick, floor], function(x, y){ + bumpOutput(x, y, 'knock'); +}); +CLOS.defMethod('bump', [undefined, carpet], function(x, y){ + bumpOutput(x, y, 'silence'); +}); + +//call the methods +CLOS.call('bump', new ball, new floor); //should bounce +CLOS.call('bump', new glass, new floor); //should crash +CLOS.call('bump', new stick, new carpet); //shold silince + +CLOS.call('bump', new floor, new stick); // undefined method +CLOS.call('put', new glass, new floor); // undefined generic +``` diff --git a/clos.js b/clos.js index ad3e24d..dfbed22 100644 --- a/clos.js +++ b/clos.js @@ -7,67 +7,90 @@ var CLOS = {}; CLOS.generics = {}; +//JS class + +/* constructor for generic-function object */ CLOS.generic = function(name){ - this.name = name; - this.methods = []; + this.name = name; + this.methods = []; }; + +/* constructor for actual method generic functions delegates to */ CLOS.method = function(clause, body){ - this.clause = clause; - this.body = body; + this.clause = clause; + this.body = body; }; -CLOS.isA = function(example, standard){ - if(standard === undefined){ - return true; - } - if(example === standard){ - return true; - } - if(typeof(example) == standard){ - return true; - } - return false; -} + CLOS.method.prototype.check = function(parameters){ - var i; - for(i in this.clause){ - if(CLOS.isA(parameters[i], this.clause[i])){ - continue; - } - return false; - } - return true; + var i; + for(i in this.clause){ + if (CLOS.isA(parameters[i], this.clause[i])) + continue; + return false; + } + return true; +}; + +/* -- /CLOS.method -- */ + +/* classes are plain constructor function */ +CLOS.defClass = function (name, /*optional*/ fn) { + var cl = fn || function () {}; + cl.prototype.toString = function () { return name; }; + return cl; }; +//procedures + +CLOS.isA = function(example, standard){ + if(standard === undefined){ + return true; + } + if(example instanceof standard){ + return true; + } + if(typeof(example) == standard){ + return true; + } + return false; +}; + +/* (define-generic `name`) */ CLOS.defGeneric = function(name){ - CLOS.generics[name] = new CLOS.generic(name); + CLOS.generics[name] = new CLOS.generic(name); }; + +/* used internally */ CLOS.getGeneric = function(name){ - if(!CLOS.generics[name]){ - throw 'CLOS error: generic ' + name + ' is not defined'; - } - return CLOS.generics[name]; + if(!CLOS.generics[name]){ + throw 'CLOS error: generic ' + name + ' is not defined'; + } + return CLOS.generics[name]; }; +/* (define-method name ((arg1 ) (arg2 )) ...) */ +/* CLOS.defMethod("name", [class1, class2], function (arg1, arg2) { ... }) */ CLOS.defMethod = function(name, parameters, body){ - var generic = CLOS.getGeneric(name); - generic.methods[generic.methods.length] = new CLOS.method(parameters, body); + var generic = CLOS.getGeneric(name); + generic.methods.push(new CLOS.method(parameters, body)); }; +//call a generic function +//current implementation does not include dispatch precedence so it may call multiple methods CLOS.call = function(name){ - var generic = CLOS.getGeneric(name), - parameters = Array.prototype.slice.call(arguments, 1), - method, i; - for(i in generic.methods){ - method = generic.methods[i]; - if(method.check(parameters)){ - return method.body.apply(parameters); - } - } - throw 'CLOS error: cannot find method ' + name + ' for ' + parameters; + var generic = CLOS.getGeneric(name), + parameters = Array.prototype.slice.call(arguments, 1), + method, i; + //iterate over methods defined on the generic + for(i in generic.methods){ + method = generic.methods[i]; + //checks if the given parameter matches the declared type + if(method.check(parameters)){ + return method.body.apply({}, parameters); + } + } + throw 'CLOS error: cannot find method ' + name + ' for ' + parameters; }; -/*CLOS.init = function(object){ - object.clos = {}; - object.clos.prototype = CLOS; - object.clos.object = object; -};*/ + +module.exports = CLOS; diff --git a/test.js b/test.js index 41a0b79..3febdc9 100644 --- a/test.js +++ b/test.js @@ -1,56 +1,67 @@ +var CLOS = require('./clos'); + // our domain -var floor = {}; -var carpet = {}; -var ball = {}; -var glass = {}; -var stick = {}; +var floor = CLOS.defClass("floor"); +var carpet = CLOS.defClass("carpet"); +var ball = CLOS.defClass("ball"); +var glass = CLOS.defClass("glass"); +var stick = CLOS.defClass("stick"); var bumpOutput = function(x, y, result){ - alert(x + ' + ' + y + ' bump = ' + result); + console.log(x + ' + ' + y + ' bump = ' + result); }; var errorOutput = function(error){ - alert('[error] ' + error); + console.log('[error] ' + error); }; // definitions CLOS.defGeneric('bump'); -CLOS.gefMethod('bump', [ball, floor], function(x, y){ - bumpOutput(x, y, 'bounce'); +CLOS.defMethod('bump', [ball, floor], function(x, y){ + bumpOutput(x, y, 'bounce'); +}); +CLOS.defMethod('bump', [glass, floor], function(x, y){ + bumpOutput(x, y, 'crash'); }); -CLOS.gefMethod('bump', [glass, floor], function(x, y){ - bumpOutput(x, y, 'crash'); +CLOS.defMethod('bump', [stick, floor], function(x, y){ + bumpOutput(x, y, 'knock'); }); -CLOS.gefMethod('bump', [stick, floor], function(x, y){ - bumpOutput(x, y, 'knock'); +CLOS.defMethod('bump', [undefined, carpet], function(x, y){ + bumpOutput(x, y, 'silence'); }); -CLOS.gefMethod('bump', [undefined, carpet], function(x, y){ - bumpOutput(x, y, 'silence'); + +/* //equiv to +CLOS.defMethod('bump', [undefined, undefined], function (x, y) { + bumpOutput(x, y, ''); }); +*/ // test var tests = [ - function(){ - CLOS.call('bump', glass, floor); // crash - }, - function(){ - CLOS.call('bump', stick, carpet); // silence - }, - function(){ - CLOS.call('bump', floor, stick); // undefined method - }, - function(){ - CLOS.call('put', glass, floor); // undefined generic - } + function () { + CLOS.call('bump', new ball, new floor); //bounce + }, + function(){ + CLOS.call('bump', new glass, new floor); // crash + }, + function(){ + CLOS.call('bump', new stick, new carpet); // silence + }, + function(){ + CLOS.call('bump', new floor, new stick); // undefined method + }, + function(){ + CLOS.call('put', new glass, new floor); // undefined generic + } ]; for(var i in tests){ - var test = tests[i]; - try{ - test(); - } - catch(error){ - errorOutput(error); - } + var test = tests[i]; + try{ + test(); + } + catch(error){ + errorOutput(error); + } } From fc55ad1f05b1bb772b6c2a693facb9385e4202ec Mon Sep 17 00:00:00 2001 From: Minori Yamashita Date: Wed, 20 Feb 2013 01:55:39 +0900 Subject: [PATCH 02/12] Make defGeneric return a function. This function can be invoked in a normal JS way. --- clos.js | 176 ++++++++++++++++++++++++++++++-------------------------- test.js | 6 +- 2 files changed, 99 insertions(+), 83 deletions(-) diff --git a/clos.js b/clos.js index dfbed22..fd82516 100644 --- a/clos.js +++ b/clos.js @@ -4,93 +4,105 @@ * LLGPL -> http://opensource.franz.com/preamble.html */ -var CLOS = {}; -CLOS.generics = {}; - -//JS class - -/* constructor for generic-function object */ -CLOS.generic = function(name){ - this.name = name; - this.methods = []; -}; - -/* constructor for actual method generic functions delegates to */ -CLOS.method = function(clause, body){ - this.clause = clause; - this.body = body; -}; - -CLOS.method.prototype.check = function(parameters){ - var i; - for(i in this.clause){ - if (CLOS.isA(parameters[i], this.clause[i])) - continue; - return false; - } - return true; -}; +module.exports = (function () { + var CLOS = {}; //exported namespace -/* -- /CLOS.method -- */ + var generics = {}; -/* classes are plain constructor function */ -CLOS.defClass = function (name, /*optional*/ fn) { - var cl = fn || function () {}; - cl.prototype.toString = function () { return name; }; - return cl; -}; + var _slice = Array.prototype.slice; -//procedures + //JS class -CLOS.isA = function(example, standard){ - if(standard === undefined){ - return true; - } - if(example instanceof standard){ - return true; - } - if(typeof(example) == standard){ + /* constructor for generic-function object */ + function Generic (name) { + var self = function () { + CLOS.call.apply({}, [name].concat(_slice.call(arguments))); + }; + self.name = name; + self.methods = []; + return self; //this is valid + }; + + /* constructor for actual method generic functions delegates to */ + function Method (clause, body){ + this.clause = clause; + this.body = body; + }; + + Method.prototype.check = function(parameters){ + var i; + for(i in this.clause){ + if (CLOS.isA(parameters[i], this.clause[i])) + continue; + return false; + } return true; - } - return false; -}; - -/* (define-generic `name`) */ -CLOS.defGeneric = function(name){ - CLOS.generics[name] = new CLOS.generic(name); -}; - -/* used internally */ -CLOS.getGeneric = function(name){ - if(!CLOS.generics[name]){ - throw 'CLOS error: generic ' + name + ' is not defined'; - } - return CLOS.generics[name]; -}; - -/* (define-method name ((arg1 ) (arg2 )) ...) */ -/* CLOS.defMethod("name", [class1, class2], function (arg1, arg2) { ... }) */ -CLOS.defMethod = function(name, parameters, body){ - var generic = CLOS.getGeneric(name); - generic.methods.push(new CLOS.method(parameters, body)); -}; - -//call a generic function -//current implementation does not include dispatch precedence so it may call multiple methods -CLOS.call = function(name){ - var generic = CLOS.getGeneric(name), - parameters = Array.prototype.slice.call(arguments, 1), - method, i; - //iterate over methods defined on the generic - for(i in generic.methods){ - method = generic.methods[i]; - //checks if the given parameter matches the declared type - if(method.check(parameters)){ - return method.body.apply({}, parameters); + }; + + /* -- /Method -- */ + + /* classes are plain constructor function */ + CLOS.defClass = function (name, supr) { + var cl = function () {}; + supr = supr || function () {}; + cl.prototype = new supr; + cl.prototype.toString = function () { return name; }; + return cl; + }; + + //procedures + + CLOS.isA = function(example, standard){ + if(standard === undefined){ + return true; } - } - throw 'CLOS error: cannot find method ' + name + ' for ' + parameters; -}; + if(example instanceof standard){ + return true; + } + if(typeof(example) == standard){ + return true; + } + return false; + }; + + /* (define-generic `name`) */ + CLOS.defGeneric = function(name){ + return generics[name] = new Generic(name); + }; + + /* used internally */ + var getGeneric = function(name){ + if(!generics[name]){ + throw 'CLOS error: generic ' + name + ' is not defined'; + } + return generics[name]; + }; + + /* (define-method name ((arg1 ) (arg2 )) ...) */ + /* CLOS.defMethod("name", [class1, class2], function (arg1, arg2) { ... }) */ + CLOS.defMethod = function(name, parameters, body){ + var generic = getGeneric(name); + generic.methods.push(new Method(parameters, body)); + }; + + //call a generic function + //current implementation does not include dispatch precedence so it may call multiple methods + CLOS.call = function(name){ + var generic = getGeneric(name), + parameters = _slice.call(arguments, 1), + method, i; + //iterate over methods defined on the generic + for(i in generic.methods){ + method = generic.methods[i]; + //checks if the given parameter matches the declared type + if(method.check(parameters)){ + return method.body.apply({}, parameters); + } + } + throw 'CLOS error: cannot find method ' + name + ' for ' + parameters; + }; + + return CLOS; -module.exports = CLOS; +}()); diff --git a/test.js b/test.js index 3febdc9..6491eb3 100644 --- a/test.js +++ b/test.js @@ -17,7 +17,8 @@ var errorOutput = function(error){ // definitions -CLOS.defGeneric('bump'); +var bump = CLOS.defGeneric('bump'); + CLOS.defMethod('bump', [ball, floor], function(x, y){ bumpOutput(x, y, 'bounce'); }); @@ -54,6 +55,9 @@ var tests = [ }, function(){ CLOS.call('put', new glass, new floor); // undefined generic + }, + function () { + bump(new ball, new floor); } ]; for(var i in tests){ From d0c33d84cc6aed0df6b3f441cb34c6b393647328 Mon Sep 17 00:00:00 2001 From: Minori Yamashita Date: Wed, 20 Feb 2013 02:19:09 +0900 Subject: [PATCH 03/12] convinient syntax --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 9cffa1d..d0c27ec 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ var bumpOutput = function(x, y, result){ }; //define a generic function `bump` -CLOS.defGeneric('bump'); +var bump = CLOS.defGeneric('bump'); //define methods CLOS.defMethod('bump', [ball, floor], function(x, y){ @@ -38,10 +38,10 @@ CLOS.defMethod('bump', [undefined, carpet], function(x, y){ }); //call the methods -CLOS.call('bump', new ball, new floor); //should bounce -CLOS.call('bump', new glass, new floor); //should crash -CLOS.call('bump', new stick, new carpet); //shold silince +bump(new ball, new floor); //should bounce +bump(new glass, new floor); //should crash +bump(new stick, new carpet); //shold silince -CLOS.call('bump', new floor, new stick); // undefined method -CLOS.call('put', new glass, new floor); // undefined generic +bump(new floor, new stick); // undefined method +bump(new glass, new floor); // undefined generic ``` From 3b88bf44fdc4d1eca48f7e249e6229947d469cca Mon Sep 17 00:00:00 2001 From: Minori Yamashita Date: Wed, 20 Feb 2013 02:39:57 +0900 Subject: [PATCH 04/12] Go nameless. --- README.md | 14 +++++++++----- clos.js | 49 ++++++++++++++++++++----------------------------- test.js | 25 +++++++++++-------------- 3 files changed, 40 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index d0c27ec..e5fa903 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Usage ```javascript //define a bunch of classes +//the name is optional var floor = CLOS.defClass("floor"); var carpet = CLOS.defClass("carpet"); var ball = CLOS.defClass("ball"); @@ -21,19 +22,22 @@ var bumpOutput = function(x, y, result){ }; //define a generic function `bump` -var bump = CLOS.defGeneric('bump'); +var bump = CLOS.defGeneric(); //define methods -CLOS.defMethod('bump', [ball, floor], function(x, y){ +CLOS.defMethod(bump, [ball, floor], function(x, y){ bumpOutput(x, y, 'bounce'); }); -CLOS.defMethod('bump', [glass, floor], function(x, y){ +CLOS.defMethod(bump, [glass, floor], function(x, y){ bumpOutput(x, y, 'crash'); }); -CLOS.defMethod('bump', [stick, floor], function(x, y){ +CLOS.defMethod(bump, [stick, floor], function(x, y){ bumpOutput(x, y, 'knock'); }); -CLOS.defMethod('bump', [undefined, carpet], function(x, y){ + +//if you prefer, the following works, too + +bump.defMethod([undefined, carpet], function(x, y){ bumpOutput(x, y, 'silence'); }); diff --git a/clos.js b/clos.js index fd82516..5818717 100644 --- a/clos.js +++ b/clos.js @@ -14,11 +14,16 @@ module.exports = (function () { //JS class /* constructor for generic-function object */ - function Generic (name) { + function Generic () { + var self = function () { - CLOS.call.apply({}, [name].concat(_slice.call(arguments))); + _call.call(self, _slice.call(arguments)); + }; + + self.defMethod = function (parameters, body) { + self.methods.push(new Method(parameters, body)); }; - self.name = name; + self.methods = []; return self; //this is valid }; @@ -47,6 +52,7 @@ module.exports = (function () { supr = supr || function () {}; cl.prototype = new supr; cl.prototype.toString = function () { return name; }; + cl.prototype.isA = function (standard) { return CLOS.isA(this, standard); }; return cl; }; @@ -65,44 +71,29 @@ module.exports = (function () { return false; }; - /* (define-generic `name`) */ - CLOS.defGeneric = function(name){ - return generics[name] = new Generic(name); + /* (define-generic) */ + CLOS.defGeneric = function () { + return new Generic(); }; - /* used internally */ - var getGeneric = function(name){ - if(!generics[name]){ - throw 'CLOS error: generic ' + name + ' is not defined'; - } - return generics[name]; - }; - - /* (define-method name ((arg1 ) (arg2 )) ...) */ - /* CLOS.defMethod("name", [class1, class2], function (arg1, arg2) { ... }) */ - CLOS.defMethod = function(name, parameters, body){ - var generic = getGeneric(name); - generic.methods.push(new Method(parameters, body)); + //alias + CLOS.defMethod = function (generic, params, body) { + generic.defMethod(params, body); }; - //call a generic function - //current implementation does not include dispatch precedence so it may call multiple methods - CLOS.call = function(name){ - var generic = getGeneric(name), - parameters = _slice.call(arguments, 1), - method, i; + var _call = function (parameters) { + var method, i; //iterate over methods defined on the generic - for(i in generic.methods){ - method = generic.methods[i]; + for(i in this.methods){ + method = this.methods[i]; //checks if the given parameter matches the declared type if(method.check(parameters)){ return method.body.apply({}, parameters); } } - throw 'CLOS error: cannot find method ' + name + ' for ' + parameters; + throw 'CLOS error: cannot find specified method for ' + parameters; }; - return CLOS; }()); diff --git a/test.js b/test.js index 6491eb3..a5b83b3 100644 --- a/test.js +++ b/test.js @@ -17,23 +17,23 @@ var errorOutput = function(error){ // definitions -var bump = CLOS.defGeneric('bump'); +var bump = CLOS.defGeneric(); -CLOS.defMethod('bump', [ball, floor], function(x, y){ +CLOS.defMethod(bump, [ball, floor], function(x, y){ bumpOutput(x, y, 'bounce'); }); -CLOS.defMethod('bump', [glass, floor], function(x, y){ +CLOS.defMethod(bump, [glass, floor], function(x, y){ bumpOutput(x, y, 'crash'); }); -CLOS.defMethod('bump', [stick, floor], function(x, y){ +CLOS.defMethod(bump, [stick, floor], function(x, y){ bumpOutput(x, y, 'knock'); }); -CLOS.defMethod('bump', [undefined, carpet], function(x, y){ +CLOS.defMethod(bump, [undefined, carpet], function(x, y){ bumpOutput(x, y, 'silence'); }); /* //equiv to -CLOS.defMethod('bump', [undefined, undefined], function (x, y) { +CLOS.defMethod(bump, [undefined, undefined], function (x, y) { bumpOutput(x, y, ''); }); */ @@ -42,22 +42,19 @@ CLOS.defMethod('bump', [undefined, undefined], function (x, y) { var tests = [ function () { - CLOS.call('bump', new ball, new floor); //bounce + bump(new ball, new floor); //bounce }, function(){ - CLOS.call('bump', new glass, new floor); // crash + bump(new glass, new floor); // crash }, function(){ - CLOS.call('bump', new stick, new carpet); // silence + bump(new stick, new carpet); // silence }, function(){ - CLOS.call('bump', new floor, new stick); // undefined method - }, - function(){ - CLOS.call('put', new glass, new floor); // undefined generic + bump(new floor, new stick); // undefined method }, function () { - bump(new ball, new floor); + bump(new ball, new floor); //bounce } ]; for(var i in tests){ From 7eec96d266514ad3176d00ed7dc2b102cb40ece4 Mon Sep 17 00:00:00 2001 From: ympbyc Date: Wed, 20 Feb 2013 15:29:15 +0900 Subject: [PATCH 05/12] Enhance CLOS.defClass. Instances can now have slots that are compatible with the protocol given to defClass --- README.md | 44 ++++++++++++++++++++++++++++++++++---------- clos.js | 48 +++++++++++++++++++++++++++++++----------------- test.js | 28 +++++++++++++++++++++++----- 3 files changed, 88 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index e5fa903..ccc81d4 100644 --- a/README.md +++ b/README.md @@ -7,14 +7,39 @@ A CLOS-like framework sketch on JavaScript. Usage ----- +### Simple Data Class ### + +```javascript +//class, when `make`d, retruns a hash of values +var _book_ = define_class(function (x) { + return slot_exists(x, 'title', "string") + && slot_exists(x, 'author', "string"); +}); + +//generic function show +var show = define_generic(); + +//show an instance of book +define_method(show, [_book_], function (b) { + return b.title + " by " b.author; +}); + +var p_city = make(_book_, {title:'Permutation City', author:'Greg Egan'}); + +show(p_city); +``` + + +### Multimethod ### + ```javascript //define a bunch of classes //the name is optional -var floor = CLOS.defClass("floor"); -var carpet = CLOS.defClass("carpet"); -var ball = CLOS.defClass("ball"); -var glass = CLOS.defClass("glass"); -var stick = CLOS.defClass("stick"); +var floor = define_class(undefined, "floor"); +var carpet = define_class(undefined, "carpet"); +var ball = define_class(undefined, "ball"); +var glass = define_class(undefined, "glass"); +var stick = define_class(undefined, "stick"); //function to display the result var bumpOutput = function(x, y, result){ @@ -22,21 +47,20 @@ var bumpOutput = function(x, y, result){ }; //define a generic function `bump` -var bump = CLOS.defGeneric(); +var bump = define_generic(); //define methods -CLOS.defMethod(bump, [ball, floor], function(x, y){ +define_method(bump, [ball, floor], function(x, y){ bumpOutput(x, y, 'bounce'); }); -CLOS.defMethod(bump, [glass, floor], function(x, y){ +define_method(bump, [glass, floor], function(x, y){ bumpOutput(x, y, 'crash'); }); -CLOS.defMethod(bump, [stick, floor], function(x, y){ +define_method(bump, [stick, floor], function(x, y){ bumpOutput(x, y, 'knock'); }); //if you prefer, the following works, too - bump.defMethod([undefined, carpet], function(x, y){ bumpOutput(x, y, 'silence'); }); diff --git a/clos.js b/clos.js index 5818717..e733fe9 100644 --- a/clos.js +++ b/clos.js @@ -46,29 +46,29 @@ module.exports = (function () { /* -- /Method -- */ - /* classes are plain constructor function */ - CLOS.defClass = function (name, supr) { - var cl = function () {}; - supr = supr || function () {}; - cl.prototype = new supr; - cl.prototype.toString = function () { return name; }; - cl.prototype.isA = function (standard) { return CLOS.isA(this, standard); }; + /* classes are constructor functions */ + /* The constructor may take a predicate function that ensures its instances + * to have specific properties */ + CLOS.defClass = function (pred, name) { + pred = pred || function () {return true;}; + var cl = function (obj) { + var key; + if ( ! pred(obj)) throw "Initialization error"; + for (key in obj) + if (obj.hasOwnProperty(key)) + this[key] = obj[key]; + }; + cl.prototype.toString = function () { return name || 'aCLOSClass'; }; + cl.prototype.isA = function (standard) { return CLOS.isA(this, standard); }; return cl; }; //procedures CLOS.isA = function(example, standard){ - if(standard === undefined){ - return true; - } - if(example instanceof standard){ - return true; - } - if(typeof(example) == standard){ - return true; - } - return false; + return (standard === undefined) + || (typeof(example) == standard) + || (example instanceof standard); }; /* (define-generic) */ @@ -94,6 +94,20 @@ module.exports = (function () { throw 'CLOS error: cannot find specified method for ' + parameters; }; + //for schemer + CLOS.define_method = CLOS.defMethod; + CLOS.define_generic = CLOS.defGeneric; + CLOS.define_class = CLOS.define_class; + + CLOS.slot_exists = function (obj, slot, cls) { + return (obj[slot] !== undefined) && CLOS.isA(obj[slot], cls); + }; + + //alias to `new` + CLOS.make = function (cls, obj) { + return new cls(obj); + }; + return CLOS; }()); diff --git a/test.js b/test.js index a5b83b3..5f964bb 100644 --- a/test.js +++ b/test.js @@ -2,11 +2,11 @@ var CLOS = require('./clos'); // our domain -var floor = CLOS.defClass("floor"); -var carpet = CLOS.defClass("carpet"); -var ball = CLOS.defClass("ball"); -var glass = CLOS.defClass("glass"); -var stick = CLOS.defClass("stick"); +var floor = CLOS.defClass(undefined, "floor"); +var carpet = CLOS.defClass(undefined, "carpet"); +var ball = CLOS.defClass(undefined, "ball"); +var glass = CLOS.defClass(undefined, "glass"); +var stick = CLOS.defClass(undefined, "stick"); var bumpOutput = function(x, y, result){ console.log(x + ' + ' + y + ' bump = ' + result); @@ -38,6 +38,16 @@ CLOS.defMethod(bump, [undefined, undefined], function (x, y) { }); */ +var Book = CLOS.defClass(function (x) { + return CLOS.slot_exists(x, 'title', "string") && CLOS.slot_exists(x, 'author', "string"); +}); + +var show = CLOS.defGeneric(); + +CLOS.defMethod(show, [Book], function (b) { + console.log(b.title + " by " + b.author); +}); + // test var tests = [ @@ -55,6 +65,14 @@ var tests = [ }, function () { bump(new ball, new floor); //bounce + }, + + function () { + show(CLOS.make(Book, {title:'Permutation City', author:'Greg Egan'})); + //Permutation City by Greg Egan + }, + function () { + CLOS.make(Book, {}); //Initialization error } ]; for(var i in tests){ From 3ee540d46a00fa34f35b66bf172d74cda5a0668e Mon Sep 17 00:00:00 2001 From: Minori Yamashita Date: Thu, 21 Feb 2013 00:01:29 +0900 Subject: [PATCH 06/12] Multiple inheritance --- README.md | 21 ++++++++++++--------- clos.js | 15 ++++++++++----- test.js | 36 +++++++++++++++++++++++++++++------- 3 files changed, 51 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index ccc81d4..f7b5780 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@ JS-CLOS ======= -A CLOS-like framework sketch on JavaScript. +A CLOS-like object system in JavaScript. + ++ Multiple inheritance ++ Multimethod ++ Type checking on construction Usage @@ -11,7 +15,7 @@ Usage ```javascript //class, when `make`d, retruns a hash of values -var _book_ = define_class(function (x) { +var _book_ = define_class([], function (x) { return slot_exists(x, 'title', "string") && slot_exists(x, 'author', "string"); }); @@ -21,7 +25,7 @@ var show = define_generic(); //show an instance of book define_method(show, [_book_], function (b) { - return b.title + " by " b.author; + return b.title + " by " + b.author; }); var p_city = make(_book_, {title:'Permutation City', author:'Greg Egan'}); @@ -35,11 +39,11 @@ show(p_city); ```javascript //define a bunch of classes //the name is optional -var floor = define_class(undefined, "floor"); -var carpet = define_class(undefined, "carpet"); -var ball = define_class(undefined, "ball"); -var glass = define_class(undefined, "glass"); -var stick = define_class(undefined, "stick"); +var floor = define_class([], undefined, "floor"); +var carpet = define_class([], undefined, "carpet"); +var ball = define_class([], undefined, "ball"); +var glass = define_class([], undefined, "glass"); +var stick = define_class([], undefined, "stick"); //function to display the result var bumpOutput = function(x, y, result){ @@ -71,5 +75,4 @@ bump(new glass, new floor); //should crash bump(new stick, new carpet); //shold silince bump(new floor, new stick); // undefined method -bump(new glass, new floor); // undefined generic ``` diff --git a/clos.js b/clos.js index e733fe9..4122990 100644 --- a/clos.js +++ b/clos.js @@ -49,16 +49,19 @@ module.exports = (function () { /* classes are constructor functions */ /* The constructor may take a predicate function that ensures its instances * to have specific properties */ - CLOS.defClass = function (pred, name) { + CLOS.defClass = function (parents, pred, name) { pred = pred || function () {return true;}; var cl = function (obj) { var key; + parents.forEach(function (p) { return p._pred(obj); }); //check for exception if ( ! pred(obj)) throw "Initialization error"; for (key in obj) if (obj.hasOwnProperty(key)) this[key] = obj[key]; }; - cl.prototype.toString = function () { return name || 'aCLOSClass'; }; + cl._pred = pred; + cl.prototype._parents = parents; + cl.prototype.toString = function () { return name || JSON.stringify(this); }; cl.prototype.isA = function (standard) { return CLOS.isA(this, standard); }; return cl; }; @@ -68,7 +71,8 @@ module.exports = (function () { CLOS.isA = function(example, standard){ return (standard === undefined) || (typeof(example) == standard) - || (example instanceof standard); + || (example instanceof standard) + || (example._parents && example._parents.indexOf(standard) > -1); }; /* (define-generic) */ @@ -97,10 +101,11 @@ module.exports = (function () { //for schemer CLOS.define_method = CLOS.defMethod; CLOS.define_generic = CLOS.defGeneric; - CLOS.define_class = CLOS.define_class; + CLOS.define_class = CLOS.defClass; CLOS.slot_exists = function (obj, slot, cls) { - return (obj[slot] !== undefined) && CLOS.isA(obj[slot], cls); + return (obj[slot] !== undefined) + && cls ? CLOS.isA(obj[slot], cls) : true; }; //alias to `new` diff --git a/test.js b/test.js index 5f964bb..bacc4cf 100644 --- a/test.js +++ b/test.js @@ -2,11 +2,11 @@ var CLOS = require('./clos'); // our domain -var floor = CLOS.defClass(undefined, "floor"); -var carpet = CLOS.defClass(undefined, "carpet"); -var ball = CLOS.defClass(undefined, "ball"); -var glass = CLOS.defClass(undefined, "glass"); -var stick = CLOS.defClass(undefined, "stick"); +var floor = CLOS.defClass([], undefined, "floor"); +var carpet = CLOS.defClass([], undefined, "carpet"); +var ball = CLOS.defClass([], undefined, "ball"); +var glass = CLOS.defClass([], undefined, "glass"); +var stick = CLOS.defClass([], undefined, "stick"); var bumpOutput = function(x, y, result){ console.log(x + ' + ' + y + ' bump = ' + result); @@ -38,9 +38,14 @@ CLOS.defMethod(bump, [undefined, undefined], function (x, y) { }); */ -var Book = CLOS.defClass(function (x) { - return CLOS.slot_exists(x, 'title', "string") && CLOS.slot_exists(x, 'author', "string"); +var Book = CLOS.defClass([], function (x) { + return CLOS.slot_exists(x, 'title', 'string') + && CLOS.slot_exists(x, 'author', 'string'); }); +var Flammable = CLOS.defClass([], function (x) { + return CLOS.slot_exists(x, 'burnTime', 'number'); +}); +var Magazine = CLOS.defClass([Book, Flammable]); var show = CLOS.defGeneric(); @@ -48,6 +53,15 @@ CLOS.defMethod(show, [Book], function (b) { console.log(b.title + " by " + b.author); }); +var burn = CLOS.defGeneric(); + +CLOS.defMethod(burn, [Magazine], function (m) { + console.log(m.title + " burnt in " + m.burnTime + " seconds."); +}); +CLOS.defMethod(burn, [Flammable], function (f) { + console.log(f + " burnt in " + f.burnTime + " seconds."); +}); + // test var tests = [ @@ -73,6 +87,14 @@ var tests = [ }, function () { CLOS.make(Book, {}); //Initialization error + }, + function () { + show(CLOS.make(Magazine, {title:'Foo', author:'Bar', burnTime:5000})); + //Foo by Bar + }, + function () { + burn(CLOS.make(Flammable, {burnTime: 5, name: "gas tank"})); + burn(CLOS.make(Magazine, {burnTime: 20, title:"Foo", author:"Bar"})); } ]; for(var i in tests){ From 289a5536c441cdf4c05447ae2712631d6092c9b4 Mon Sep 17 00:00:00 2001 From: Minori Yamashita Date: Thu, 21 Feb 2013 01:01:44 +0900 Subject: [PATCH 07/12] API documentation --- README.md | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ clos.js | 11 +++++- 2 files changed, 119 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f7b5780..fbed08d 100644 --- a/README.md +++ b/README.md @@ -76,3 +76,112 @@ bump(new stick, new carpet); //shold silince bump(new floor, new stick); // undefined method ``` + +API +--- + +### define_class ### + +Takes: ++ an arrey of classes to inherit from. *(required)* ++ a validator function called upon instance construction. *(optional)* ++ a name string which is used as the string representation of its instances *(optional)* + +Returns a constructor function (called **class**) that can be `new`ed or get applied to `make` + +#### Syntax #### +**define_class**([ *parent classes* ], function (x) { *protocol* }, *name*); + +#### Example ### + +```javascript +var x = define_class([]); +var y = define_class([x]); +var z = define_class([x, y], funciton (a) { + return slot_exists(a, 'name', 'string'); +}); +``` + +### define_generic ### + +Takes nothing. + +Returns **a generic function**. + +#### Syntax #### +**define_generic**(); + +#### Example #### + +```javascript +var show = define_generic(); +``` + +### define_method ### + +Takes: ++ **a generic function** *(required)* ++ an array of **classes** that specifies the type of arguments given to the method *(required)* ++ a function that is the body of the method *(required)* + +Returns void. + +#### Syntax #### +**define_method**( *generic function* , [ *classA* , *...* ], function ( *a* , *...* ) { *body* }); + +#### Example #### + +```javascript +define_method(show, [z], function (a) { + console.log(a.name); +}); + +define_method(show, [x], function (a) { + console.log("an instance of x"); +}); +``` + +### make ### + +Takes: ++ **a class** *(required)* ++ a hash object specifying the initial values of each slot *(optional)* + +Returns an instance of the class. + +#### Syntax #### + +**make**( *class*, { *slot_name*: *initial_value*, *...*}); + +#### Example #### + +```javascript +make(x); +make(z, {name: "foo"}); +make(z); //ERROR +``` + +#### Note #### + +The hash object given as the second argument is matched with the function given as the second argument to `define_class`. If the result is false, an exception gets thrown. + +### is_a ### + +Takes: ++ an instance *(required)* ++ a class or a string *(required)* + +Returns boolean + +### slot_exists ### + +Takes: ++ an instance *(required)* ++ a slot identifier *(required)* ++ a class or a string to specify the type *(optional)* + +Returns a boolean. True if the instance has the slot of the specified type. False otherwise. + +### defClass, defGeneric, defMethod, isA ### + +Aliaces. diff --git a/clos.js b/clos.js index 4122990..1810e22 100644 --- a/clos.js +++ b/clos.js @@ -4,6 +4,14 @@ * LLGPL -> http://opensource.franz.com/preamble.html */ +if ( ! Array.prototype.forEach) + Array.prototype.forEach = function (f) { + var i = 0, l = this.length; + for (; i < l; ++i) + f(this[i], i); + }; + + module.exports = (function () { var CLOS = {}; //exported namespace @@ -35,7 +43,7 @@ module.exports = (function () { }; Method.prototype.check = function(parameters){ - var i; + var i, self = this; for(i in this.clause){ if (CLOS.isA(parameters[i], this.clause[i])) continue; @@ -102,6 +110,7 @@ module.exports = (function () { CLOS.define_method = CLOS.defMethod; CLOS.define_generic = CLOS.defGeneric; CLOS.define_class = CLOS.defClass; + CLOS.is_a = CLOS.isA; CLOS.slot_exists = function (obj, slot, cls) { return (obj[slot] !== undefined) From 3f066b15c997c8d39f00c94546ebd6b2499de7d2 Mon Sep 17 00:00:00 2001 From: Minori Yamashita Date: Thu, 21 Feb 2013 01:48:30 +0900 Subject: [PATCH 08/12] Let methods return values --- clos.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clos.js b/clos.js index 1810e22..b71706f 100644 --- a/clos.js +++ b/clos.js @@ -25,7 +25,7 @@ module.exports = (function () { function Generic () { var self = function () { - _call.call(self, _slice.call(arguments)); + return _call.call(self, _slice.call(arguments)); }; self.defMethod = function (parameters, body) { From 3d8f673f3f9ab32cd76221abae6132052234a866 Mon Sep 17 00:00:00 2001 From: Minori Yamashita Date: Thu, 21 Feb 2013 03:24:53 +0900 Subject: [PATCH 09/12] Sort methods by specificity --- clos.js | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/clos.js b/clos.js index b71706f..38b2fd9 100644 --- a/clos.js +++ b/clos.js @@ -15,8 +15,6 @@ if ( ! Array.prototype.forEach) module.exports = (function () { var CLOS = {}; //exported namespace - var generics = {}; - var _slice = Array.prototype.slice; //JS class @@ -30,12 +28,27 @@ module.exports = (function () { self.defMethod = function (parameters, body) { self.methods.push(new Method(parameters, body)); + self.methods.sort(specificity); }; self.methods = []; return self; //this is valid }; + //sort function + function specificity (a, b) { + var aWin = 0, bWin = 0, i = 0, l = a.clause.length; + for (; i < l; ++i) { + if (CLOS.isA(a.clause[i], b.clause[i])) ++bWin; + if (CLOS.isA(b.clause[i], a.clause[i])) ++aWin; + } + return aWin < bWin + ? -1 + : aWin > bWin + ? 1 + : 0; + }; + /* constructor for actual method generic functions delegates to */ function Method (clause, body){ this.clause = clause; @@ -68,6 +81,7 @@ module.exports = (function () { this[key] = obj[key]; }; cl._pred = pred; + cl._parents = parents; cl.prototype._parents = parents; cl.prototype.toString = function () { return name || JSON.stringify(this); }; cl.prototype.isA = function (standard) { return CLOS.isA(this, standard); }; @@ -77,8 +91,10 @@ module.exports = (function () { //procedures CLOS.isA = function(example, standard){ + if (!example) return false; + if (typeof(standard) == "string") + return (typeof(example) == standard); return (standard === undefined) - || (typeof(example) == standard) || (example instanceof standard) || (example._parents && example._parents.indexOf(standard) > -1); }; From 0b3d7a41d716a09431b0f62cd2f6646672604ac8 Mon Sep 17 00:00:00 2001 From: Minori Yamashita Date: Thu, 21 Feb 2013 04:21:38 +0900 Subject: [PATCH 10/12] search parent recursively. sort methods more precisely --- clos.js | 31 ++++++++++++++++++++++++------- test.js | 17 +++++++++++++++++ 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/clos.js b/clos.js index 38b2fd9..c492e76 100644 --- a/clos.js +++ b/clos.js @@ -17,6 +17,10 @@ module.exports = (function () { var _slice = Array.prototype.slice; + CLOS.options = {}; + CLOS.options.dispatchBasedOnSpecificity = true; + CLOS.options.deepLookupParent = true; + //JS class /* constructor for generic-function object */ @@ -28,7 +32,8 @@ module.exports = (function () { self.defMethod = function (parameters, body) { self.methods.push(new Method(parameters, body)); - self.methods.sort(specificity); + if (CLOS.options.dispatchBasedOnSpecificity) + self.methods.sort(specificity); }; self.methods = []; @@ -42,11 +47,7 @@ module.exports = (function () { if (CLOS.isA(a.clause[i], b.clause[i])) ++bWin; if (CLOS.isA(b.clause[i], a.clause[i])) ++aWin; } - return aWin < bWin - ? -1 - : aWin > bWin - ? 1 - : 0; + return aWin - bWin; }; /* constructor for actual method generic functions delegates to */ @@ -96,15 +97,31 @@ module.exports = (function () { return (typeof(example) == standard); return (standard === undefined) || (example instanceof standard) - || (example._parents && example._parents.indexOf(standard) > -1); + || hasParent(example._parents, standard); }; + var hasParent; + if (CLOS.options.deepLookupParent) + //recursive function to see if the list of parents include the class + hasParent = function (parents, standard) { + if ( ! parents || !parents[0]) return false; + if (parents[0] === standard) return true; + return hasParent(parents[0]._parents, standard) || hasParent(parents.slice(1), standard); + }; + else + //shallow lookup + hasParent = function (parents, standard) { + return parents.indexOf(standard) > -1; + }; + + /* (define-generic) */ CLOS.defGeneric = function () { return new Generic(); }; //alias + //this function is expensive CLOS.defMethod = function (generic, params, body) { generic.defMethod(params, body); }; diff --git a/test.js b/test.js index bacc4cf..ef28890 100644 --- a/test.js +++ b/test.js @@ -62,6 +62,17 @@ CLOS.defMethod(burn, [Flammable], function (f) { console.log(f + " burnt in " + f.burnTime + " seconds."); }); + +//method precedence test +var Foo = CLOS.defClass([]); +var Bar = CLOS.defClass([Foo]); +var Baz = CLOS.defClass([Bar]); +var alice = CLOS.defGeneric(); +CLOS.defMethod(alice, [Bar, Foo], function () { console.log("bar foo"); }); +CLOS.defMethod(alice, [Bar, Bar], function () { console.log("bar bar"); }); +CLOS.defMethod(alice, [Baz, Baz], function () { console.log("baz baz"); }); +CLOS.defMethod(alice, [Bar, Baz], function () { console.log("bar baz"); }); + // test var tests = [ @@ -95,6 +106,12 @@ var tests = [ function () { burn(CLOS.make(Flammable, {burnTime: 5, name: "gas tank"})); burn(CLOS.make(Magazine, {burnTime: 20, title:"Foo", author:"Bar"})); + }, + function () { + alice(CLOS.make(Baz), CLOS.make(Baz)); //baz baz + alice(CLOS.make(Baz), CLOS.make(Bar)); //bar bar + alice(CLOS.make(Baz), CLOS.make(Foo)); //bar foo + alice(CLOS.make(Bar), CLOS.make(Baz)); //bar baz } ]; for(var i in tests){ From a873fce18896bdff222e855b6945400121272c55 Mon Sep 17 00:00:00 2001 From: ympbyc Date: Thu, 21 Feb 2013 11:19:08 +0900 Subject: [PATCH 11/12] flatten the list of parents on construction to reduce lookup cost --- clos.js | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/clos.js b/clos.js index c492e76..1faaf06 100644 --- a/clos.js +++ b/clos.js @@ -19,7 +19,6 @@ module.exports = (function () { CLOS.options = {}; CLOS.options.dispatchBasedOnSpecificity = true; - CLOS.options.deepLookupParent = true; //JS class @@ -81,9 +80,12 @@ module.exports = (function () { if (obj.hasOwnProperty(key)) this[key] = obj[key]; }; + var flatParents = parents.reduce(function (acc, cur) { + return acc.concat(cur._parents); }, parents); cl._pred = pred; - cl._parents = parents; - cl.prototype._parents = parents; + cl.prototype.constructor = cl; + cl._parents = flatParents; + cl.prototype._parents = flatParents; cl.prototype.toString = function () { return name || JSON.stringify(this); }; cl.prototype.isA = function (standard) { return CLOS.isA(this, standard); }; return cl; @@ -96,23 +98,14 @@ module.exports = (function () { if (typeof(standard) == "string") return (typeof(example) == standard); return (standard === undefined) + || (example === standard) //compare classes || (example instanceof standard) || hasParent(example._parents, standard); }; - var hasParent; - if (CLOS.options.deepLookupParent) - //recursive function to see if the list of parents include the class - hasParent = function (parents, standard) { - if ( ! parents || !parents[0]) return false; - if (parents[0] === standard) return true; - return hasParent(parents[0]._parents, standard) || hasParent(parents.slice(1), standard); - }; - else - //shallow lookup - hasParent = function (parents, standard) { - return parents.indexOf(standard) > -1; - }; + function hasParent (parents, standard) { + return parents.indexOf(standard) > -1; + }; /* (define-generic) */ From e23f80bd4db50e8f6ae0b29a7a4c5fe7d0771e2e Mon Sep 17 00:00:00 2001 From: ympbyc Date: Thu, 21 Feb 2013 11:42:12 +0900 Subject: [PATCH 12/12] allow immidiate values in method clauses --- clos.js | 33 +++++++++++++++++++++++++-------- test.js | 10 ++++++++++ 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/clos.js b/clos.js index 1faaf06..4ede347 100644 --- a/clos.js +++ b/clos.js @@ -93,17 +93,34 @@ module.exports = (function () { //procedures - CLOS.isA = function(example, standard){ - if (!example) return false; - if (typeof(standard) == "string") - return (typeof(example) == standard); - return (standard === undefined) - || (example === standard) //compare classes - || (example instanceof standard) - || hasParent(example._parents, standard); + //more like a pattern-matching + /** + * passes when: + * example === standard + * standard === undefined + * typeof(example) == standard + * example instanceof standard + * member(example._parent, standard) + */ + CLOS.isA = function (example, standard) { + if (example === standard) return true; + if (! example) return false; + switch(typeof(standard)) { + case "undefined": + return true; + case "string": + return (typeof(example) == standard); + case "function": + case "object": + return (example instanceof standard) + || hasParent(example._parents, standard); + default: + return false; + } }; function hasParent (parents, standard) { + if ( ! parents) return false; return parents.indexOf(standard) > -1; }; diff --git a/test.js b/test.js index ef28890..fd6a752 100644 --- a/test.js +++ b/test.js @@ -73,6 +73,13 @@ CLOS.defMethod(alice, [Bar, Bar], function () { console.log("bar bar"); }); CLOS.defMethod(alice, [Baz, Baz], function () { console.log("baz baz"); }); CLOS.defMethod(alice, [Bar, Baz], function () { console.log("bar baz"); }); +//immidiate values +var fib = CLOS.define_generic(); +CLOS.define_method(fib, [0], function (_) { return 1; }); +CLOS.define_method(fib, [1], function (_) { return 1; }); +CLOS.define_method(fib, ["number"], function (n) { + return fib(n - 1) + fib(n - 2); }); + // test var tests = [ @@ -112,6 +119,9 @@ var tests = [ alice(CLOS.make(Baz), CLOS.make(Bar)); //bar bar alice(CLOS.make(Baz), CLOS.make(Foo)); //bar foo alice(CLOS.make(Bar), CLOS.make(Baz)); //bar baz + }, + function () { + console.log(fib(10)); } ]; for(var i in tests){