【问题标题】:How do I collapse Maybe monads in sanctuary js我如何在 sanctuary js 中折叠 Maybe monads
【发布时间】:2019-12-17 10:50:43
【问题描述】:

这是一个使用现代 javascript 的简单链式表达式,用于查找位于字符串中的特定键的值,该字符串包含由 = 分隔的键值对的逗号分隔列表。

如果源为空或未找到密钥,这将失败,在我看来,这对于 Maybe monad 来说似乎是一项伟大的任务。

// Grab the tag with key in `tag`
const getTag = (product, tag) =>
  product.Tags
    .split(',')
    .find(t => t.startsWith(`${tag}=`))
    .split('=')[1]

getTag({Tags: 'a=y,b=z'}, 'a') // returns 'y'
getTag({Tags: 'a=y,b=z'}, 'z') // returns boom (desired null)
getTag({Tags: null}, 'a') // returns boom (desired null)

所以我 npm 安装了 sanctuary 并开始使用功能解决方案。这是迄今为止我所获得的,并且感觉它非常丑陋,这告诉我我一定做错了什么或使用了错误的工具。

const getk = S.map(S.filter(S.test(/=/)))(S.splitOn(','))

S.map(S.map(S.map(S.splitOn('='))))(S.map(getk))(S.toMaybe(null))
// Nothing
S.map(S.map(S.map(S.splitOn('='))))(S.map(getk))(S.toMaybe('a=y,b=z'))
//Just ([["a", "y"], ["b", "z"]])

我不希望这是一个“为我解决这个问题”的问题,但我很难传达我真正需要帮助的地方。

注意我仍在尝试“弄清楚”FP,所以这绝对是一个熟悉的问题。

【问题讨论】:

    标签: functional-programming sanctuary


    【解决方案1】:

    我们可以使用S.map 转换内部值,使用S.join 删除不需要的嵌套:

    const S = require ('sanctuary');
    const $ = require ('sanctuary-def');
    
    //    getTag :: String -> Object -> Maybe String
    const getTag = tag => S.pipe ([
      S.get (S.is ($.String)) ('Tags'),             // :: Maybe String
      S.map (S.splitOn (',')),                      // :: Maybe (Array String)
      S.map (S.map (S.stripPrefix (tag + '='))),    // :: Maybe (Array (Maybe String))
      S.map (S.head),                               // :: Maybe (Maybe (Maybe String))
      S.join,                                       // :: Maybe (Maybe String)
      S.join,                                       // :: Maybe String
    ]);
    
    getTag ('a') ({Tags: 'a=y,b=z'});   // => Just ('y')
    getTag ('z') ({Tags: 'a=y,b=z'});   // => Nothing
    getTag ('z') ({Tags: null});        // => Nothing
    

    S.map 后跟S.join 始终等价于S.chain

    //    getTag :: String -> Object -> Maybe String
    const getTag = tag => S.pipe ([
      S.get (S.is ($.String)) ('Tags'),             // :: Maybe String
      S.map (S.splitOn (',')),                      // :: Maybe (Array String)
      S.map (S.map (S.stripPrefix (tag + '='))),    // :: Maybe (Array (Maybe String))
      S.chain (S.head),                             // :: Maybe (Maybe String)
      S.join,                                       // :: Maybe String
    ]);
    

    这种方法通过不短路做了一些不必要的工作,但是S.stripPrefix 允许我们在一个步骤中检查标签是否存在,如果存在则提取其值。 :)

    使用S.justs 选择第一个匹配项的更新版本:

    //    getTag :: String -> Object -> Maybe String
    const getTag = tag => S.pipe ([
      S.get (S.is ($.String)) ('Tags'),             // :: Maybe String
      S.map (S.splitOn (',')),                      // :: Maybe (Array String)
      S.map (S.map (S.stripPrefix (tag + '='))),    // :: Maybe (Array (Maybe String))
      S.map (S.justs),                              // :: Maybe (Array String)
      S.chain (S.head),                             // :: Maybe String
    ]);
    

    【讨论】:

    • 好的,我想知道 pipe 是不是一个常见的习语。感谢您的分析,这是一次最有趣且内容丰富的体验。
    • 我喜欢这里的很多小块:currying 谓词标签(参数的原始顺序是向后的),使用管道执行串行操作(而不是“嵌套”?),返回一个 @987654336 @(函数外的转换对重用更有意义),使用get 来获取可为空/未定义的对象道具(使用toMaybe 感觉将数据蛮力输入到“monad 空间”),以及stripPrefix 优化(在管道中使用 Maybe String 返回效果很好)。所有这些与chainjoin 一起大大降低了复杂性。
    • 你是否经常这样在你的函数管道旁边写下分步类型信息?
    • 我不认为head 的工作方式符合您的预期。 getTag('b', {Tags: 'a=y,b=z'}) // Nothing
    • 我在最初编写函数时使用了S.justs,但一定是我的重构搞砸了。我在我的答案中添加了一个有效的实现。至于类型签名,我经常注释函数,但很少注释管道中的每个步骤(尽管我喜欢在演示幻灯片和 Stack Overflow 上的答案中这样做)。
    【解决方案2】:

    下面是使用现代 JavaScript 替代 Sanctuary 代码的方法:

    const stripPrefix = e =>
        e.startsWith(`${tag}=`)
            && e.replace(`${tag}=`, "")
    
    const getTag = tag => product =>
        product?.Tags
            ?.split(",")
            .map(stripPrefix)
            .filter(Boolean)
            [0]
        || null
    
    
    getTag("b")({ Tags: "a=y,b=c" }) // returns 'y'
    getTag("z")({ Tags: "a=y,b=z" }) // returns null
    getTag("a")({ Tags: null }) // returns null
    

    如果未找到标签,stripPrefix 函数将返回 false,然后获取 filtered。

    您可以使用optional chaining 运算符(?.)处理{ Tags: null }

    【讨论】:

    • 又短又甜。再次证明精明的 JS 开发人员不需要外来的紧身衣库来进行函数式编程,他们希望有原生类型来避免设计错误。卢卡斯,你介意联系一下吗?我有一些 vanilla-JS FP 项目,你可以成为一个很好的合作伙伴来提出想法。请做。
    猜你喜欢
    • 1970-01-01
    • 2022-01-12
    • 1970-01-01
    • 2011-08-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-10
    • 2013-07-01
    相关资源
    最近更新 更多