[js] pattern-matching in a functional way
От: A13x США  
Дата: 14.05.10 10:13
Оценка: 38 (3)
Всем привет.

Недавно потребовалось заиметь простой и легко расширяемый способ сопоставления по образцу.
Было решено сделать конструирование сопоставляющей функции в функциональном стиле, иными словами нечто вроде такого:

// сопоставляющая функция. Её можно инициализировать, чтобы в случае отстутствия шаблона выкидывалось исключение
// к примеру так: matcher = function() { throw new Error("no applicable pattern found") }
var matcher

matcher = combine(PATTERN1, CALLBACK1(OBJ, .. OPTIONAL_ARGS){...}, matcher)
matcher = combine(PATTERN2, CALLBACK2(OBJ, .. OPTIONAL_ARGS){...}, matcher)
// промежуточные результаты matcher можно так же сохранять и использовать в сопоставлении
matcher = combine(PATTERN3, CALLBACK3(OBJ, .. OPTIONAL_ARGS){...}, matcher)

...

// сопоставление
matcher(OBJ, ... OPTIONAL_ARGS)


Пример использования:

    var matcher = function(val, arg) {
        print("matcher fallback: val = " + val + ", arg = " + arg)
    }

    matcher = pm.combine({type: "string"}, function(val, arg) {
        print({expr: "matcher(stringVal, arg)", value: "val = " + val + ", arg = " + arg})
    }, matcher)

    matcher = pm.combine({instanceOf: Function}, function(val, arg) {
        print({expr: "matcher(functionVal, arg)", value: "val = " + val + ", arg = " + arg})
    }, matcher)

    matcher = pm.combine({scheme: {key: "number", value: "any"}}, function(val, arg) {
        print({expr: "matcher({key:number, value:any}, arg)", value: "val = (" + val.key + "," + val.value + "), arg = " + arg})
    }, matcher)

    matcher(5, "one")
    matcher("str", "two")
    matcher(new Function("return 1"), "three")
    matcher({key: 12, value: 34}, "four")
    matcher({key: "some", value: "unk"}, "five")


Реализация (простая как 3 рубля. Расширение сопоставителя — простое добавление):

// namespace
var pm = {}

/**
 * Matcher functions constructors are used in pm.combine method.
 * Each key in this object corresponds to the certain pattern member.
 */
pm._matcherConstructors = {
    instanceOf: function (matcher, instanceTarget) {
        return function (obj) {
            if (obj instanceof instanceTarget) {
                return matcher.apply(this, arguments)
            }
            return false
        }
    },

    type: function (matcher, typeId) {
        return function (obj) {
            if (typeof(obj) === typeId) {
                return matcher.apply(this, arguments)
            }
            return false
        }
    },

    scheme: function (matcher, scheme) {
        return function (obj) {
            if (typeof(obj) !== "object") {
                return false
            }
            for (var i in scheme) {
                if (i in obj) {
                    var target = obj[i]
                    var source = scheme[i]
                    var sourceType = typeof(source)
                    if (sourceType === "string") {
                        if (source === "any" || source == typeof(target)) {
                            continue
                        }

                        return false
                    }

                    if (source !== target) {
                        return false
                    }
                }
                else {
                    return false
                }
            }
            return matcher.apply(this, arguments)
        }
    }
}

/**
 * Creates pattern matching function that accepts the pattern given.
 * The latter combined patterns takes priority over the previously declared ones.
 * @param pattern Pattern to match the target object.
 * @param callback User-defined callback to accept target object as well as the accompanying arguments.
 * @param prevMatcher Previous matcher function created by combine method or null or undefined.
 * @returns Matcher function to be used as follows: matcher.call(objectToBeMatched, optionalArguments...).
 */
pm.combine = function(pattern, callback, prevMatcher) {
    var matcher = function() {
        callback.apply(this, arguments)
        return true
    }

    // join visitor function according to the pattern given
    for (var i in pattern) {
        if (!(i in pm._matcherConstructors)) {
            throw new Error("unexpected pattern tag: " + i)
        }

        matcher = pm._matcherConstructors[i](matcher, pattern[i])
    }

    // if prev matcher either undefined or null - create new function
    if (prevMatcher == null) {
        return matcher
    }
    else {
        return function() {
            if (matcher.apply(this, arguments)) {
                return true
            }
            return prevMatcher.apply(this, arguments)
        }
    }
}

/**
 * Helper function that initializes matcher for all the types of objects with
 * the callback that throws an error.
 */
pm.unknownObjectMatcher = function() {
    throw new Error("unknown object matched")
}


Буду рад любым комментариям
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.