好的,这个是单子范畴吗?

4
我正在为面向文档的系统添加安全层,并且我需要一种合理抽象的方法来定义哪些新文档和对现有文档的更新是特定用户合法的。
具体问题是: - 这些文档(至少在传输过程中)被定义为JSON对象,因此规则可能具有分层结构,因此规则引擎必须递归工作。例如,Employee对象可能有一个称为Compensation的子对象,该子对象具有称为PayPeriod的字段,该字段必须为“weekly”,“biweekly”或“monthly”之一。 - 它运行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个回答

3
不,这似乎不是单子的样子。您定义的似乎是规则组合器的小型DSL,其中包括像ofType(typeName)这样的简单规则以及将规则组合成更大规则的方法,例如and(rule1, rule2)
为了有一个单子,您需要一些可以将任何值放入其中的上下文概念。您还需要以下操作:
  1. 一个函数wrap(x),用于将任何值放入某个默认上下文中。
  2. 一个函数map(f, m),用于应用函数f来转换m内部的值,而不改变上下文。
  3. 一个函数flatten(mm),用于将两层上下文压缩成一层。
这些操作必须满足某些“显然”的法则:
  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))
    

我们可以使用上述的wrap操作和另一个操作bind来定义一个单子,但是这与上面的方法等效,因为我们可以用mapflatten来定义bind,反之亦然。

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

1
组合子——这就是我正在寻找的模式名称。它感觉非常熟悉。 - Michael Lorton
这是我见过的最易于理解的JavaScript开发者单子定律的解释! - user5536315

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接