Как эффективно объединить несколько монад Maybe в JavaScript? - PullRequest
4 голосов
/ 01 июня 2019

У меня есть объект правил 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 или включать дополнительные вещи, которые нам не нужны.

Я нашел решение, используя , которыйЯ был вполне доволен, пока не решил углубиться в монады.

Я был впечатлен количеством кода, который мне удалось удалить, используя несколько монадических принципов.

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 голосов
/ 03 июня 2019

Как указывалось, я не думаю, что использовал тип 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>'
0 голосов
/ 13 июля 2019

Это то, что я бы сделал.

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>

Обратите внимание, что не рекомендуется включать монады и композицию в каждую функциональную программу.

Функциональное программирование - это больше, чем просто монады и композиция.В основе его лежит превращение входов в выходы без побочных эффектов.Монады и композиция - это лишь некоторые из инструментов, которые функциональное программирование предоставляет для этого.В коробке больше инструментов.Вам просто нужно найти правильный инструмент.

0 голосов
/ 01 июня 2019

Вы действительно усложняете вещи:

  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;
...