【问题标题】:Ramda: How can I make this imperative reducer more declarative?Ramda:我怎样才能让这个命令式 reducer 更具声明性?
【发布时间】:2019-11-05 12:43:48
【问题描述】:

我有以下reducer函数:

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

/**
 * 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 执行一系列 reducer?

【问题讨论】:

  • 您是否尝试使用ap 创建函数列表?
  • 查看curryflipjuxtmappipe

标签: javascript typescript functional-programming ramda.js


【解决方案1】:

and 构造一个新的 reducer,(r, x) => ...,通过组合两 (2) 个输入 reducer,fg -

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)

使用all 定义myReducer -

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' }

展开下面的 sn-p 以在浏览器中验证结果 -

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

【讨论】:

    【解决方案2】:

    在这里使用 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")
    )
    &lt;script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"&gt;&lt;/script&gt;

    【讨论】:

    • 酷,我不知道R.chain 支持这种方式的功能。小拼写更正:kleisli
    • 你永远不会知道,有一天我们可能会收到Function.prototype.flatMap :D
    【解决方案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,但它们似乎不会在这里添加太多内容。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-12-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-09
      • 2021-07-16
      相关资源
      最近更新 更多