A JavaScript Functional Programming Example - CodeProject

:

Introduction

JavaScript has several features that make it possible to use functional programming constructs. These constructs can be used to derive a function by building it up step by step. This will allow testing the parts of the function at each step in its development. This tip will demonstrate this with a simple task, it will create a function that takes an array of objects as input and appends the data in the objects to a table.

Using the Code

The demo web page has buttons for testing the code in this tip. Download the demo page and the FJS script file.

The goal is to create a function to append data from an array to a table. The function will be built out of basic JavaScript functions and some functional programming constructs that are also implemented in JavaScript. This process will allow the testing of each part of the function and then the parts will be combined together.

This project makes use of a library named FJS.js. This is an implementation of some basic functional programming constructs. Any functional programming library can be used instead.

First, a little functional programming:

  • Map(f,[a1, a2, ..]) = [f(a1), f(a2)]
    Applies the function f to the elements of an array to create another array.
  • Compose(f1,f2)(arg) = f1(f2(arg))
    Creates a function out of the composition of the functions f1,f2 (can use more than two functions).
  • LCurry(f,arg1)(arg2) = f(arg1,arg2)
    Creates a function out of f and the argument arg1. When this new function is invoked, it will be that same as f with arg1 and arg2.
  • Prop(propertyname)(object) = object.propertyname
    A function that returns the property value of an object.
  • Onto([f1,f2,...])(arg) = [f1(arg), f2(arg), ...]
    Applies an array of functions [f1,f1,...] to an argument to create an array.
  • Foldl(f,initial,[arg1,arg2,...]) = f(f(initial,arg1),arg2)
    Applies a function to an initial argument and the first element of an array and then recursively to the rest of the array.

The plan is to create a function in three phases:

  1. Create a function that returns an array of td elements from the fields in an element of the input data array.
  2. Then create a function that returns a tr element that contains the td elements from 1)
  3. Lastly, create a function that appends the tr element from 2) to the table and apply that function to all of the elements of the data array.

Building the Function

Define some aliases for the functional programming we will need.

var LCurry = FJS.LCurry;
var Prop = FJS.Prop;
var Compose = FJS.Compose;
var OnTo = FJS.OnTo;
var Foldl = FJS.Foldl;
var Map = FJS.Map;

The data array that will be the input is:

var A = [{title:"On the Revolutions of Heavenly Spheres",author:"Nicolaus Copernicus"},
		 {title:"Dialogues Concerning the Two Sciences",author:"Galileo Galilei"},
		 {title:"The Geometry",author:"Rene Descartes"}];

Select an index for testing, this will later be removed.

var i = 2;

Define some basic support functions:

function AppendTo(Parent,Child)
{
	Parent.appendChild(Child);
	return Parent;
}
function TDNode(innerHTML)
{
	var tdele = document.createElement("td");
	tdele.innerHTML = innerHTML;
	return tdele;
}
function TRNode(TREle,TDEle)
{
	if (TREle === null)
		TREle = document.createElement("tr");
	TREle.appendChild(TDEle);					
	return TREle;
}

Define some variables that will hold the final functions.

var Cells;
var TR;
var TableAppend;

Now, derive the program in a step by step fashion so that each step can be tested. Each step will be assigned to a variable that begins with the word step. In this way, a breakpoint can be set on these lines to check the output at that stage of the program. Later, these lines can be deleted.

For 1)

var TargetTable = document.getElementById("TargetTable1");

Create a td element with the TDNode and Prop functions in the usual JavaScript way. This is then written in the form F(A[i]) with the Compose function.

var step1 = TDNode(Prop("title")(A[i]));
var step2 = Compose(TDNode,Prop("title"))(A[i]);

Do a similar thing to create functions for the other fields in the object. Put these together in an array and apply the array of functions to A[i] with Onto. This will create an array of td elements. Then, use the LCurry to rewrite the Onto in the form F(A[i]). This is then the Cells function.

