Currying Favor with Partial Application to get JavaScript SQL

9:16 PM

There is a domain that attends to curry as one of the quintessential elements of regional cuisine. This is not that domain; though in this domain I would argue we need a paprika.

If the terms curry or partial application are at all unfamiliar to you, I highly recommend Reg Braithwaite's latest opus on the subject. Lending support are +Ben Alman with his own eviscerating tour de force as well as +Axel Rauschmayer with his very, extremely, routinely reputable entry. If you've ever followed me here before, you may remember a fog surrounding an idea that touched on partial application from Good Reads, where I linked to (again to Mr. Braithwaite) the skinny.

Do not be confused, dismayed, disheartened or discouraged. Currying and partial application are not easy-to-grok. The literature on the subject, while vast, is dense. Even in the most skilled hands, attempts to bring these tomes down from Mt. Sinai, have not routinely resulted in greater clarity.

But this is not yet-another-indoctrination on the subject. Defer to the authorities above for that. Here, I'm attempting  to exploit the potential for good. Using both ingredients, it is possible to create a semantic for querying JavaScript objects in a syntax that resembles SQL. Are there other solutions to do this? Yes.

Once you've apprehended grokation of the concepts, it should be straightforward to see the implementation of a few partial application standard methods: map, filter and fold. Lots of libraries have already done the diligence and written these for us but for the sake of having something to write, let's implement them again (In the real world, you're better off taking what functional js or wu.js have already built)!

function curryLeft(func) {
   var slice = Array.prototype.slice;
   var args = slice.call(arguments, 1);
   return function() {
       return func.apply(this, args.concat(slice.call(arguments, 0)));

   }
}

function foldLeft(func,newArray,oldArray) {
    var accumulation = newArray;
    each(oldArray, function(val) {
        accumulation = func(accumulation, val);
    });
    return accumulation;
    
}

function map(func, array) {
    var onIteration = function(accumulation, val) {
        return accumulation.concat(func(val));
    };
    return foldLeft(onIteration, [], array)
}

function filter(func, array) {
    var onIteration = function(accumulation, val) {
        if(func(val)) {
            return accumulation.concat(val);
        } else {
            return accumulation;
        }
    };
    return foldLeft(onIteration, [], array)
}

With just these, we can do something that's almost cool. We can extend the native Array class to add some new methods:

Object.defineProperties(Array.prototype, {
    '_where': {
        value: function(func) {
            return filter(func, this);
        }
    },
    '_select': {
        value: function(func) {
            return map(func, this);
        }
    }
});

At this point, given an instance of an Array (thanks to Faker), like:

var somePeople = [
    {"FirstName":"Cristina", "LastName":"Quigley", "PhoneNumber":"1-189-868-2830", "Email":"Imelda@lourdes.ca", "Id":0},
    {"FirstName":"Eriberto", "LastName":"Bailey", "PhoneNumber":"1-749-549-2050 x36612", "Email":"Pamela_Gaylord@ludie.net", "Id":1},
    {"FirstName":"Amina", "LastName":"Schaden", "PhoneNumber":"463-301-9579 x9511", "Email":"Conner_Gusikowski@jolie.tv", "Id":2}];

If we wanted to select the FirstName of each record, we could do something crude like:

somePeople._select(function(row) { return row.FirstName });

But this is still too obtuse. With a little curry, we can make it better. We used partial application to get to '_select', but we can switch gears to curry to get a better query mechanism. First, let's define a query method: Update: we technically don't need curry here, as we're not abstracting the 'query' object as a parameter. Thanks to Thomas Burette in the comments.

var query = function(array) {
    var tables = [];
    tables.push(array);
    var _query = {
        tables: tables,
        from: from,
        select: select,
        run: run
    };
    return _query;
};

select and from methods are straightforward:

function select() {
    var query = this;

    var slice = Array.prototype.slice;
    var args = slice.call(arguments, 0);
    query.columns = query.columns || [];
    each(args, function(argumentValue) {
        query.columns.push(argumentValue);
    });
    return query;
}

function from(array) {
    var query = this;
    query.tables.push(array);
    return query;
}
which then only leaves execution. I've deliberately not optimized this method for the purpose of illustration: in the absence of the tools, this is what such code looks like. Look at the redundancy and duplication. Marvel at the inelegance. Appreciate the fact that it works.

function run() {
    var query = this;
    var ret = [];
    if (query.columns.length > 0) {
        var results = [];
        each(query.columns, function(columnName) {

            each(query.tables, function(tbl) {
                if (Array.isArray(tbl)) {
                    var res = {};
                    var val = tbl._select(function(val) {
                        return val[columnName];
                    });
                    if (val) {
                        res[columnName] = val;
                        results.push(res);
                    }
                }
            }, true);

        });

        var returnRows = [];
        if(results && results.length > 0) {
            var firstResult = results[0];
            
            each(firstResult, function(val, key) {
                
                each(val, function(cell){
                    var row = {};
                    row[key] = cell;
                    each(results.slice(1), function(result) {
                        each(result, function(v,k){
                            each(v, function(c) {
                                row[k] = c;
                            })
                        },true)
                    },true)
                    returnRows.push(row);
                },true);
                
            },true)
            
        }
        
    }
    return returnRows;
}

Now, this yields a syntax which looks a lot more like SQL:

var newQuery = query(people).select('FirstName', 'LastName');
var results = newQuery.run();

From here, 'where', 'join' (yes I said JOIN), 'orderby' and 'groupby' are all implementation details. This is just a proof-of-concept post, but given some large Faker data sets it already works quite well given its limitations. Refactoring 'run' into a method which utilizes partial application will yield mountains.

As always, everything I blog and code is public domain. You can view the source from the oj-sql project here, collab with me on c9 here, or do whatever strikes your whim. May the wind that strikes your whim be always at your back.

You Might Also Like

6 comments

Public Domain. All content published here is released into the PD, except when otherwise indicated. Powered by Blogger.

Peers

Popular Posts

Like us on Facebook

Flickr Images