Skip to content

Instantly share code, notes, and snippets.

@clausreinke
Created February 20, 2013 17:23
Show Gist options
  • Save clausreinke/4997274 to your computer and use it in GitHub Desktop.
Save clausreinke/4997274 to your computer and use it in GitHub Desktop.
JavaScript: generic methods, programming to interfaces, classes implementing interfaces; a sketch (load in browser or run with nodejs)
// see console output <script>
// quick hack of classes implementing interfaces, just to give the idea; for a
// proper design, feel free to use better syntax, (Weak)Maps, unique symbols, ..;
//
// the core idea is to relate interfaces to classes that implement them, in
// such a way that the relevant implementations of interface methods can be
// extracted systematically in generic code
var Mappable = {}; // interface, to be implemented by classes;
// for simplicity, we index by class name:
// Mappable.<class>.map(<target>,<function>)
// Array implements Mappable
Mappable.Array = { map : function(a,f) {
return Array.prototype.map.call(a,f)
}
};
// String implements Mappable
Mappable.String = { map : function(s,f) {
return Array.prototype.map.call(s,f).join("")
}
};
// typed arrays implement Mappable, Int32Array as example
Mappable.Int32Array = { map : function(i32a,f) {
var l = i32a.length;
var buf = new ArrayBuffer(l*4);
var res = new Int32Array(buf);
for (var i=0; i<i32a.length; i++)
res[i] = f(i32a[i]);
return res;
}
};
// copy and modify for the other typed arrays.. highly inelegant;
//
// better would be to separate Array<>-specific loop and <element>-specific
// size/conversion/whatever;
//
// to get there, we'd need to turn Int32Array into Array<Int32> and have
// support for letting Array<elemtype> implement Mappable, with code access
// to interfaces implemented by either Array<> or elemtype
// a map for all seasons; generic, extensible
function genericMap(x,xElemMapper) {
var xClassName = x.constructor.name; // console.log("// "+xClassName);
return Mappable[xClassName].map(x,xElemMapper) ;
}
// a curried map, swapped arguments for easier composition
function curriedMap(xElemMapper) { return function(x) {
return genericMap(x,xElemMapper)
}}
// TODO: iterable should implement Mappable, so from can be separate from map
// handle Dictionary (no .constructor)
// error handling in genericMap
// ------ testing
// some container data and element functions
var arr_num = [0,1,2,3];
var arr_arr_num = [[1,2,3],[3,2,1]];
var arr_arr_str = [["hi","there"],["gdkkn","vnqkc"]];
var str = "hello";
var i32arr = new Int32Array([2,3,4]);
var numf1 = function(x) { return x+1 };
var numf2 = function(x) { return x/2 };
var charf = function(c) { return String.fromCharCode(c.charCodeAt(0)+1) };
console.log("\n// mapping over an array of numbers, explicit class");
console.log( Mappable.Array.map(arr_num,numf1) );
console.log("\n// mapping over a typed array of numbers, explicit class");
console.log( Mappable.Int32Array.map(i32arr,numf2) );
console.log("\n// Int32Array map differs from Array map");
console.log( Mappable.Array.map(i32arr,numf2) );
console.log("\n// mapping over a String, explicit classes;");
console.log( Mappable.String.map(str,charf) );
console.log("\n// String map differs from Array map");
console.log( Mappable.Array.map(str,charf) );
console.log("\n-----------------");
console.log("\n// mapping over array, typed array and string, generically");
console.log( genericMap(arr_num,numf1) );
console.log( genericMap(i32arr,numf1) );
console.log( genericMap(str,charf) );
console.log("\n-----------------");
console.log("\n// deep, nested mapping in nested containers, generically");
console.log( curriedMap(curriedMap(numf1))(arr_arr_num) );
console.log( curriedMap(curriedMap(curriedMap(charf)))(arr_arr_str) );
// ------ another mapping example, nodejs-specific code, needs Q, optional;
// demonstrates that map works for non-containers, as well
if (typeof require!="undefined") {
console.log("\n-----------------");
var Q = require("q");
// Q promise implements Mappable
Mappable.makePromise = { map : function(q,f) {
var mkQ = function(val) {
return Q.fcall(function(){return f(val) })
};
return q.then( mkQ )
}
};
var filePromise = Q.nfcall(require("fs").readFile,__filename,"utf-8");
var head = function(text) { return text.split("\n").slice(1,8).join("\n") };
console.log("\n// generically mapping over a promise");
genericMap( filePromise, head ).then(console.log);
/* */
}
// </script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment