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

标签 javascript functional-programming lodash immutable.js pointfree

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

考虑以下实用程序函数,它重命名对象中的键,类似于使用终端命令 mv 重命名文件。

import { curry, get, omit, pipe, set, reduce } from 'lodash/fp'

const mv = curry(
  (oldPath, newPath, source) =>
    get(oldPath, source)
      ? pipe(
          set(newPath, get(oldPath, source)),
          omit(oldPath)
        )(source)
      : source
)

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

  expect(result).toEqual(expected)
})

这只是一个可以在任何地方使用的示例函数。接下来考虑一个大型数据集,其中可能有一小部分要重命名的键。

test('mvMore', () => {
  const largeDataSet = { a: 'z', b: 'y', c: 'x' }
  const expected = { a: 'z', q: 'y', m: 'x' }
  const keysToRename = [['b', 'q'], ['c', 'm']]

  const result = reduce(
    (acc, [oldPath, newPath]) => mv(oldPath, newPath, acc),
    largeDataSet,
    keysToRename
  )

  expect(result).toEqual(expected)
})

现在我们进入我的问题的主题,该主题围绕一种模式,在该模式中,您可能有一个大型数据集和许多类似于 mv 的不同操作的小列表,以对所述数据集执行。设置一个无点管道将数据集从一个归约函数传递到下一个似乎是理想的选择;但是,每个都必须将数据集作为累加器参数传递,因为我们不是在数据集上迭代,而是在一小部分操作上迭代。

test('pipe mvMore and similar transforms', () => {
  const largeDataSet = { a: 'z', b: 'y', c: 'x' }
  const expected = { u: 'z', r: 'y', m: 'x' }
  const keysToRename = [['b', 'q'], ['c', 'm']]
  const keysToRename2 = [['q', 'r'], ['a', 'u']]
  const mvCall = (source, [oldPath, newPath]) => mv(oldPath, newPath, source)
  const reduceAccLast = curry((fn, it, acc) => reduce(fn, acc, it))

  const result = pipe(
    // imagine other similar transform
    reduceAccLast(mvCall, keysToRename),
    // imagine other similar transform
    reduceAccLast(mvCall, keysToRename2)
  )(largeDataSet)

  expect(result).toEqual(expected)
})

我的问题是这是否是某种反模式,或者是否有更好的方法来实现相同的结果。令我惊愕的是,通常,reducer 函数的累加器参数被用作内部状态,并且数据集被迭代;然而,这里却恰恰相反。大多数 reducer 迭代函数都会改变累加器,并理解它仅在内部使用。在这里,数据集作为累加器参数从 reducer 传递到 reducer ,因为迭代大型数据集是没有意义的,因为在数据集上只有一些操作的列表。只要 reducer iteratee 函数(例如 mv)不会改变累加器,这种模式是否有任何问题,或者我是否缺少一些简单的东西?


根据 @tokland 的回答,我重写了测试以使用 Immutable.js 来查看不变性的保证和潜在的性能提升是否值得付出努力。互联网上有一些关于 Immutable.js 不太适合无点风格函数式编程的言论。这话有一定道理。不过,不多。据我所知,我们所要做的就是编写一些基本函数来调用您想要使用的方法,例如 mapfilterreduce 。不处理 Javascript 数组或对象的 Lodash 函数仍然可以使用;换句话说,处理函数的 Lodash 函数(例如 currypipe)或处理字符串(例如 upperCase)的 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)
})

Typescript 似乎在处理高阶函数时遇到一些问题,因此如果您使用 进行测试,则必须在 pipe 之前抛出 //@ts-ignore >tsc.

最佳答案

你的做法没有任何问题。有时您折叠输入对象,有时您将其用作初始累加器,这取决于算法。如果一个reducer改变了函数调用者传递的值,那么每当需要不变性时就不能使用这个reducer。

也就是说,您的代码可能存在性能问题,具体取决于对象的大小(输入、键映射)。每次更改 key 时,都会创建一个全新的对象。如果您发现这是一个问题,您通常会使用一些有效的不可变结构来重用输入数据(对于映射来说不是必需的,因为您不更新它们)。例如查看 Map来自 immutable.js。

关于javascript - 以累加器作为最终参数的无点归约函数 - 函数式编程 - Javascript - Immutable.js,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52581232/

相关文章:

haskell - 惰性评估如何允许更大的模块化?

Javascript 数组提供两个日期范围之间的所有日期/月份

javascript - 对映射函数中的特定 react 元素执行操作

c# - REST api 不调用导航

javascript - 如果存在无法识别的运算符 javascript,则返回一个值

Java 8 Generic 的 Generic for Monad Transformer

c++ - 映射/折叠运算符(在 C++ 中)

javascript - “功能性”根据另一个对象键/值从对象中删除值/键

angular - @Angular 中的 Lodash 替换

javascript - 递归 trim 对象中所有元素的更好方法?