Хорошо, это * это * монадическое? - PullRequest
4 голосов
/ 27 августа 2011

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

Особые проблемы: документы определены (по крайней мере, в пути) как объекты JSON, поэтому правила могут быть иерархическими, поэтому механизм правил должен работать рекурсивно.Например, объект Employee может иметь подобъект под названием Compensation, а у этого подобъекта есть поле с именем PayPeriod, которое должно быть одним из «еженедельно», «раз в две недели» или «ежемесячно».- он запускает Node.js, и некоторые правила нужно читать из ввода (например, чтобы прочитать больше пользовательских данных из базы данных), поэтому он должен работать в стиле продолжения.

Итак, что я придумалазаключается в следующем: каждое правило - это функция, которая принимает текущее значение, предлагаемое новое значение и обратный вызов, который вызывается со значением, которое будет использоваться.Это значение может быть одним из двух входов или некоторым третьим значением, рассчитанным по правилу.Вот одно правило:

var nonEmpty = function(proposedValue, existingValue, callback) {
    callback( (proposedValue.length > 0) ? proposedValue : existingValue);
};

Это правило позволит вам только установить или заменить это поле значением, отличным от нуля.Конечно, это имеет смысл только для строковых значений (на данный момент игнорируем списки, поэтому нам нужно правило для принудительного применения строковых значений):

var isString = function(proposedValue, existingValue, callback) {
    callback( ( typeof(proposedValue) === 'string') ? proposedValue : existingValue);
};

На самом деле это кажется общей проблемой,поэтому я написал генератор правил:

var ofType = function(typeName) {
    return function(proposedValue, existingValue, callback) {
    callback( ( typeof(proposedValue) === typeName) ? proposedValue : existingValue);
    };
};

var isString = ofType('string')

, но мне нужен способ объединения правил:

var and = function(f1, f2) {
    return function(proposedValue, existingValue, callback) {
    f1(proposedValue, existingValue, 
       function(newProposedValue) {
           f2(newProposedValue, existingValue, callback);
       });
    };
};

var nonEmptyString = and(isString, nonEmpty);

Таким образом, правило для администратора для обновления записи Сотрудника может быть:

limitedObject({
   lastName : nonEmptyString,
   firstName : nonEmptyString,
   compensation : limitedObject({
      payPeriod : oneOf('weekly', 'biweekly', 'monthly'),
      pay : numeric
  }
}) 

limitedObject (подобно ofType) - это функция, генерирующая правила, которая разрешает только поля, указанные в ее аргументе, и применяет данное правило к значениям этих полей.

Итак, я написал все это, и это работает как шарм.Все мои ошибки оказались ошибками в модульных тестах!Ну, почти все из них.В любом случае, если вы читали это далеко, вот мой вопрос:

Я лихорадочно изучал монады, и мое чтение вдохновило меня на решение этой проблемы.Но действительно ли это монадическое?

(Возможные ответы: «Да», «Нет, но это нормально, потому что монады не совсем правильный подход к этой проблеме» и «Нет, и вот что нужноизменить ". Четвертые возможности также приветствуются.)

1 Ответ

4 голосов
/ 27 августа 2011

Нет, это не похоже на монадическое.То, что вы определили, выглядит как мини-DSL с правилом комбинаторов , где у вас есть простые правила, такие как ofType(typeName), и способы объединения правил в большие правила, такие как and(rule1, rule2).

.Для того, чтобы иметь монаду, вам нужно некоторое понятие контекста, в которое вы можете поместить любое значение.Вам также понадобятся следующие операции:

  1. Функция wrap(x) для помещения любого значения в некоторый контекст по умолчанию.
  2. Функция map(f, m) для примененияфункция f для преобразования значения в m без изменения контекста.
  3. Функция flatten(mm) для сглаживания двух слоев контекста в один.

Эти операции должны удовлетворятьнекоторые «очевидные» законы:

  1. Добавление внешнего контекста и свертывание слоя возвращает вам то, с чего вы начали.

    flatten(wrap(m)) == m
    
  2. Добавление слоя контекста внутри и свертывание вернет вам то, с чего вы начали.

    flatten(map(wrap, m)) == m
    
  3. Если у вас есть значение с тремя слоями контекста, не имеет значения,сначала вы сворачиваете два внутренних или два внешних слоя.

    flatten(flatten(mmm)) == flatten(map(flatten, mmm))
    

Также возможно определить монаду в терминах wrap, как указано выше, и другую операцию bind,Однако это эквивалентно вышеизложенному, так как выможно определить bind в терминах map и flatten, и наоборот.

function bind(f, m) { return flatten(map(f, m)); }

# or

function map(f, m) { return bind(function(x) { return wrap(f(x)); }, m); }
function flatten(mm) { return bind(function(x) { return x; }, mm); }

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

Я не думаю, что монада является подходящей абстракцией здесь.

Однако там легко увидеть, что вашand образует моноид с всегда следующим правилом (показано ниже) в качестве элемента идентичности.

function anything(proposedValue, existingValue, callback) {
    callback(proposedValue);
}
...