【问题标题】:Okay, is *this* monadic?好的,*this* 是 monadic 吗?
【发布时间】:2011-08-27 00:40:35
【问题描述】:

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

具体问题是 -- 文档被定义(至少在传输中)为 JSON 对象,因此规则可能是分层的,因此规则引擎必须递归工作。例如,Employee 对象可能有一个名为 Compensation 的子对象,而该子对象有一个名为 PayPeriod 的字段,该字段必须是“weekly”、“biweekly”或“monthly”之一。 -- 它运行 Node.js 并且需要从输入中读取一些规则(例如,从数据库中读取更多用户数据),因此它必须以 continuation 样式运行。

所以我想出的是这样的:每个规则都是一个函数,它接受当前值、建议的新值和一个使用要使用的值调用的回调。该值可以是两个输入之一,也可以是规则计算的某个第三个值。这是一条规则:

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)是一个规则生成函数,它只允许在其参数中指定的字段并将给定规则应用于这些字段的值。

所以我写了这一切,它就像一个魅力。我所有的错误都是单元测试中的错误!好吧,几乎所有人。无论如何,如果你已经读到这里,这是我的问题:

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

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

【问题讨论】:

    标签: javascript monads


    【解决方案1】:

    不,这似乎不是一元的。您定义的似乎是规则 combinators 的迷你 DSL,其中您有像 ofType(typeName) 这样的简单规则以及将规则组合成更大规则的方法,如 and(rule1, rule2)

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

    1. 一个函数 wrap(x) 用于将 any 值放入某个默认上下文中。
    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 定义一个monad,但是这等效于上面,因为您可以根据map 和@987654336 定义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); }
    

    不清楚这里的上下文概念是什么,如何将任何值变成规则。因此,如何扁平化两层规则的问题就更没有意义了。

    我不认为 monad 在这里是合适的抽象。

    但是很容易看出,您的and 确实形成了一个monoid,其中始终成功的规则(如下所示)作为标识元素。

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

    【讨论】:

    • combinators -- 那是我正在寻找的模式的名称。感觉很熟悉。
    • 这是我见过的 Javascript 开发者最容易理解的 monad 法则!
    猜你喜欢
    • 2014-10-09
    • 1970-01-01
    • 2023-04-10
    • 2021-05-07
    • 2010-11-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多