把簡單的javascript改成functional programming的風格練習

 Fri, 02 May 2008 20:00:51 +0800

繼續練習把javascript改成functional programming的風格...

先試著把之前寫的依照我需要的格式產生表格的javascript改成pure functional programming的風格,包含currying以及把所有敘述盡量改成lambda calculus。

首先是這個產生表格的函數最早的模樣:

function draw(target, values) {
var row0 = target.insertRow(-1);
for (var j=0; j<values.length; j++) {
var cell0 = row0.insertCell(-1);
cell0.innerHTML += (j+1);
}
var row0 = target.insertRow(-1);
for (var j=0; j<values.length; j++) {
var cell0 = row0.insertCell(-1);
cell0.style.fontSize = 12;
cell0.innerHTML += values[j];
}
}
var t = document.getElementById("target");
draw(t, [0.23,0.351,0.3481,1.382]);

接著先做一次currying,讓這個函數可以用每次接受一個參數的方式來操作:

function drawc(target){
return function(values) {
var row0 = target.insertRow(-1);
for (var j=0; j<values.length; j++) {
var cell0 = row0.insertCell(-1);
cell0.innerHTML += (j+1);
}
var row0 = target.insertRow(-1);
for (var j=0; j<values.length; j++) {
var cell0 = row0.insertCell(-1);
cell0.style.fontSize = 12;
cell0.innerHTML += values[j];
}
};
}
drawc(document.getElementById("target"))([0.23,0.351,0.3481,1.382]);

其實沒做多大更動,只是把之前的function body改成一個以第二個參數為唯一參數的匿名函數,然後回傳這個匿名函數。上面的例子drawc函數會回傳一個函數,我直接就用([0.23,0.351,0.3481,1.382])呼叫這個函數並且傳入values參數,實際應用時可以把這個動作再推遲,例如傳給另一個函數處理,或是先存起來等需要的時候再傳values參數給他完成動作等等。

接下來把整個function body改寫成lambda calculus:

function drawcl(target){
return function(values) {
(function(row0){
(function(j){
if(j<values.length) {
(function(cell0){
cell0.innerHTML += j+1;
})(row0.insertCell(-1));
arguments.callee(j+1);
}
})(0);
})(target.insertRow(-1));
(function(row0){
(function(j){
if(j<values.length) {
(function(cell0){
cell0.style.fontSize = 12;
cell0.innerHTML += values[j];
})(row0.insertCell(-1));
arguments.callee(j+1);
}
})(0);
})(target.insertRow(-1));
};
}
drawcl(document.getElementById("target"))([0.23,0.351,0.3481,1.382]);

從這個過程可以看出幾個修改的原則:

  1. 把變數改成匿名函數的參數,把與這個變數相關的運算移到匿名函數裡面,變數名變成參數名,然後等號(assign)右側的敘述放在呼叫函數的參數運算中。
  2. 把iteration的運算改成匿名函數的遞迴,控制條件當作匿名函數的參數傳給他,然後在函數中判斷是否要停止遞迴
  3. 額外的技巧,與fp無關:在javascript中,可以利用arguments.callee來讓匿名函數做遞迴

恩恩,經過嘗試以後,感覺並不是很困難。有空的話再來做更複雜的例子。

以下是在表格border="1" cellpadding="1" cellspacing="0" id="target"的條件中測試跑出來的表格內容:
fp

沒意外的話,應該三個畫面都一樣。