One of the less used, but widely distributed macro processors (M4) can be transformed into a functional language. After reading http://www.cs.stir.ac.uk/~kjt/research/pdf/expl-m4.pdf
I decided to create some list processing functions (Foldr,Foldl,Head,Tail,Append,Prepend,Null,Sum,Filter)
To implement Foldr and Foldl I have used CPS (continuation passing style), so these functions are difficult to read, but easily testable. Just fireup haskell and try: foldr (-) 0 [3,2,1] -> 2 and foldl should give -6. So Foldl(`Sub',0,[3;2;1]) should give -6 too.
Some logic operators (Not,And,Or,isBool)
Adding support for 'lambdas' -not really, but it is close- and add support for currying.
Maybe I change it in a real project some day. It is rather fun and with foldl and foldr a lot of functions from Data.List can be ported. The real challenge is adding monads. I am not sure if that is possible yet, but I have the feeling it probably can.
Of course there is no type system, but it suprised me how easily most functions could be implemented.
An example:
Lambda(x,`eval($1-($2))')dnl
`foldr'(`x',0,[3;2;1]) is : Foldr(`x',0,t)
`foldl'(`x',0,[3;2;1]) is : Foldl(`x',0,t)
The output of this is:
foldr(x,0,[3;2;1]) is : 2
foldl(x,0,[3;2;1]) is : -6
Even cooler is that you can write your testcases in haskell. Like:
test1 = `foldr' (-) 0 [3,2,1] == Foldr(`x',0,`[3;2;1;]')
Which gives us in ghci:
test1 = foldr (-) 0 [3,2,1] == (2)
ann@ann-desktop:~$ ghci
GHCi, version 6.12.2: http://www.haskell.org/ghc/ :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Loading package ffi-1.0 ... linking ... done.
Prelude> foldr (-) 0 [3,2,1] == (2)
True
Prelude>
This is the minilib, it is somewhat dense, but so I could use it in a project:
changecom(`#',`')dnl
dnl# `Id`
define(Id, $1)dnl
dnl# `Lambda'
define(Lambda, `define($1, `$2')')dnl
define(Curry1, `define($1,`$2'($3,$`'1,$`'2,$`'3,$`'4))')dnl
define(Curry2, `define($1,`$2'($3,$4,$`'1,$`'2,$`'3,$`'4))')dnl
define(Curry3, `define($1,`$2'($3,$4,$5,$`'1,$`'2,$`'3,$`'4))')dnl
define(Curry4, `define($1,`$2'($3,$4,$5,$6,$`'1,$`'2,$`'3,$`'4))')dnl
define(Curry5, `define($1,`$2'($3,$4,$5,$6,$7,$`'1,$`'2,$`'3,$`'4))')dnl
define(Curry6, `define($1,`$2'($3,$4,$5,$6,$7,$8,$`'1,$`'2,$`'3,$`'4))')dnl
dnl# `Logical operators'
define(True, 1)dnl
define(False,0)dnl
define(Not, `ifelse($1, True, False, `ifelse($1, False, True, `errprint(Not a bool)')')')dnl
define(isBool, `ifelse($1, True, True, `ifelse($1, False, True, False)')')dnl
define(And, `ifelse($1, True, `ifelse($2, True, True, False)', False)')dnl
define(Or, `ifelse($1, True, True, `ifelse($2, True, True, False)')')dnl
dnl# `List operators'
define(Empty, [])dnl
define(Prep, `ifelse($2,[],[$1;],[$1;`substr($2,1,decr(len($2)))')')dnl
define(App, `ifelse($2,[],[$1;],`substr($2, 0, decr(len($2)))'$1;])')dnl
define(Next, `substr($2,$1,1)')dnl
define(Getpos, `ifelse(Next($1,$2),$3,$1,`Getpos(incr($1), $2, $3)')')dnl
define(Head, `substr($1, 1, decr(Getpos(0, $1,;)))')dnl
define(Tail, `ifelse($1,[],`errprint(tail: empty list)',[`substr($1,incr(Getpos(0,$1,;)))')')dnl
define(Index, `ifelse($1,0,`Head($2)',`Index(decr($1), Tail($2))')')dnl
define(Null, `ifelse($1,[],True,False)')dnl
dnl# `Foldr continuation passing style'
define(_Step, `$4(`$1',$1(Head($3),$2),Tail($3))')dnl
define(Foldr, `ifelse(Null($3),True,$2,`_Step(`$1',$2,$3,`Foldr')')')dnl
dnl# `Foldl continuation passing style'
define(_Stepl, `$4(`$1',$1($2,Head($3)),Tail($3))')dnl
define(Foldl, `ifelse(Null($3),True,$2,`_Stepl(`$1',$2,$3,`Foldl')')')dnl
dnl# `Sum example usage of Foldr'
define(Plus, `eval($1+$2)')dnl
define(Sum, `Foldr(`Plus',0,$1)')dnl
dnl# `Filter'
dnl# `Filter creates an locally scoped curried function, implemented withdefine..undefine'
dnl# ` ```$1''' is just a trick to get the function unpacked at the right place. Every passing
dnl# `removes a `' from the functionname'
define(_Stepf, `ifelse($1($2), True, Prep($2, $3), $3)')dnl
define(Filter,`Curry1(__Stepf,`_Stepf',```$1''')Foldr(`__Stepf',Empty,`$2')undefine(`__Stepf')')dnl
dnl# `Example foldr and foldl'
define(t, Prep(3,Prep(2,Prep(1, Empty))))dnl
Lambda(x,`eval($1-($2))')dnl
`foldr'(`x',0,[3;2;1]) is : Foldr(`x',0,t)
`foldl'(`x',0,[3;2;1]) is : Foldl(`x',0,t)
zaterdag 8 mei 2010
donderdag 18 februari 2010
Some stuff I miss in javascript
I haven't go a lot of time, so this will be a short one.
There is a lot of stuff I miss in javascript. I made an small lib for myself, which I want to share.
First I added some OO patterns:
Decorator,
Proxy,
Observer,
Observable,
Strategy,
Chain,
Chainable exceptionhandlers
I added a logger, which plays nicely with firebug. And some functional stuff like:
foldr,
foldl
A function, which returns a function, which allow partial function application:
pfunc
And a function, which gives any object an fluent interface (useful for loggers):
fluentize
And also added the function merge to the Array object. Here is the small lib:
There are probably some bugs and I haven't documentation, but on request I can add some examples.
There is a lot of stuff I miss in javascript. I made an small lib for myself, which I want to share.
First I added some OO patterns:
Decorator,
Proxy,
Observer,
Observable,
Strategy,
Chain,
Chainable exceptionhandlers
I added a logger, which plays nicely with firebug. And some functional stuff like:
foldr,
foldl
A function, which returns a function, which allow partial function application:
pfunc
And a function, which gives any object an fluent interface (useful for loggers):
fluentize
And also added the function merge to the Array object. Here is the small lib:
There are probably some bugs and I haven't documentation, but on request I can add some examples.
/* Some functions I miss in javascript */
/* Haskell style partial function application */
/*
Transforms a function of type:
( (a, b) -> c)
to
a -> b -> c
so if we have a function
f(x,y,z) -> x - y + z
we can do this now:
f(1,y,z) = g(y,z) = 1 - y + z
or this:
f(1,2,z) = g(z) = 1 - 2 + z
or this
f(x,y,z) = f(x,y,z);
use this class as
function min(a,b,c){
return a - b + c;
}
var pmin = pfunc(min);
var t = pmin(1)(2)(3);
print(t);
*/
function pfunc(func, i, acc){
if(!i){ i = 0; acc = new Array(); }
if(i == func.arity) return func.apply(func, acc);
return function(){
acc.merge(arguments);
return pfunc(func, i + arguments.length, acc);
}
}
/* Makes an object fluent */
function fluentize(obj){
var newobj = {};
for(key in obj){
var val = obj[key];
var valn;
if(typeof(val) == 'function'){
newobj[key] = function(){
val.apply(obj, this.arguments);
return obj;
}
} else {
newobj[key] = val;
}
}
return newobj;
}
/* Foldr and fold */
function foldr(list, func, acc){
var list = list.reverse();
if(list.length == 0) return acc;
for(var i = 0; i < list.length; i++){
var val = list[i];
if(typeof(val) != 'string' && typeof(val) != 'boolean' && typeof(val) != 'number') continue;
acc = func(val,acc);
}
return acc;
}
function foldl(list, func, acc){
if(list.length == 0) return acc;
for(var i = 0; i < list.length; i++){
var val = list[i];
if(typeof(val) != 'string' && typeof(val) != 'boolean' && typeof(val) != 'number') continue;
acc = func(acc, val);
}
return acc;
}
/* Very important, array merge: */
Array.prototype.merge = function(arr){
for(var i = 0; i < arr.length; i++){
this.push(arr[i]);
}
}
/* Voor ieder een hel
Voor de enkeling een hemel
maak je keuze snel
*/
/*
Function which can ensure an element exists.
callback should be return true or false
*/
function ensure(callback, cps){
function _ensurethis(){
if(callback()){
cps();
return;
}
ensure(callback, cps);
}
window.setTimeout(_ensurethis, 10);
}
/* Some OO patterns, which are very useful: */
/* Extends an object with other objects
* @function extendedObject = Extend(obj, obj1, obj2, obj3 .. objn)
*/
function Extend(){
var obj = arguments[0];
for(var i = 1; i < arguments.length; i++){
for(key in arguments[i]){
obj[key] = arguments[i][key];
}
}
return obj;
}
/* Couple of methods, which let us forward calls to an inner object,
very useful to build decorators, proxies etcetera */
function ForwardCall(){
}
/* Forward calls to an inner object */
ForwardCall.prototype.forwardCall = function(func) {
this[func] = function(){
return this.innerCall(func, arguments);
}
}
ForwardCall.prototype.innerCall = function (func, args){
return this.obj[func].apply(this.obj, args);
}
/* Decorator object */
Decorator.prototype = ForwardCall.prototype;
function Decorator(obj){
this.obj = obj;
for(key in this.obj){
var val = this.obj[key];
if(typeof(val) == 'function'){
this.forwardCall(key);
}
}
}
/* Proxy object */
Proxy.prototype = ForwardCall.prototype;
function Proxy(obj){
this.obj = obj;
for(key in obj){
var val = this.obj[key];
if(typeof(val) == 'function'){
this[key] = function(){
var args = this.before(key, arguments);
ret = this.innerCall(key, args);
return this.after(key, ret);
}
}
}
this.before = function(func, args){
return args;
};
this.after = function (func, ret){
return ret;
}
}
/* Observable */
function Observable(){
}
Observable.prototype.addObserver = function(observer){
if(typeof(this.observers) == 'undefined'){
this.observers = new Array();
}
this.observers.push(observer);
}
Observable.prototype.updateObservers = function (message){
for(observerid in this.observers){
var observer = this.observers[observerid];
observer.receiveMessage(message);
}
}
/* Observer */
function Observer(){
}
Observer.prototype.receiveMessage = function (message){
}
/* Implementation of the strategy pattern */
function Strategy(){
this.strategies = new Array();
this.createStrategy = function (func, test){
return {func: func, test: test};
};
this.addStrategy = function (func, test){
this.strategies.push(this.createStrategy(func, test));
return this.strategies.length;
};
this.setDefaultStrategy = function (func){
this.defaultStrategy = func;
};
this.removeStrategy = function (id){
this.strategies[id] = null;
};
this.run = function() {
var args = arguments;
for(key in this.strategies){
if(this.strategies[key] != null){
var strategy = this.strategies[key];
if(strategy['test'].apply(this, args)){
return strategy['func'].apply(this, args);
}
}
}
return this.defaultStrategy.apply(this, args);
}
}
function Chain(){
}
function Chain(test, func){
this.func = func;
this.test = test;
this.next = null;
this.run = function(){
if( this.test.apply(this, arguments) ){
return this.func.apply(this, arguments);
}
if(this.next != null){
return this.next.run.apply(this.next, arguments);
}
return null;
}
this.addToChain = function(test,func){
if(this.next == null){
this.next = new Chain(test,func);
return;
}
this.next.addToChain(test,func);
}
}
ExceptionHandler.prototype = ForwardCall.prototype;
function ExceptionHandler (obj){
this.obj = obj;
this.strategy = new Strategy;
this.strategy.setDefaultStrategy(function(ex,key){
print ("Uncaught Exception calling: " + key);
print(ex);
return;
});
this.addFunc = function(key){
this[key] = function(){
try {
this.innerCall(key, arguments);
} catch (e){
this.strategy.run(e, key);
}
}
}
for(key in this.obj){
var val = this.obj[key];
if(typeof(val) == 'function'){
this.addFunc(key);
return this.next.run.apply(this.next, arguments);
}
return null;
}
this.addToChain = function(test,func){
if(this.next == null){
this.next = new Chain(test,func);
return;
}
this.next.addToChain(test,func);
}
}
ExceptionHandler.prototype = ForwardCall.prototype;
function ExceptionHandler (obj){
this.obj = obj;
this.strategy = new Strategy;
this.strategy.setDefaultStrategy(function(ex,key){
print ("Uncaught Exception calling: " + key);
print(ex);
return;
});
this.addFunc = function(key){
this[key] = function(){
try {
this.innerCall(key, arguments);
} catch (e){
this.strategy.run(e, key);
}
}
}
for(key in this.obj){
var val = this.obj[key];
if(typeof(val) == 'function'){
this.addFunc(key);
this.addFunc(key);
}
}
this.addHandler = function(test, func){
this.strategy.addStrategy(test, func);
}
}
/* Inspection function */
function Inspect(obj, depth, i, indent){
if(!depth){
depth = 5;
}
if(!i){
i = 0;
indent = '';
}
for(key in obj){
var type = typeof(obj[key]);
print (indent + type + ": " + key);
if((type == 'object' || type == 'function' ) && i < depth){
Inspect(obj[key], depth, i + 1, indent + " ");
}
}
}
/* We need to define a print function, to work with all the classes above:
*/
function print(str){
Logger.getInstance().startGroup('print').log('from: ' + arguments.callee.caller.toString()).log(str).endGroup();
}
/* HTML generating functions */
/* Simple positioning function */
function putHTML(pos){
return function(data){
Logger.getInstance().startGroup('putHTML');
Logger.getInstance().log('position data at: ' + pos);
$(pos).html(data);
Logger.getInstance().endGroup();
}
}
/* Javascript logger, logs to firebug, this an singleton, on non development site, we have to run Logger.getInstance().setSilence(),
then it shuts up */
function Logger(){
this.silence = false;
}
Logger.getInstance = function(){
if(!Logger.instance){
Logger.instance = /*fluentize(*/new Logger();//);
}
return Logger.instance;
}
Logger.prototype.setSilence = function(silence){
if(!silence){
silence = true;
}
this.silence = silence;
}
Logger.prototype.startGroup = function(name){
if(this.silence) return;
console.group(name);
return this;
}
Logger.prototype.endGroup = function(){
if(this.silence) return;
console.groupEnd();
return this;
}
Logger.prototype.log = function (logline, errorlevel){
/* formatted mode */
if(!console || this.silence){
return;
}
if(typeof(logline) == 'object'){
this.multiline(logline, errorlevel);
}
if(errorlevel == 'error'){
console.error(logline);
} else
if(errorlevel == 'warn'){
console.warn(logline);
} else {
console.info(logline);
}
return this;
}
Logger.prototype.multiline = function(loglines, errorlevel){
console.group('Object inspector:');
console.dir(loglines);
console.groupEnd();
return this;
}
Labels:
functional,
Javascript,
logger,
OO,
php design patterns decorators
Abonneren op:
Reacties (Atom)
