好吧,*这*是一元的吗?

发布于 2024-12-01 22:27:55 字数 1985 浏览 1 评论 0原文

我在面向文档的前面放置了一个安全层,并且我需要一种合理抽象的方式让应用程序定义哪些新文档以及对现有文档的哪些更新对于特定用户来说是合法的。

具体问题是 -- 文档被定义(至少在传输过程中)为 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)是一个规则生成函数,它只允许在其参数中指定的字段并适用这些字段的值的给定规则。

所以我写了这一切,它就像一个魅力。我所有的错误都被证明是单元测试中的错误!嗯,几乎全部。不管怎样,如果你已经读到这里,我的问题是:

我一直在热衷于研究单子,我的阅读启发了我以这种方式解决问题。但是,这真的是一元吗?

(可能的答案:“是”、“不,但这没关系,因为单子并不是解决这个问题的正确方法”和“不,这就是需要改变的地方”。也欢迎第四种可能性。)

I'm putting a security layer in front of a document-oriented and I needed a reasonably abstract way for the application to define what new documents and what updates to existing documents are legal for a particular user.

The particular issues are
-- The documents are defined (at least in transit) as JSON objects, so the rules may be hierarchical and so the rule-engine has to work recursively. For example, the Employee object may have a sub-object called Compensation and that sub-object has a field called PayPeriod that must be one of 'weekly', 'biweekly', or 'monthly'.
-- it runs Node.js and some rules need to read from input (for example, to read more user data from a database) , so it has to run in continuation style.

So what I came up with is this: each rule is a function that takes the current value, the proposed new value, and a callback that is invoked with the value to be used. That value can be one of the two inputs or some third value calculated by the rule. Here's one rule:

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

This rule would only allow you to set or replaces this field with a non-zero-length value. Of course, that only makes sense for string values (ignore lists for the moment, so we need a rule to enforce string-ness):

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

In fact, that seems like a common sort of problem, so I wrote a rule generator:

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

var isString = ofType('string')

but I need a way to string rules together:

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

var nonEmptyString = and(isString, nonEmpty);

So the rule for an adminstrator to update an Employee record might be:

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

limitedObject (like ofType) is a rule-generating function, and it that only allows the fields specified in its argument and applies the given rule to the values of those fields.

So I wrote all this, and it works like a charm. All my bugs turned out to be errors in the unit tests! Well, almost all of them. Anyway, if you've read this far, here is my question:

I've been febrilely studying monads and my reading inspired me to solve the problem this way. But, is this truly monadic?

(Possible answers: "Yes", "No, but that's okay because monads aren't really the right approach for this problem", and "No, and here's what needs to change". Fourth possibilities also welcome.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

北音执念 2024-12-08 22:27:55

不,这似乎不是一元的。您定义的内容似乎是规则组合器的迷你 DSL,其中您有简单的规则,例如 ofType(typeName) 以及将规则组合成更大规则的方法,例如 <代码>和(规则1,规则2)。

为了拥有一个 monad,您需要一些可以在其中放置任何值的上下文概念。您还需要以下操作:

  1. 一个函数 wrap(x),用于将任何值放入某个默认上下文中。
  2. 函数map(f, m),用于应用函数f来转换m内的值而不改变上下文。
  3. 用于将两层上下文扁平化为一层的函数flatten(mm)

这些操作必须满足某些“明显”的法则:

  1. 在外部添加一层上下文并折叠可以恢复到开始时的状态。

    展平(wrap(m)) == m
    
  2. 在内部添加一层上下文并折叠可以让您回到开始时的状态。

    展平(map(wrap, m)) == m
    
  3. 如果您的值具有三层上下文,那么先折叠两个内层还是先折叠两个外层并不重要。

    展平(展平(mmm))==展平(地图(展平,mmm))
    

也可以按照上面的 wrap 和另一个操作 bind 定义一个 monad,但这与上面的等效,因为您可以定义 bind< /code> 就 mapflatten 而言,反之亦然。

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

目前尚不清楚上下文的概念是什么,以及如何将任何值转化为规则。因此,如何扁平化两层规则的问题就更没有意义了。

我认为单子在这里不是一个合适的抽象。

然而,很容易看出您的 确实形成了 monoid以始终成功的规则(如下所示)作为标识元素。

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

No, this does not appear to be monadic. What you've defined appears to be a mini-DSL of rule combinators, where you have simple rules like ofType(typeName) and ways of combining rules into bigger rules like and(rule1, rule2).

In order to have a monad, you need some notion of context in which you can put any value. You also need the following operations:

  1. A function wrap(x) for putting any value into some default context.
  2. A function map(f, m) for applying a function f to transform the value within m without altering the context.
  3. A function flatten(mm) for flattening two layers of context into one.

These operations must satisfy certain "obvious" laws:

  1. Adding a layer of context on the outside and collapsing gives you back what you started with.

    flatten(wrap(m)) == m
    
  2. Adding a layer of context on the inside and collapsing gives you back what you started with.

    flatten(map(wrap, m)) == m
    
  3. If you have a value with three layers of context, it does not matter whether you collapse the two inner or the two outer layers first.

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

It is also possible to define a monad in terms of wrap as above and another operation bind, however this is equivalent to the above, as you can define bind in terms of map and flatten, and vice versa.

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

It is not clear what the notion of context would be here, how you would turn any value into a rule. Thus the question of how to flatten two layers of rules makes even less sense.

I don't think a monad is a suitable abstraction here.

However there it is easy to see that your and does form a monoid with the always-succeeding rule (shown below) as the identity element.

function anything(proposedValue, existingValue, callback) {
    callback(proposedValue);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文