【问题标题】:How to effectively combine multiple Maybe monads in JavaScript?如何在 JavaScript 中有效地组合多个 Maybe monad?
【发布时间】:2019-10-17 16:53:03
【问题描述】:

我有一个 css 规则对象,它可以有或没有以下属性:

{ 'font-style': '…',
  'font-variant': '…',
  'font-weight': '…',
  'text-decoration': '…',
  'vertical-align': '…' }

下一步是构建一个应用于输入的 css 字符串,例如:

style({'text-decoration': 'underline'}, 'foo');
//=> '<span style="text-decoration:underline">foo</span>'

但是如果rules对象不包含以上5个css规则中的任何一个,则输入原样返回:

style({}, 'foo'); //=> 'foo'

如您所见,这不是火箭科学,但必须注意不要应用空的 css 字符串或包含我们不需要的额外内容。

我确实提出了一个使用 的解决方案,直到我决定更深入地研究 Monads 之前,我对此感到非常满意。

我对使用一些 Monadic 原则能够删除的大量代码印象深刻。

const {curry} = require('ramda');
const {Maybe} = require('monet');

const css = (attrs, key) =>
  attrs[key] ?
    Maybe.of(`${key}:${attrs[key]};`) :
    Maybe.of('');

const style = curry((va, td, fw, fv, fs, input) =>
  va || td || fw || fv || fs ?
    `<span style="${va}${td}${fw}${fv}${fs}">${input}</span>` : input);

module.exports = curry((attrs, input) =>
  Maybe.of(input)
    .ap(css('font-style', attrs)
    .ap(css('font-variant', attrs)
    .ap(css('font-weight', attrs)
    .ap(css('text-decoration', attrs)
    .ap(css('vertical-align', attrs)
    .map(style))))))
    .some());

我对此很满意,但我不禁想到所有这些嵌套的 ap 都是某种伪装的回调地狱。也许有更好的方法我不知道?

问题:有没有更好的方法来组合多个Maybe monad?

【问题讨论】:

  • 这里是应用风格,任何地方都没有单子。回调是源自异步计算的术语。 ap 只是一个方法,它有一个包含在应用上下文中的隐式函数,并且需要一堆应用程序。没有回调地狱。可以说方法链避免了嵌套函数组合,也就是说ap(...).ap(...).ap(...)是变相的嵌套函数组合。甚至更好:方法链允许从嵌套函数组合中抽象出来。
  • 另一种思考问题的方法是将 Maybe 类型视为 Nothing 的空数组或具有一个元素的数组 Some 值。然后,您可以简单地将所有数组值 concat 放在一起。这将为您提供与catMaybes 相似的结果。
  • 我刚刚注意到您实际上也没有使用任何关于 Maybe 类型的特定内容。生成的所有Maybe 值都使用Maybe.of,因此使用.ap 实际上与使用Identity 之类的值相同,而不是Maybe。换句话说,您的示例最终只是作为常规函数应用程序。 module.exports = attrs =&gt; ['font-style', 'font-variant', 'font-weight', 'font-decoration', 'vertical-align'].reduce((fn, k) =&gt; fn(css(attrs(k))), style)
  • 感谢@ScottChristopher。我明白你的意思了。我最终使用了Maybe.of,尽管我更喜欢使用Just/Nothing 子类型。问题是ap 不适用于Nothing
  • @customcommander,这段代码真的运行了吗?我看不出style 应该如何实现style({}, input)。为什么它有6个参数?

标签: ramda.js javascript functional-programming monads ramda.js monetjs


【解决方案1】:

你真的把事情复杂化了:

  const keys = ['font-style', 'font-variant', 'font-weight', 'text-decoration', 'vertical-align'];

  const css = attrs => keys
     .map(it => attrs[it] && `${it}: ${attrs[it]}`)
     .filter(it => it)
     .join(", ");

 const style = (attrs, input) =>
   css(attrs) ? `<span style="${css(attrs)}">${input}</span>` : input;

【讨论】:

  • 这是一个公平的答案,因为我确实问过如果没有 monad 是否会更好。当然可以;谢谢你的建议。但是,我不想重复不必要的迭代。我将编辑我的问题,以便更清楚地了解使用 monad 是否有更好的解决方案。
【解决方案2】:

正如我所指出的,我认为我没有尽可能多地利用 Maybe 类型。

我最终确定了以下解决方案:

  1. 我按原样接受初始的rules 对象
  2. 稍后(cf chain)我决定该对象是否可以使用
  3. 我继续使用常规映射函数
  4. 最后,如果我有 Nothing,我会按原样返回输入,否则我会应用计算出的 css 字符串
const styles = (rules, input) =>
  Maybe
    .of(rules)
    .map(pick(['font-style', 'font-variant', 'font-weight', 'text-decoration', 'vertical-align']))
    .chain(ifElse(isEmpty, Maybe.none, Maybe.some))
    .map(toPairs)
    .map(reduce((str, arr) => str + arr.join(':') + ';', ''))
    .fold(input)(css => `<span style="${css}">${input}</span>`);



styles({}, 'foo');
//=> 'foo'

styles({'text-decoration':'underline'}, 'foo');
//=> '<span style="text-decoration:underline;">foo</span>'

【讨论】:

  • 我也将这个问题提交给monet.js,以创建一个新的静态帮助器来帮助解决这个用例。
【解决方案3】:

这就是我要做的。

const style = (attrs, text) => {
    const props = Object.entries(attrs);
    if (props.length === 0) return text;
    const css = props.map(([key, val]) => key + ":" + val);
    return `<span style="${css.join(";")}">${text}</span>`;
};

const example1 = {};
const example2 = { "text-decoration": "underline" };
const example3 = { "font-weight": "bold", "font-style":  "italic" };

console.log(style(example1, "foo")); // foo
console.log(style(example2, "foo")); // <span style="text-decoration:underline">foo</span>
console.log(style(example3, "foo")); // <span style="font-weight:bold;font-style:italic;">foo</span>

请注意,虽然这可能看起来像命令式代码,但它实际上是纯函数式的。可以转写成Haskell如下。

import Data.List (intercalate)
import Data.Map.Strict (fromList, toList)

style (attrs, text) =
    let props = toList attrs in
    if length props == 0 then text else
    let css = map (\(key, val) -> key ++ ":" ++ val) props in
    "<span style=\"" ++ intercalate ";" css ++ "\">" ++ text ++ "</span>"

example1 = fromList []
example2 = fromList [("text-decoration", "underline")]
example3 = fromList [("font-weight", "bold"), ("font-style", "italic")]

main = do
    putStrLn $ style (example1, "foo") -- foo
    putStrLn $ style (example2, "foo") -- <span style="text-decoration:underline">foo</span>
    putStrLn $ style (example3, "foo") -- <span style="font-weight:bold;font-style:italic;">foo</span>

请注意,不建议将 monad 和组合硬塞到每个功能程序中。

函数式编程不仅仅是单子和组合。其核心就是将输入转化为输出而没有副作用。 Monad 和组合只是函数式编程提供的一些工具。盒子里还有更多工具。你只需要找到合适的工具。

【讨论】:

  • 近一年后回首,现在更有意义了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-04
  • 2011-08-02
  • 2020-01-25
  • 2017-09-01
  • 1970-01-01
相关资源
最近更新 更多