【发布时间】:2019-10-17 03:55:23
【问题描述】:
我有一个称为“规范”的数据结构,如下所示:
const spec = {
command: {
name: 'name',
description: 'description',
alias: 'alias',
arguments: '_children/Arguments'
},
arguments: {
name: 'name',
alias: 'alias',
optional: 'optional',
description: 'description'
}
};
所以 command 和 arguments 中的元素是映射到路径的属性。 spec.command.arguments 就是最好的说明。我需要做的是把它转换成另一个形状相同的物体,但是路径被转换成 Ramda 镜头(使用 R.lensPath)。
所以从概念上讲,这被翻译成这样的:
const spec = {
command: {
name: lens('name'),
description: lens('description'),
alias: lens('alias'),
arguments: lens('_children/Arguments')
},
arguments: {
name: lens('name'),
alias: lens('alias'),
optional: lens('optional'),
description: lens('description')
}
};
以上不是字面意思,它是一个伪结构。例如 lens('_children/Arguments') 仅表示使用 Ramda lensPath 构建的镜头。
这是我的代码:
const spec = {
command: {
name: 'name',
description: 'description',
alias: 'alias',
arguments: '_children/Arguments'
},
arguments: {
name: 'name',
alias: 'alias',
optional: 'optional',
description: 'description'
}
};
function lensify (spec) {
const result = R.pipe(
R.toPairs,
R.reduce((acc, pair) => {
const field = pair[0];
const path = pair[1];
const lens = R.compose(
R.lensPath,
R.split('/')
)(path);
acc[field] = lens; // Is there something wrong with this, if so what?
return acc;
}, { dummy: '***' }) // list of pairs passed as last param here
)(spec);
// The following log should show entries for 'name', 'description', 'alias' ...
console.log(`+++ lensify RESULT: ${JSON.stringify(result)}`);
return result;
}
function makeLenses (spec) {
const result = {
command: lensify(spec.command),
arguments: lensify(spec.arguments)
};
return result;
}
makeLenses(spec);
我认为失败的关键点在reducer函数内部,它返回更新的累加器(acc[field] = lens;)。由于某种我无法理解的原因,这个分配正在丢失,并且每次迭代都没有正确填充累加器。从代码示例中可以看出,传递给 reduce 的初始值是一个具有单个 dummy 属性的对象。减少的结果错误地只是这个单一的虚拟值,而不是所有具有各自 Ramda 镜头的字段。
然而,真正让你吃不消的是,在 Ramda repl 中运行的完全相同的代码表现出不同的行为,请参阅 repl 中的此代码:Ramda code
我正在运行节点版本 10.13.0
Repl 代码产生的结果是这样的:
{
'arguments': {
'alias': function (r) {
return function (e) {
return z(function (t) {
return n(t, e)
}, r(t(e)))
}
},
'description': function (r) {
return function (e) {
return z(function (t) {
return n(t, e)
}, r(t(e)))
}
},
'dummy': '***',
'name': function (r) {
return function (e) {
return z(function (t) {
return n(t, e)
}, r(t(e)))
}
},
'optional': function (r) {
return function (e) {
return z(function (t) {
return n(t, e)
}, r(t(e)))
}
}
},
'command': {
'alias': function (r) {
return function (e) {
return z(function (t) {
return n(t, e)
}, r(t(e)))
}
},
'arguments': function (r) {
return function (e) {
return z(function (t) {
return n(t, e)
}, r(t(e)))
}
},
'description': function (r) {
return function (e) {
return z(function (t) {
return n(t, e)
}, r(t(e)))
}
},
'dummy': '***',
'name': function (r) {
return function (e) {
return z(function (t) {
return n(t, e)
}, r(t(e)))
}
}
}
}
如您所见,结果看起来有点复杂,因为每个属性的值都是由 lensProp 创建的镜头。
这与以下相反(注意命令和参数的顺序是相反的,但这不应该很重要):
{
'command': {
'dummy': '***'
},
'arguments': {
'dummy': '***'
}
}
在我的单元测试中返回。
我在这件事上浪费了大约 2 天的时间,现在承认失败了,所以希望有人能对此有所了解。干杯。
【问题讨论】:
-
我真的不确定你的问题是什么。 REPL 中的结果对我来说是正确的。 (请注意,
JSON.stringify不会为您提供有关函数属性的有用结果。)您返回一个具有arguments和command属性的对象,每个属性都是包含name、alias和arguments和optional属性,每人拿着一个镜头。这不是你想要的吗? -
请注意,只要您实际上并不想要
dummy属性,您可以将lensify更简单地写为const lensify = map(pipe(split('/'), lensPath))。 -
您的最终 REPL 结果未包含在
JSON.stringify中。它使用 REPL 更复杂的显示功能。JSON.stringify(makeLenses(spec)) //=> "{\"command\":{\"dummy\":\"***\"},\"arguments\":{\"dummy\":\"***\"}}" -
另外,您可以跳过
lensify和const makeLenses = map(map(pipe(split('/'), lensPath))),假设您要将其应用于规范的所有元素。 -
实际上,我意识到我在我的一些客户端代码中做了一个很大的嘘声,这导致了我在尝试访问镜头时看到的未定义。它现在可以工作并产生与 Repl 相同的结果。谢谢你帮助斯科特。我将在我的最终代码中使用您更精简的版本,尽管我已经知道我将对其进行精简。我不知道的一件事是 repl 增强的显示能力,这让我感到困惑。我使用的 console.log/JSON.stringify 语句没有显示 Repl 所做的镜头属性,这让我陷入了困境!。
标签: javascript functional-programming reduce ramda.js