以累加器作为最终参数的无点Reduce函数-函数式编程-Javascript-Immutable.js

以累加器作为最终参数的无点Reduce函数-函数式编程-Javascript-Immutable.js,javascript,functional-programming,lodash,immutable.js,pointfree,Javascript,Functional Programming,Lodash,Immutable.js,Pointfree,我遇到了一种模式,我觉得可能是某种反模式,或者可能有更好的方法来实现 请考虑以下实用程序函数,该函数重命名对象中的键,类似于使用terminal命令重命名文件mv 从'lodash/fp'导入{curry,get,omit,pipe,set,reduce} 常数mv=咖喱( (旧路径、新路径、源)=> 获取(旧路径,源) ?管道( set(newPath,get(oldPath,source)), 省略(旧路径) )(来源) :来源 ) 测试('mv',()=>{ 常量数据集={a:'z',b:

我遇到了一种模式,我觉得可能是某种反模式,或者可能有更好的方法来实现

请考虑以下实用程序函数,该函数重命名对象中的键,类似于使用terminal命令重命名文件
mv

从'lodash/fp'导入{curry,get,omit,pipe,set,reduce}
常数mv=咖喱(
(旧路径、新路径、源)=>
获取(旧路径,源)
?管道(
set(newPath,get(oldPath,source)),
省略(旧路径)
)(来源)
:来源
)
测试('mv',()=>{
常量数据集={a:'z',b:'y',c:'x'}
预期常数={a:'z',q:'y',c:'x'}
常量结果=mv('b','q',大数据集)
期望(结果)toEqual(期望)
})
这只是一个可以在任何地方使用的示例函数。接下来考虑一个大数据集,它可能有一个重命名的小列表。
test('mvMore',()=>{
常量数据集={a:'z',b:'y',c:'x'}
预期常数={a:'z',q:'y',m:'x'}
常量keysToRename=['b','q'],['c','m']]
常数结果=减少(
(acc,[oldPath,newPath])=>mv(oldPath,newPath,acc),
大数据集,
密钥重命名
)
期望(结果)toEqual(期望)
})
现在我们来讨论我的问题,它围绕着一个模式,在这个模式中,你可能有一个大的数据集和许多类似于
mv
的不同操作的小列表来对所述数据集执行。设置一个无点管道将数据集从一个reduce函数传递到下一个reduce函数似乎是理想的;但是,每个都必须将数据集作为累加器参数传递,因为我们不是在数据集上迭代,而是在一个小的操作列表上迭代

test('pipemvmore和类似的转换',()=>{
常量数据集={a:'z',b:'y',c:'x'}
应为常数={u:'z',r:'y',m:'x'}
常量keysToRename=['b','q'],['c','m']]
常量keysToRename2=[['q','r'],['a','u']]
const mvCall=(source,[oldPath,newPath])=>mv(oldPath,newPath,source)
const reduceaclast=咖喱((fn,it,acc)=>reduce(fn,acc,it))
常数结果=管道(
//想象一下其他类似的变换
reduceaclast(mvCall,keystername),
//想象一下其他类似的变换
reduceaclast(mvCall,keysToRename2)
)(大数据集)
期望(结果)toEqual(期望)
})
我的问题是,这是某种反模式,还是有更好的方法来实现同样的结果。让我吃惊的是,通常情况下,reducer函数的累加器参数被用作内部状态,数据集被迭代;然而,这里的情况正好相反。大多数reducer iteratee函数都会对累加器进行变异,因为它只在内部使用。在这里,数据集作为累加器参数从一个reducer传递到另一个reducer,因为在一个大数据集上进行迭代是没有意义的,因为在这个大数据集上只有几个操作的列表要在数据集上执行。只要reducer iteratee函数(例如,
mv
不改变累加器),该模式是否有任何问题,或者是否缺少一些简单的东西



根据@tokland的回答,我重新编写了测试,使用Immutable.js来查看不变性的保证和性能上的潜在收益是否值得付出努力。互联网上有一些关于Immutable.js不适合无点风格的函数式编程的喧嚣。这是有道理的;然而,这并不多。据我所知,我们所要做的就是编写一些基本函数来调用您想要使用的方法,例如,
map
filter
reduce
。不处理Javascript数组或对象的Lodash函数仍然可以使用;换句话说,处理函数(如
curry
pipe
)或字符串(如
大写
)的Lodash函数似乎很好

import { curry, pipe, upperCase } from 'lodash/fp'
import { Map } from 'immutable'

const remove = curry((oldPath, imm) => imm.remove(oldPath))
const get = curry((path, imm) => imm.get(path))
const set = curry((path, source, imm) => imm.set(path, source))
const reduce = curry((fn, acc, it) => it.reduce(fn, acc))
const reduceAcc = curry((fn, it, acc) => reduce(fn, acc, it))
const map = curry((fn, input) => input.map(fn))

const mv = curry((oldPath, newPath, source) =>
  pipe(
    set(newPath, get(oldPath, source)),
    remove(oldPath)
  )(source)
)

const mvCall = (acc, newPath, oldPath) => mv(oldPath, newPath, acc)

function log(x) {
  console.log(x)
  return x
}

test('mv', () => {
  const largeDataSet = Map({ a: 'z', b: 'y', c: 'x' })
  const expected = Map({ a: 'z', q: 'y', c: 'x' })
  const result = mv('b', 'q', largeDataSet)

  expect(result).toEqual(expected)
})

test('mvMore', () => {
  const largeDataSet = Map({ a: 'z', b: 'y', c: 'x' })
  const expected = Map({ a: 'z', q: 'y', m: 'x' })
  const keysToRename = Map({ b: 'q', c: 'm' })
  const result = reduce(mvCall, largeDataSet, keysToRename)

  expect(result).toEqual(expected)
})

test('pipe mvMore and similar transforms', () => {
  const largeDataSet = Map({ a: 'z', b: 'y', c: 'x' })
  const expected = Map({ u: 'Z', r: 'Y', m: 'X' })
  const keysToRename = Map({ b: 'q', c: 'm' })
  const keysToRename2 = Map({ q: 'r', a: 'u' })

  const result = pipe(
    reduceAcc(mvCall, keysToRename),
    reduceAcc(mvCall, keysToRename2),
    map(upperCase)
  )(largeDataSet)

  const result2 = keysToRename2
    .reduce(mvCall, keysToRename.reduce(mvCall, largeDataSet))
    .map(upperCase)

  expect(result).toEqual(expected)
  expect(result2).toEqual(expected)
})
import { curry, pipe, upperCase } from 'lodash/fp'
import { Map } from 'immutable'

const remove = curry((oldPath, imm) => imm.remove(oldPath))
const get = curry((path, imm) => imm.get(path))
const set = curry((path, source, imm) => imm.set(path, source))
const reduce = curry((fn, acc, it) => it.reduce(fn, acc))
const reduceAcc = curry((fn, it, acc) => reduce(fn, acc, it))
const map = curry((fn, input) => input.map(fn))

const mv = curry((oldPath, newPath, source) =>
  pipe(
    set(newPath, get(oldPath, source)),
    remove(oldPath)
  )(source)
)

const mvCall = (acc, newPath, oldPath) => mv(oldPath, newPath, acc)

function log(x) {
  console.log(x)
  return x
}

test('mv', () => {
  const largeDataSet = Map({ a: 'z', b: 'y', c: 'x' })
  const expected = Map({ a: 'z', q: 'y', c: 'x' })
  const result = mv('b', 'q', largeDataSet)

  expect(result).toEqual(expected)
})

test('mvMore', () => {
  const largeDataSet = Map({ a: 'z', b: 'y', c: 'x' })
  const expected = Map({ a: 'z', q: 'y', m: 'x' })
  const keysToRename = Map({ b: 'q', c: 'm' })
  const result = reduce(mvCall, largeDataSet, keysToRename)

  expect(result).toEqual(expected)
})

test('pipe mvMore and similar transforms', () => {
  const largeDataSet = Map({ a: 'z', b: 'y', c: 'x' })
  const expected = Map({ u: 'Z', r: 'Y', m: 'X' })
  const keysToRename = Map({ b: 'q', c: 'm' })
  const keysToRename2 = Map({ q: 'r', a: 'u' })

  const result = pipe(
    reduceAcc(mvCall, keysToRename),
    reduceAcc(mvCall, keysToRename2),
    map(upperCase)
  )(largeDataSet)

  const result2 = keysToRename2
    .reduce(mvCall, keysToRename.reduce(mvCall, largeDataSet))
    .map(upperCase)

  expect(result).toEqual(expected)
  expect(result2).toEqual(expected)
})

Typescript似乎在处理高阶函数时遇到了一些问题,因此如果您使用
tsc
进行测试,则必须在
管道之前抛出
/@ts ignore
s。我认为您的问题的答案是肯定的和否定的。我的意思是,在函数编程中,纯函数是一件事,您正在尝试这样做它以一种功能性的方式运行,但会改变输入。所以我认为你需要考虑一个类似于Loasy/FP >如何:

虽然lodash/fp及其方法模块经过预转换,但仍有 您可能希望自定义转换的时间。就在那时 convert方法很方便

//默认情况下,每个选项都是
true


注意那里的
不可变的
转换器。这就是我回答的
部分。。。但是
no
的一部分是,您仍然需要一个
不可变的
方法作为默认值,以实现真正的纯/功能性。

您的方法没有任何问题。有时折叠输入对象,有时将其用作初始累加器,这取决于算法。如果一个减速机改变了函数调用方传递的值,那么无论何时需要不可变性,都不能使用这个减速机


也就是说,根据对象的大小(输入、键映射),代码可能会有性能问题。每次更改关键点时,都会创建一个全新的对象。如果您发现这是一个问题,您通常会使用一些高效的不可变结构来重用输入数据(映射不需要,因为您不更新它们)。以immutable.js为例。

基于@tokland的回答,我重新编写了使用immutable.js的测试,以查看不变性的保证和性能方面的潜在收益是否值得付出努力。互联网上有一些关于
import { curry, pipe, upperCase } from 'lodash/fp'
import { Map } from 'immutable'

const remove = curry((oldPath, imm) => imm.remove(oldPath))
const get = curry((path, imm) => imm.get(path))
const set = curry((path, source, imm) => imm.set(path, source))
const reduce = curry((fn, acc, it) => it.reduce(fn, acc))
const reduceAcc = curry((fn, it, acc) => reduce(fn, acc, it))
const map = curry((fn, input) => input.map(fn))

const mv = curry((oldPath, newPath, source) =>
  pipe(
    set(newPath, get(oldPath, source)),
    remove(oldPath)
  )(source)
)

const mvCall = (acc, newPath, oldPath) => mv(oldPath, newPath, acc)

function log(x) {
  console.log(x)
  return x
}

test('mv', () => {
  const largeDataSet = Map({ a: 'z', b: 'y', c: 'x' })
  const expected = Map({ a: 'z', q: 'y', c: 'x' })
  const result = mv('b', 'q', largeDataSet)

  expect(result).toEqual(expected)
})

test('mvMore', () => {
  const largeDataSet = Map({ a: 'z', b: 'y', c: 'x' })
  const expected = Map({ a: 'z', q: 'y', m: 'x' })
  const keysToRename = Map({ b: 'q', c: 'm' })
  const result = reduce(mvCall, largeDataSet, keysToRename)

  expect(result).toEqual(expected)
})

test('pipe mvMore and similar transforms', () => {
  const largeDataSet = Map({ a: 'z', b: 'y', c: 'x' })
  const expected = Map({ u: 'Z', r: 'Y', m: 'X' })
  const keysToRename = Map({ b: 'q', c: 'm' })
  const keysToRename2 = Map({ q: 'r', a: 'u' })

  const result = pipe(
    reduceAcc(mvCall, keysToRename),
    reduceAcc(mvCall, keysToRename2),
    map(upperCase)
  )(largeDataSet)

  const result2 = keysToRename2
    .reduce(mvCall, keysToRename.reduce(mvCall, largeDataSet))
    .map(upperCase)

  expect(result).toEqual(expected)
  expect(result2).toEqual(expected)
})