Ramda - 如何将动态参数传递给管道内的函数

Ramda - how to pass dynamic argument to function inside pipe

我正在尝试 add/use 管道内的一个变量,以从另一个对象获取一个对象的 name。这是我到目前为止得到的:

我有一个 ID 数组 allOutgoingNodes,我正在管道中使用它们。 然后我使用 tableItemId 属性 过滤结果然后我添加额外的 属性 externalStartingPoint 然后我想从 [=16 添加 tableItem 的名称=] 使用 concat.

反对 content -> html
const startingPointId = 395;

const allNodes = {
    "818": {
        "id": "818",
        "content": {
            "html": "<p>1</p>"
        },
        "outgoingNodes": [
            "819"
        ],
        "tableItemId": 395
    },
    "821": {
        "id": "821",
        "content": {
            "html": "<p>4</p>"
        },
        "tableItemId": 396
    }
}

const tableItems = {
    "395": {
        "id": "395",
        "name": "SP1",
        "code": "SP1"
    },
    "396": {
        "id": "396",
        "name": "SP2",
        "code": "SP2"
    }
}

const allOutgoingNodes = R.pipe(
  R.values,
  R.pluck('outgoingNodes'),
  R.flatten
)(tableItemNodes);

const result = R.pipe(
  R.pick(allOutgoingNodes),
  R.reject(R.propEq('tableItemId', startingPointId)),
  R.map(
    R.compose(
      R.assoc('externalStartingPoint', true),
      SomeMagicFunction(node.tableItemId),
      R.over(
        R.lensPath(['content', 'html']),
        R.concat(R.__, '<!-- Table item name should display here -->')
      )
    )
  ),
)(allNodes);

这是一个完整的工作示例:ramda editor

任何关于如何改进这段代码的帮助和建议将不胜感激。

谢谢。

更新

在评论中,OriDrori 指出我的第一个版本存在问题。我真的不明白其中一个要求。此版本试图解决该问题。

const {compose, chain, prop, values, lensPath, 
       pipe, pick, reject, propEq, map, assoc, over} = R

const getOutgoing = compose (chain (prop('outgoingNodes')), values)
const htmlLens = lensPath (['content', 'html'])
const addName = (tableItems) => ({tableItemId}) => (html) => 
  html + ` <!-- ${tableItems [tableItemId] ?.name} -->`

const convert = (tableItemNodes, tableItems, startingPointId) => pipe (
  pick (getOutgoing (tableItemNodes)),
  reject (propEq ('tableItemId', startingPointId)),
  map (assoc ('externalStartingPoint', true)),
  map (chain (over (htmlLens), addName (tableItems)))
)

const startingPointId = 395;
const tableItemNodes = {818: {id: "818", content: {html: "<p>1</p>"}, outgoingNodes: ["819"], tableItemId: 395}, 819: {id: "819", content: {html: "<p>2</p>"}, outgoingNodes: ["820"], tableItemId: 395}};
const tableItems = {395: {id: "395", name: "SP1", code: "SP1"}, 396: {id: "396", name: "SP2", code: "SP2"}}
const allNodes = {818: {id: "818", content: {html: "<p>1</p>"}, outgoingNodes: ["819"], tableItemId: 395}, 819: {id: "819", content: {html: "<p>2</p>"}, outgoingNodes: ["820"], tableItemId: 395}, 820: {id: "820", content: {html: "<p>3</p>"}, outgoingNodes: ["821"], tableItemId: 396}, 821: {id: "821", content: {html: "<p>4</p>"}, tableItemId: 396}}

console .log (
  convert (tableItemNodes, tableItems, startingPointId) (allNodes)
)
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>

除了以下版本的大部分评论仍然适用外,我们还应注意 chain,当应用于函数时如下所示:

chain (f, g) (x) //~> f (g (x)) (x)

所以chain (over (htmlLens), addName (tableItems))

最终变成了

