Я столкнулся с шаблоном, который, по моему мнению, может быть своего рода анти-шаблоном, или, возможно, есть просто лучший способ сделать это.
Рассмотрим следующую служебную функцию, которая переименовывает ключ в объекте.аналогично переименованию файла с помощью команды терминала 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)
})
Мой вопрос заключается в том, является ли это антипаттерномкакой-то, или если есть лучший способ достичь того же результата.Меня пугает то, что обычно аргумент-накопитель функции-редуктора используется как внутреннее состояние, а набор данных повторяется;однако здесь все наоборот.Большинство итеративных функций-редукторов будут мутировать аккумулятор с пониманием того, что он используется только для внутреннего использования.Здесь набор данных передается от редуктора к редуктору в качестве аргумента накопителя, потому что нет смысла перебирать большой набор данных, где есть только списки из нескольких операций, которые нужно выполнить с набором данных.Пока функции итераторов редуктора, например, mv
, не изменяют аккумулятор, есть ли проблемы с этим шаблоном или есть что-то простое, что мне не хватает?
Основано на ответе @ tokland Iпереписал тесты для использования Immutable.js, чтобы посмотреть, стоили ли гарантии неизменности и потенциального прироста производительности.В интернете была некоторая шумиха о том, что Immutable.js не подходит для функционального программирования в стиле без точек.В этом есть доля правды;впрочем, не сильно.Из того, что я могу сказать, все, что нужно сделать, это написать несколько основных функций, которые вызывают методы, которые вы хотите использовать, например, map
, filter
, reduce
.Функции Lodash, которые не работают с массивами Javascript или объектами, все еще могут использоваться;другими словами, функции Lodash, которые имеют дело с функциями, такими как curry
и pipe
, или со строками, такими как upperCase
, кажутся хорошими.
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 есть некоторые проблемы с обработкойФункции упорядочения, поэтому вы должны увеличить // @ts-ignore
до pipe
, если вы тестируете с tsc
.