Splitting/nesting _.flow 使用 lodash(或 ramda)
Splitting/nesting _.flow with lodash (or ramda)
我有两个对象,一个描述一个位置的features
,另一个描述那些特征的prices
。
features = {
improvements: [...] // any array of many id's
building: {} // only one id, may be undefined
}
prices = {
id_1: 10,
...
}
我想遍历 features
并整理所有 prices
。 有时features.building
会是undefined
,有时features.improvements
会是空的。
Additional code/workbench on repl.it
我可以用 lodash
以这种方式做到这一点:
result = _(features.improvements)
.map(feature => prices[feature.id])
.concat(_.cond([
[_.isObject, () => prices[features.building.id]]
])(features.building)) // would like to clean this up
.compact()
.value();
我有兴趣以更实用的方式编写它,最后我得到了:
result = _.flow([
_.partialRight(_.map, feature => prices[feature.id]),
_.partialRight(_.concat, _.cond([
[_.isObject, () => prices[features.building.id]]
])(features.building)),
_.compact,
])(features.improvements)
还得差点偷偷叫features.building
中流,感觉很别扭
我想得到的是(伪代码):
flow([
// maybe need some kind of "split([[val, funcs], [val, funcs]])?
// the below doesn't work because the first
// flow's result ends up in the second
// do the improvement getting
flow([
_.map(feature => prices[feature.id])
])(_.get('improvements')),
// do the building getting
flow([
_.cond([
[_.isObject, () => [argument.id]]
])
])(_.get('building')),
// concat and compact the results of both gets
_.concat,
_.compact,
])(features); // just passing the root object in
可能吗?经验丰富的 FP 程序员将如何处理这个问题?
我愿意接受用 lodash-fp
或 rambda
编写的解决方案(或任何我可以尝试理解的好文档),因为它们可能会提供更清晰的代码,因为它们更 functionally-orientated/curried 比标准 lodash
.
Lodash
这是一个使用_.flow()
的解决方案:
- 使用
_.values()
、_.flatten()
和 _.compact()
将特征转换为数组(在 undefined
时忽略 building
)。
- 转换为
id
的数组 _.map()
。
- 获取
_.at()
的值。
const { values, flatten, compact, partialRight: pr, map, partial, at } = _;
const fn = prices => _.flow([
values,
flatten,
compact,
pr(map, 'id'),
partial(at, prices)
])
const prices = {
i_1: 'cost_i_1',
i_2: 'cost_i_2',
i_3: 'cost_i_3',
i_4: 'cost_i_4',
b_1: 'cost_b_1',
};
const features = {
improvements: [
{id: 'i_1'},
{id: 'i_2'},
{id: 'i_3'},
{id: 'i_4'},
],
building: {
id: 'b_1'
},
};
const result = fn(prices)(features);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
lodash/fp
const { values, flatten, compact, map, propertyOf } = _;
const fn = prices => _.flow([
values,
flatten,
compact,
map('id'),
map(propertyOf(prices))
])
const prices = {"i_1":"cost_i_1","i_2":"cost_i_2","i_3":"cost_i_3","i_4":"cost_i_4","b_1":"cost_b_1"};
const features = {"improvements":[{"id":"i_1"},{"id":"i_2"},{"id":"i_3"},{"id":"i_4"}],"building":{"id":"b_1"}};
const result = fn(prices)(features);
console.log(result);
<script src='https://cdn.jsdelivr.net/g/lodash@4(lodash.min.js+lodash.fp.min.js)'></script>
拉姆达
- 使用
R.identity
. 获取值、展平和过滤 undefined
s
- 通过
R.map
获得 id
道具。
- 使用翻转的
R.props
从 prices
获取 ids 值
const { pipe, values, flatten, filter, identity, map, prop, flip, props } = R;
const propsOf = flip(props);
const fn = prices => pipe(
values,
flatten,
filter(identity),
map(prop('id')),
propsOf(prices)
);
const prices = {"i_1":"cost_i_1","i_2":"cost_i_2","i_3":"cost_i_3","i_4":"cost_i_4","b_1":"cost_b_1"};
const features = {"improvements":[{"id":"i_1"},{"id":"i_2"},{"id":"i_3"},{"id":"i_4"}],"building":{"id":"b_1"}};
const result = fn(prices)(features);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
这是我使用 Ramda 的建议。
我建议将问题分解成更小的函数:
- 获取所有改进的价格 ID:
getImprovementIds
- 获取建筑物的价格 ID:
getBuildingId
- 获取所有商品的价格 ID:
getPriceIds
- 获取给定价格 ID 的价格:
getPrice
例子
getImprovementIds(features); //=> ['id_1', 'id_2', 'id_3']
getBuildingIds(features); //=> ['id_5']
getPriceIds(features); //=> ['id_1', 'id_2', 'id_3', 'id_5']
getPrice(prices, 'id_2'); //=> 20
getPrice(prices, 'foo'); //=> 0
获得价格 ID 列表后,可以轻松将该列表转换为价目表:
map(getPrice(prices), ['id_1', 'id_2', 'id_3', 'id_5']); //=> [10, 20, 0, 50]
完整示例
const {propOr, ifElse, hasPath, path, always, compose, sum, map, flip, converge, of, concat} = R;
const features = {
improvements: ['id_1', 'id_2', 'id_3'],
building: {
id: 'id_5'
}
};
const prices = {
id_1: 10,
id_2: 20,
id_5: 50
};
/**
* Take a features object and return the price id of all improvements.
* @param {object} features
* @return {array} array of ids
*/
const getImprovementIds = propOr([], 'improvements');
/**
* Take a features object and return the price id of the building (if any)
* @param {object} features
* @return {array} array of ids
*/
const getBuildingId =
ifElse(hasPath(['building', 'id']),
compose(of, path(['building', 'id'])),
always([]));
/**
* Take a features object and returns all price id of all improvements and of the building (if any)
* @param {object} features
* @return {array} array of ids
*/
const getPriceIds = converge(concat, [getImprovementIds, getBuildingId]);
/**
* Take a prices object and a price id and return the corresponding price
*
* @example
* getPrice(prices, 'id_2'); //=> 20
*
* @param {object} prices
* @param {string} id
* @return {number}
*/
const getPrice = flip(propOr(0));
const getPriceList = (prices, features) =>
map(getPrice(prices), getPriceIds(features));
console.log(
getPriceList(prices, features)
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
我有两个对象,一个描述一个位置的features
,另一个描述那些特征的prices
。
features = {
improvements: [...] // any array of many id's
building: {} // only one id, may be undefined
}
prices = {
id_1: 10,
...
}
我想遍历 features
并整理所有 prices
。 有时features.building
会是undefined
,有时features.improvements
会是空的。
Additional code/workbench on repl.it
我可以用 lodash
以这种方式做到这一点:
result = _(features.improvements)
.map(feature => prices[feature.id])
.concat(_.cond([
[_.isObject, () => prices[features.building.id]]
])(features.building)) // would like to clean this up
.compact()
.value();
我有兴趣以更实用的方式编写它,最后我得到了:
result = _.flow([
_.partialRight(_.map, feature => prices[feature.id]),
_.partialRight(_.concat, _.cond([
[_.isObject, () => prices[features.building.id]]
])(features.building)),
_.compact,
])(features.improvements)
还得差点偷偷叫features.building
中流,感觉很别扭
我想得到的是(伪代码):
flow([
// maybe need some kind of "split([[val, funcs], [val, funcs]])?
// the below doesn't work because the first
// flow's result ends up in the second
// do the improvement getting
flow([
_.map(feature => prices[feature.id])
])(_.get('improvements')),
// do the building getting
flow([
_.cond([
[_.isObject, () => [argument.id]]
])
])(_.get('building')),
// concat and compact the results of both gets
_.concat,
_.compact,
])(features); // just passing the root object in
可能吗?经验丰富的 FP 程序员将如何处理这个问题?
我愿意接受用 lodash-fp
或 rambda
编写的解决方案(或任何我可以尝试理解的好文档),因为它们可能会提供更清晰的代码,因为它们更 functionally-orientated/curried 比标准 lodash
.
Lodash
这是一个使用_.flow()
的解决方案:
- 使用
_.values()
、_.flatten()
和_.compact()
将特征转换为数组(在undefined
时忽略building
)。 - 转换为
id
的数组_.map()
。 - 获取
_.at()
的值。
const { values, flatten, compact, partialRight: pr, map, partial, at } = _;
const fn = prices => _.flow([
values,
flatten,
compact,
pr(map, 'id'),
partial(at, prices)
])
const prices = {
i_1: 'cost_i_1',
i_2: 'cost_i_2',
i_3: 'cost_i_3',
i_4: 'cost_i_4',
b_1: 'cost_b_1',
};
const features = {
improvements: [
{id: 'i_1'},
{id: 'i_2'},
{id: 'i_3'},
{id: 'i_4'},
],
building: {
id: 'b_1'
},
};
const result = fn(prices)(features);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
lodash/fp
const { values, flatten, compact, map, propertyOf } = _;
const fn = prices => _.flow([
values,
flatten,
compact,
map('id'),
map(propertyOf(prices))
])
const prices = {"i_1":"cost_i_1","i_2":"cost_i_2","i_3":"cost_i_3","i_4":"cost_i_4","b_1":"cost_b_1"};
const features = {"improvements":[{"id":"i_1"},{"id":"i_2"},{"id":"i_3"},{"id":"i_4"}],"building":{"id":"b_1"}};
const result = fn(prices)(features);
console.log(result);
<script src='https://cdn.jsdelivr.net/g/lodash@4(lodash.min.js+lodash.fp.min.js)'></script>
拉姆达
- 使用
R.identity
. 获取值、展平和过滤 - 通过
R.map
获得id
道具。 - 使用翻转的
R.props
从prices
获取 ids 值
undefined
s
const { pipe, values, flatten, filter, identity, map, prop, flip, props } = R;
const propsOf = flip(props);
const fn = prices => pipe(
values,
flatten,
filter(identity),
map(prop('id')),
propsOf(prices)
);
const prices = {"i_1":"cost_i_1","i_2":"cost_i_2","i_3":"cost_i_3","i_4":"cost_i_4","b_1":"cost_b_1"};
const features = {"improvements":[{"id":"i_1"},{"id":"i_2"},{"id":"i_3"},{"id":"i_4"}],"building":{"id":"b_1"}};
const result = fn(prices)(features);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
这是我使用 Ramda 的建议。
我建议将问题分解成更小的函数:
- 获取所有改进的价格 ID:
getImprovementIds
- 获取建筑物的价格 ID:
getBuildingId
- 获取所有商品的价格 ID:
getPriceIds
- 获取给定价格 ID 的价格:
getPrice
例子
getImprovementIds(features); //=> ['id_1', 'id_2', 'id_3']
getBuildingIds(features); //=> ['id_5']
getPriceIds(features); //=> ['id_1', 'id_2', 'id_3', 'id_5']
getPrice(prices, 'id_2'); //=> 20
getPrice(prices, 'foo'); //=> 0
获得价格 ID 列表后,可以轻松将该列表转换为价目表:
map(getPrice(prices), ['id_1', 'id_2', 'id_3', 'id_5']); //=> [10, 20, 0, 50]
完整示例
const {propOr, ifElse, hasPath, path, always, compose, sum, map, flip, converge, of, concat} = R;
const features = {
improvements: ['id_1', 'id_2', 'id_3'],
building: {
id: 'id_5'
}
};
const prices = {
id_1: 10,
id_2: 20,
id_5: 50
};
/**
* Take a features object and return the price id of all improvements.
* @param {object} features
* @return {array} array of ids
*/
const getImprovementIds = propOr([], 'improvements');
/**
* Take a features object and return the price id of the building (if any)
* @param {object} features
* @return {array} array of ids
*/
const getBuildingId =
ifElse(hasPath(['building', 'id']),
compose(of, path(['building', 'id'])),
always([]));
/**
* Take a features object and returns all price id of all improvements and of the building (if any)
* @param {object} features
* @return {array} array of ids
*/
const getPriceIds = converge(concat, [getImprovementIds, getBuildingId]);
/**
* Take a prices object and a price id and return the corresponding price
*
* @example
* getPrice(prices, 'id_2'); //=> 20
*
* @param {object} prices
* @param {string} id
* @return {number}
*/
const getPrice = flip(propOr(0));
const getPriceList = (prices, features) =>
map(getPrice(prices), getPriceIds(features));
console.log(
getPriceList(prices, features)
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>