(node) => over (htmlLens) (addName (tableItems) (node)) (node)

在 Ramda 中相当于

(node) => over (htmlLens, addName (tableItems) (node), node)

然后我们将其映射到到达它的节点上。 (您也可以在 the Ramda REPL 中看到它。)

原答案

通过管道编织额外的参数并非易事,因为管道的设计目的很简单,即向下传递单个参数,并在每一步转换它。当然,我们可以找到一些技术,但我希望它们不值得付出努力。因为他们给我们带来的唯一好处就是能够无障碍地编写我们的代码。无积分本身不应该是一个目标。当它使您的代码更简单和更具可读性时使用它;没有的时候跳过它。

相反,我会用一些辅助函数将其分开,然后编写一个主函数,该函数接受我们的参数并根据需要将它们传递给主管道内的辅助函数。展开此代码段以查看一种方法:

const {compose, chain, prop, values, lensPath, flip, concat, 
       pipe, pick, reject, propEq, map, assoc, over} = R

const getOutgoing = compose (chain (prop ('outgoingNodes')), values)
const htmlLens = lensPath (['content', 'html'])
const addName = flip (concat) ('Table item name goes here')

const convert = (tableItemNodes, startingPointId) => pipe (
  pick (getOutgoing (tableItemNodes)),
  reject (propEq ('tableItemId', startingPointId)),
  map (assoc ('externalStartingPoint', true)),
  map (over (htmlLens, addName))
)

const startingPointId = 395;
const tableItemNodes = {818: {id: "818", content: {html: "<p>1</p>"}, outgoingNodes: ["819"], tableItemId: 395}, 819: {id: "819", content: {html: "<p>2</p>"}, outgoingNodes: ["820"], tableItemId: 395}};
const allNodes = {818: {id: "818", content: {html: "<p>1</p>"}, outgoingNodes: ["819"], tableItemId: 395}, 819: {id: "819", content: {html: "<p>2</p>"}, outgoingNodes: ["820"], tableItemId: 395}, 820: {id: "820", content: {html: "<p>3</p>"}, outgoingNodes: ["821"], tableItemId: 396}, 821: {id: "821", content: {html: "<p>4</p>"}, tableItemId: 396}}

console .log (
  convert (tableItemNodes, startingPointId) (allNodes)
)
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>

(您也可以在 Ramda REPL 上看到这个。)

注意事项

  • 我发现 compose (chain (prop ('outgoingNodes')), values)pipe (values, pluck('outgoingNodes'), flatten) 稍微简单一些,但它们的工作原理相似。

  • 我经常将镜头定义分开,即使我只打算使用它们一次以使调用站点更清晰。

  • 可能没有充分的理由在 addName 中使用 Ramda。这同样有效: const addName = (s) => s + 'Table item name goes here' 并且更干净。我只是想显示 flip 作为使用占位符的替代方法。

  • 有论证要更换

      map (assoc ('externalStartingPoint', true)),
      map (over (htmlLens, addName))
    

      map (pipe (
        assoc ('externalStartingPoint', true),
        over (htmlLens, addName)
      ))
    

    和原来一样。 Functor composition law 表示它们具有相同的结果。这需要对数据进行更少的迭代。但是它给代码增加了一些复杂性,除非性能测试指出这是一个问题,否则我不会费心去处理它。

在看到您的回答之前,我设法做了如下示例中的操作:

 return R.pipe(
   R.pick(allOutgoingNodes),
   R.reject(R.propEq('tableItemId', startingPointId)),
   R.map((node: Node) => {
     const startingPointName = allTableItems[node.tableItemId].name;
     return R.compose(
       R.assoc('externalStartingPoint', true),
       R.over(
         R.lensPath(['content', 'html']),
         R.concat(
           R.__,
           `<p class='test'>See node in ${startingPointName}</p>`
         )
       )
     )(node);
   }),
   R.merge(newNodesObject)
 )(allNodes);

你怎么看?