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

时间:2019-06-01 08:58:57

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

我有一个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对象不包含上述五个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单子?

3 个答案:

答案 0 :(得分:0)

您真的使事情复杂化了:

  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;

答案 1 :(得分:0)

如前所述,我认为我没有尽我所能利用Maybe类型。

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

  1. 我照原样接受初始rules对象
  2. 稍后(参见{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>'

答案 2 :(得分:0)

这就是我要做的。

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和组合只是函数式编程提供的一些工具。包装盒中还有更多工具。您只需要找到合适的工具。