zaterdag 8 mei 2010

Making M4 into a functional language

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)

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.

/* 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;
}