数组:有条件地映射元素

时间:2017-11-09 10:13:20

标签: javascript arrays functional-programming

我使用Redux,我经常遇到用这样的表达式编写reducers:

return users.map(user =>
  user.id !== selectedUserID ? user : {
    ... user,
    removed: false
  }
);

意图应该足够清楚:只修改数组中具有给定id的项目,同时复制其他项目。另请注意,排序很重要,否则我可能只使用了filter()函数。

此代码段会在eslint上触发no-confusing-arrow错误,这对我来说很有意义。这也可以通过在箭头函数体周围添加圆括号来轻松解决,所以这里没什么大不了的:

return users.map(user => (
  user.id !== selectedUserID ? user : {
    ... user,
    removed: false
  }
));

我还希望通过prettier解析此代码,它会自动删除箭头函数体周围的括号,返回到代码片段的第1版。

这里显而易见的解决方案是用冗长的方式编写箭头函数:

return users.map(user => {
  if(user.id !== selectedUserID) {
    return user; // unmodified user
  }
  return {
    ... user,
    removed: false
  };
});

但老实说,我觉得它有点太笨拙了。

除了特定的上下文和使用的工具(eslint和更漂亮,可以配置不同/关闭/无论如何),有没有更好的方法来写这个?

在我最疯狂的梦想中,它存在一个具有类似于:

的签名的函数
Array.mapIf(callback, condition)

循环数组中的所有元素,并仅将callback函数调用给满足给定condition的函数,同时返回未修改的其他元素。

我显然可以自己编写这样的函数,但也许其他函数式语言中已经存在一些值得关注的一般文化/灵感。

4 个答案:

答案 0 :(得分:3)

没有这样的原生功能,因为您可以自己轻松实现它:

const mapWhen = (p, f) => xs => xs.map(x => p(x) ? f(x) : x);

const users = [
  {id: 1, removed: true},
  {id: 2, removed: true},
  {id: 3, removed: true}
];

console.log(
  mapWhen(
    ({id}) => id === 2,
    user => Object.assign({}, user, {removed: false})
  ) (users)
);

我选择mapWhen作为名称而不是mapIf,因为后者意味着有一个else分支。

使用成熟的函数式语言,您可能会使用functional lense来解决此问题。不过,我认为,mapWhen足以满足您的需求并且更具惯用性。

答案 1 :(得分:2)

在红宝石中,你会返回类似

的内容
users.dup.select {|u| u.id == selected_user_id }.each {|u| u.removed = false }
在这种情况下,

dup很重要,因为在Redux中,您希望返回一个新数组而不是修改原始数组,因此首先必须创建原始数据的副本。请参阅https://stackoverflow.com/a/625746/1627766(注意讨论与此非常相似)

在你的情况下,我会使用:

const users = [
  {id: 1, removed: true},
  {id: 2, removed: true},
  {id: 3, removed: true}
];

const selectedUserId = 2;

console.log(
  users.map(
    (user) => ({
      ...user,
      removed: (user.id !== selectedUserId ? false : user.removed)
    })
  )
);

答案 2 :(得分:0)

如果需要,您可以使用Object.assign添加属性。

return users.map(user =>
    Object.assign({}, user, user.id === selectedUserID && { removed: false })
);

答案 3 :(得分:0)

我将提供这个作为@ ftor实际答案的补充 - 这个片段展示了如何使用泛型函数来完成某种结果

const identity = x =>
  x

const comp = ( f, g ) =>
  x => f ( g ( x ) )
  
const compose = ( ...fs ) =>
  fs.reduce ( comp, identity )
  
const append = ( xs, x ) =>
  xs.concat ( [ x ] )

const transduce = ( ...ts ) => xs =>
  xs.reduce ( compose ( ...ts ) ( append ), [] )

const when = f =>
  k => ( acc, x ) => f ( x ) ? k ( acc, x ) : append ( acc, x )

const mapper = f =>
  k => ( acc, x ) => k ( acc, f ( x ) )

const data0 =
  [ { id: 1, remove: true }
  , { id: 2, remove: true }
  , { id: 3, remove: true }
  ]

const data1 =
  transduce ( when ( x => x.id === 2)
            , mapper ( x => ( { ...x, remove: false } ) )
            )
            (data0)
            
console.log (data1)
// [ { id: 1, remove: true }
// , { id: 2, remove: false }
// , { id: 3, remove: true }
// ]

但是,我认为我们可以通过更通用的行为改进when,我们需要的只是一些发明类型LeftRight的帮助 - 在此之后展开完整的代码段摘录

const Left = value =>
  ({ fold: ( f, _ ) =>
       f ( value )
  })

const Right = value =>
  ({ fold: ( _, f ) =>
       f ( value )
  })

// a more generic when
const when = f =>
  k => ( acc, x ) => f ( x ) ? k ( acc, Right ( x ) ) : k ( acc, Left ( x ) )

// mapping over Left/Right
mapper ( m =>
  m.fold ( identity                         // Left branch
         , x => ( { ...x, remove: false } ) // Right branch
         ) ) 

const Left = value =>
  ({ fold: ( f, _ ) =>
       f ( value )
  })
  
const Right = value =>
  ({ fold: ( _, f ) =>
       f ( value )
  })

const identity = x =>
  x

const comp = ( f, g ) =>
  x => f ( g ( x ) )
  
const compose = ( ...fs ) =>
  fs.reduce ( comp, identity )
  
const append = ( xs, x ) =>
  xs.concat ( [ x ] )

const transduce = ( ...ts ) => xs =>
  xs.reduce ( compose ( ...ts ) ( append ), [] )

const when = f =>
  k => ( acc, x ) => f ( x ) ? k ( acc, Right ( x ) ) : k ( acc, Left ( x ) )

const mapper = f =>
  k => ( acc, x ) => k ( acc, f ( x ) )

const data0 =
  [ { id: 1, remove: true }
  , { id: 2, remove: true }
  , { id: 3, remove: true }
  ]

const data1 =
  transduce ( when ( x => x.id === 2 )
            , mapper ( m =>
               m.fold ( identity
                      , x => ( { ...x, remove: false } ) 
                      ) )
            )
            (data0)
            
console.log (data1)
// [ { id: 1, remove: true }
// , { id: 2, remove: false }
// , { id: 3, remove: true }
// ]

所以你的最终功能可能看起来像这样

const updateWhen = ( f, records, xs ) =>
  transduce ( when ( f )
            , mapper ( m =>
                m.fold ( identity
                       , x => ( { ...x, ...records } )
                       ) )
            ) ( xs )

你会在你的减速机中称它为

const BakeryReducer = ( donuts = initState , action ) =>
  {
    switch ( action.type ) {
      case ApplyGlaze:
        return updateWhen ( donut => donut.id === action.donutId )
                          , { glaze: action.glaze }
                          , donuts
                          )
      // ...
      default:
        return donuts
    }
  }