Примечание редактора. Это старая запись от марта 2013 года из старого блога. По-видимому, он все еще получает достаточно просмотров, чтобы нуждаться в обновлении, поскольку ссылки изменились. Повторная публикация здесь с исправлениями URL.

Существует область, в которой карри рассматривается как один из основных элементов региональной кухни. Это не тот домен; хотя в этой области я бы сказал, что нам нужна паприка.

Если термины карри или частичное применение вам совсем незнакомы, я настоятельно рекомендую последний опус на эту тему ​​Рега Брейтуэйта. Поддержку оказывают +Бен Алман с его собственным потрошащим проявлением силы, а также +Аксель Раушмайер с его очень, чрезвычайно, обычно авторитетной записью. Если вы когда-либо следили за мной здесь раньше, вы, возможно, помните туман, окружающий идею, касающуюся частичного применения из Хороших чтений, где я ссылался на (опять же на мистера Брейтуэйта) тощих.

Не смущайтесь, не расстраивайтесь, не унывайте и не унывайте. Каррирование и частичное применение не так-то просто получить. Литература по этому вопросу, хотя и обширна, плотна. Даже в самых умелых руках попытки принести эти фолианты с горы Синай обычно не приводили к большей ясности.

Но это еще не очередная индоктринация по этому вопросу. Для этого обратитесь к вышестоящим властям. Здесь я пытаюсь использовать потенциал во благо. Используя оба ингредиента, можно создать семантику для запросов к объектам JavaScript с синтаксисом, напоминающим SQL. Есть ли другие решения для этого? "Да".

После того, как вы разобрались с понятиями, должно быть несложно увидеть реализацию нескольких стандартных методов частичного приложения: карта, фильтр и свернуть. Множество библиотек уже постарались и написали эти для нас, но ради того, чтобы было что писать, давайте реализуем их снова (В реальном мире вам лучше взять какой-нибудь функциональный js или wu.js уже построили)!

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)
}

Только с ними мы можем сделать что-то почти крутое. Мы можем расширить родной класс Array, добавив несколько новых методов:

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

На данный момент, учитывая экземпляр массива (спасибо Faker), например:

var somePeople = [
    {"FirstName":"Cristina", "LastName":"Quigley", "PhoneNumber":"1-189-868-2830", "Email":"[email protected]", "Id":0},
    {"FirstName":"Eriberto", "LastName":"Bailey", "PhoneNumber":"1-749-549-2050 x36612", "Email":"[email protected]", "Id":1},
    {"FirstName":"Amina", "LastName":"Schaden", "PhoneNumber":"463-301-9579 x9511", "Email":"[email protected]", "Id":2}];

Если бы мы хотели выбрать FirstName каждой записи, мы могли бы сделать что-то грубое, например:

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

Но это все равно слишком тупо. С небольшим количеством карри мы можем сделать его лучше. Мы использовали частичное приложение, чтобы добраться до «_select», но мы можем переключиться на карри, чтобы получить лучший механизм запросов. Во-первых, давайте определим метод запроса:Обновление: технически здесь нам не нужно карри, поскольку мы не абстрагируем объект «запрос» как параметр. Спасибо Томасу Бюретту в комментариях.

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

методы select и from просты:

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

который затем оставляет только выполнение. Я намеренно не оптимизировал этот метод для иллюстрации: в отсутствие инструментов такой код выглядит так. Посмотрите на избыточность и дублирование. Поразитесь неэлегантности. Цените тот факт, что это работает.

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

Теперь это дает синтаксис, который больше похож на SQL:

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

Отсюда «где», «присоединиться» (да, я сказал «СОЕДИНИТЬСЯ»), «orderby» и «groupby» — все это детали реализации. Это всего лишь пост с доказательством концепции, но, учитывая некоторые большие наборы данных Faker, он уже работает довольно хорошо, учитывая его ограничения. Рефакторинг «перехода» к методу, использующему частичное применение, даст горы.

Как всегда, все, что я пишу в блоге и коде, является общественным достоянием. Вы можете просмотреть исходный код из проекта oj-sql здесь, поработать со мной над c9 здесь или сделать все, что вам взбредет в голову. Пусть ветер, поражающий твою прихоть, всегда будет за твоей спиной.