如何在 JavaScript 中有效地组合多个 Maybe monad?

How to effectively combine multiple Maybe monads in JavaScript?

我有一个 css 规则的对象,它可以具有任何或 none 以下属性:

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

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

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

然而,如果 rules 对象不包含上述五个 css 规则中的任何一个,则输入按原样返回:

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

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

我确实想出了一个使用 的解决方案,在我决定深入研究 Monad 之前,我对此非常满意。

我对使用一些 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 monads?

你真是把事情复杂化了:

  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;

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

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

  1. 我按原样接受初始 rules 对象
  2. 稍后 (cf chain) 我决定那个对象是否是我可以使用的东西
  3. 我继续使用常规映射函数
  4. 最后,如果我 Nothing 我 return 按原样输入,否则我应用计算的 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>'

这就是我要做的。

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 和组合只是函数式编程为此提供的一些工具。盒子里有更多的工具。你只需要找到合适的工具。