Ramda:如何使这个命令式的减少器更具声明性?

时间:2019-06-23 13:09:14

标签: javascript typescript functional-programming ramda.js

我具有以下减速器功能

reducers的 first 参数是合计值,而 second 参数是下一个值。下面的reducer函数在相同的reaction参数上进行归约,但聚合state$值。每个化简函数都会产生一个新的合计值。

/**
 * Applies all the reducers to create a state object.
 */
function reactionReducer(reaction: ReactionObject): ReactionObject {
    let state$ = reactionDescriptionReducer({}, reaction);
    state$ = reactionDisabledReducer(state$, reaction);
    state$ = reactionIconReducer(state$, reaction);
    state$ = reactionOrderReducer(state$, reaction);
    state$ = reactionStyleReducer(state$, reaction);
    state$ = reactionTitleReducer(state$, reaction);
    state$ = reactionTooltipReducer(state$, reaction);
    state$ = reactionVisibleReducer(state$, reaction);
    return state$;
}

const state = reactionReducer(value);

上面的方法有效,但是该函数已通过化简器列表进行了修复。看来我应该可以使用RamdaJS进行类似的操作。

const state = R.????({}, value, [reactionDescriptionReducer
    reactionDisabledReducer,
    reactionIconReducer,
    reactionOrderReducer,
    reactionStyleReducer,
    reactionTitleReducer,
    reactionTooltipReducer,
    reactionVisibleReducer]);

我是RamdaJS的新手,如果这是一个菜鸟问题,请原谅我。

如何仅使用RamdaJS执行一连串的减速器?

3 个答案:

答案 0 :(得分:5)

and通过组合两个(2)输入缩减器(r, x) => ...f构造一个新的缩减器g-

const and = (f, g) =>
  (r, x) => g (f (r, x), x)

all通过使用and,通过组合任意数量的reducer来构造新的reducer-

const identity = x =>
  x

const all = (f = identity, ...more) =>
  more .reduce (and, f)

使用myReducer定义all-

const myReducer =
  all
    ( reactionDisabledReducer
    , reactionIconReducer
    , reactionOrderReducer
    // ...
    )

给出这三(3)个reducer的模拟实现-

const reactionDisabledReducer = (s, x) =>
  x < 0
    ? { ...s, disabled: true }
    : s

const reactionIconReducer = (s, x) =>
  ({ ...s, icon: `${x}.png` })

const reactionOrderReducer = (s, x) =>
  x > 10
    ? { ...s, error: "over 10" }
    : s

运行myReducer以查看输出

const initState =
  { foo: "bar" }

myReducer (initState, 10)
// { foo: 'bar', icon: '10.png' }

myReducer (initState, -1)
// { foo: 'bar', disabled: true, icon: '-1.png' }

myReducer (initState, 100)
// { foo: 'bar', icon: '100.png', error: 'over 10' }

展开下面的代码片段,以在浏览器中验证结果-

const identity = x =>
  x

const and = (f, g) =>
  (r, x) => g (f (r, x), x)

const all = (f, ...more) =>
  more .reduce (and, f)

const reactionDisabledReducer = (s, x) =>
  x < 0
    ? { ...s, disabled: true }
    : s

const reactionIconReducer = (s, x) =>
  ({ ...s, icon: `${x}.png` })

const reactionOrderReducer = (s, x) =>
  x > 10
    ? { ...s, error: "over 10" }
    : s

const myReducer =
  all
    ( reactionDisabledReducer
    , reactionIconReducer
    , reactionOrderReducer
    // ...
    )

const initState =
  { foo: "bar" }

console .log (myReducer (initState, 10))
// { foo: 'bar', icon: '10.png' }

console .log (myReducer (initState, -1))
// { foo: 'bar', disabled: true, icon: '-1.png' }

console .log (myReducer (initState, 100))
// { foo: 'bar', icon: '100.png', error: 'over 10' }


您可以为andall选择任意名称。我可以将它们视为reducer模块的一部分,例如reducer.andreducer.all

答案 1 :(得分:3)

我的第一次尝试根本不会涉及Ramda,只是一个简单的事情:

const makeReducer = (...fns) => (x) => fns .reduce ( (s, fn) => fn (s, x), {} )

const fn = makeReducer (
  (state$, reaction) => ({...state$, foo: `<<-${reaction.foo}->>`}),
  (state$, reaction) => ({...state$, bar: `=*=${reaction.bar}=*=`}),
  (state$, reaction) => ({...state$, baz: `-=-${reaction.baz}-=-`})
)

console .log (
  fn ( {foo: 'a', bar: 'b', baz: 'c'} )
) //~> {foo: '<<-a->>', bar: '=*=b=*=', baz: '-=-c-=-'}

虽然您可以选择使用Ramda的reduceflip,但似乎他们不会在这里添加太多内容。

答案 2 :(得分:3)

这里使用Ramda的一种选择是利用以下事实:它支持将函数作为monad实例传递给R.chain(也称为Reader monad)。

这使您可以将共享某些公共环境的多个函数排序在一起-在您的情况下为reaction

我们可以利用R.pipeWith(R.chain)来组成一系列这些需要一些输入的函数(例如,您的$state穿过每个函数)并返回一个获取环境的函数,并产生结果传递给管道中的下一个功能。

// Some mock functions to demonstrate

const reactionDescriptionReducer = ({...state}, reaction) =>
  ({ description: reaction, ...state })

const reactionDisabledReducer = ({...state}, reaction) =>
  ({ disabled: reaction, ...state })

const reactionIconReducer = ({...state}, reaction) =>
  ({ icon: reaction, ...state })

// effectively `R.pipeK`
const kleisli = R.pipeWith(R.chain)

// we need the functions going into chain to be curried
const curried = f => a => b => f(a, b)

// finally, compose the series of functions together
const reactReducer = kleisli([
  curried(reactionDescriptionReducer),
  curried(reactionDisabledReducer),
  curried(reactionIconReducer)
])({})

// and if all goes well...
console.log(
  reactReducer("someCommonReactionValue")
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>