var step3 = OnTo([Compose(TDNode,Prop("title")), Compose(TDNode,Prop("author"))],A[i]);
var step4 = LCurry(OnTo,[Compose(TDNode,Prop("title")), Compose(TDNode,Prop("author"))])(A[i]);
Cells = LCurry(OnTo,[Compose(TDNode,Prop("title")), Compose(TDNode,Prop("author"))]);

For 2), want to derive the TR function that creates a tr element and appends to it the array from Cells. This is done by recursively applying the TRNode function (defined previously) to the array returned by Cells, which is then rewritten with LCurry to "remove" the TRNode function and make a composition. Then, use Compose to get the desired function.

var step5 = Foldl(TRNode,null,Cells(A[i]));
var step6 = LCurry(Foldl,TRNode,null)(Cells(A[i]));
var step7 = Compose(LCurry(Foldl,TRNode,null),Cells)(A[i]);
TR = Compose(LCurry(Foldl,TRNode,null),Cells);

Finally for 3), use the AppendTo function to append the row element from TR to the table. Then use LCurry to make the AppendTo a function. This is then a composition of the LCurry and TR so using Compose converts the function into the form F(A[i]). Apply this function to each element of A with Map. And again, LCurry puts the equation in the desired format. So the final product, TableAppend, is:

var step8 = AppendTo(TargetTable,TR(A[i]));
var step9 = LCurry(AppendTo,TargetTable)(TR(A[i]));
var step10 = Compose(LCurry(AppendTo,TargetTable),TR)(A[i]);
var step11 = Map(Compose(LCurry(AppendTo,TargetTable),TR),A);
var step12 = LCurry(Map,Compose(LCurry(AppendTo,TargetTable),TR))(A);
TableAppend = LCurry(Map,Compose(LCurry(AppendTo,TargetTable),TR));

Removing all of the step lines gives the final product:

Cells = LCurry(OnTo,[Compose(TDNode,Prop("title")), Compose(TDNode,Prop("author"))]);
TR = Compose(LCurry(Foldl,TRNode,null),Cells);
TableAppend = LCurry(Map,Compose(LCurry(AppendTo,TargetTable),TR));

One nice feature of functional programming is that TableAppend can be written as a single function by substituting for TR, and Cells.

TableAppend = LCurry(Map,Compose(LCurry(AppendTo,TargetTable),
Compose(LCurry(Foldl,TRNode,null),LCurry(OnTo,[Compose(TDNode,Prop("title")), 
Compose(TDNode,Prop("author"))]))));

This is pretty complicated, keeping it in three previous functions makes it easier to read.

To Add Attributes to the Table

Now, we will add attribute objects to the function. These attributes will be applied to the td elements that are created from the A array. This will only require a change in the Cells function.

First, change the TDNode function to get an attribute object parameter and apply the properties of the object as attributes of the TD node.

function TDNode2(AttrObject,innerHTML)
{
	var tdele = document.createElement("td");
	tdele.innerHTML = innerHTML;
	var p;
	for (p in AttrObject) {
	    tdele.setAttribute(p,AttrObject[p]);
 	}
	return tdele;
}

Now, create the attribute objects for title and author. These objects just use a style attribute but any attributes can be put into the objects.

var AttrTitle = {style:"background-color:LightBlue"};
var AttrAuthor = {style:"background-color:LightGreen"};

Then, rewrite the previous functions as follows: use LCurry and then Compose to form the function that creates the td node with the attributes object.

var step13 = TDNode2(AttrTitle,Prop("title")(A[i]));
var step14 = LCurry(TDNode2,AttrTitle)(Prop("title")(A[i]));
var step15 = Compose(LCurry(TDNode2,AttrTitle),Prop("title"))(A[i]);

This is then inserted into the array in the definition of Cells to get:

var step16 = LCurry(OnTo,[Compose(LCurry(TDNode2,AttrTitle),Prop("title")), 
Compose(LCurry(TDNode2,AttrAuthor),Prop("author"))])(A[i]);
Cells = LCurry(OnTo,[Compose(LCurry(TDNode2,AttrTitle),Prop("title")), 
Compose(LCurry(TDNode2,AttrAuthor),Prop("author"))]);

TR and TableAppend stay the same.