繁体   English   中英

基于前一个参数设置属性的 Ramda.js 管道

[英]Ramda.js pipe that sets a property based in a previous parameter

目前,我有以下代码(有效):

 const double = R.multiply(2); const piped = R.pipe( (obj) => R.assoc('b', double(obj.a))(obj), (obj) => R.assoc('c', double(obj.b))(obj) ); console.log( piped({ a: 1 }) );
 <script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>

但是我认为由于每个管道函数末尾的(obj) ,我想我可以将它重构为“Ramda 世界”中更好的东西。

我还是这个库的新手,所以我还不知道所有的方法和技巧。

使用 Ramda 有没有更好的方法来做到这一点?

我的“真实”代码是这样的:

function getScripts() {
  const tryRequire = tryCatch((path) => require(path).run, always(null));

  const addPathAndRunProps = pipe(
    // Note: The `scriptsPath` function is a bound path.join function.
    // It just returns a string.
    (dir) => assoc('path', scriptsPath(dir.name, 'index.js'))(dir),
    (dir) => assoc('run', tryRequire(dir.path))(dir)
  );

  const addModuleRunAndFilterInvalid = pipe(
    map(addPathAndRunProps),
    filter((dir) => typeof dir.run === 'function')
  );

  return addModuleRunAndFilterInvalid(
    fs.readdirSync(SCRIPTS_PATH, { withFileTypes: true })
  );
}

我认为您可能在这里过度使用 Ramda。 代码有点混乱。 这在未来可能更容易阅读,更易于维护,同时仍然有效:

function getScripts() {
  const tryRequire = tryCatch((path) => require(path).run, always(null));

  const addPathAndRunProps = dir => {
    const path = scriptsPath(dir.name, 'index.js')

     return {
        ...dir,
        path,
        run: tryRequire(path),
     }
  }

  return pipe(
     map(addPathAndRunProps),
     filter(x => typeof x.run === 'function'),
  )(fs.readdirSync(SCRIPTS_PATH, { withFileTypes: true }))
}

或者,如果您真的想保留这些 setter,请尝试将addPathAndRunProps函数拆分为两个 setter:

function getScripts() {
  const tryRequire = tryCatch((path) => require(path).run, always(null));

  const addPathProp = x => assoc('path', scriptsPath(x.name, 'index.js'), x)
  const addRunProp = x => assoc('run', tryRequire(x.path), x)

  return pipe(
    map(addPathProp),
    map(addRunProp),
    filter(x => typeof x.run === 'function'),
  )(fs.readdirSync(SCRIPTS_PATH, { withFileTypes: true }))
}

在这两种情况下,我都摆脱了您的addModuleRunAndFilterInvalid函数。 addModuleRunAndFilterInvalid拆分为自己的函数并不会为您的函数增加任何清晰度,并且返回管道的结果阐明了getScripts函数本身的用途。

此外,在您的代码中,您不断调用正在dir操作的对象。 这是令人困惑的,因为它意味着对象在每个函数调用中都具有相同的结构。 然而,传递给addRunProp的变量与传递给addRunProp的变量的结构addPathProp (传递给addRunProp需要一个额外的path道具)。 要么想出一个描述性的名称,要么只使用x 您可以将x视为您的函数正在运行的对象。 要弄清楚x是什么,请查看函数名称(例如addRunProp意味着x将添加一个 run 属性)。

另一个可能有用的提示:我已经确定了aug的命名约定(简称“增强”),用于向对象添加属性或信息位。 所以我会重命名您的addPathProp函数augPath并重命名您的addRunProp函数augRun 由于我一直使用它,我知道当我在函数开头看到aug时,它正在添加一个属性。

我同意 Cully 的回答——在这里尝试使用 Ramda 的函数可能没有任何充分的理由。

但是,如果您有兴趣,可以选择使用一些 Ramda 函数。

chainap是在两种不同的抽象类型上运行的相当通用的函数。 但是当与函数一起使用时,它们作为组合器有一些相当有用的行为:

chain (f, g) (x) //=> f (g (x)) (x)
ap (f, g) (x)    //=> f (x) (g (x))

这意味着您可以像这样编写函数:

const piped = R.pipe(
  chain (assoc ('b'), pipe (prop ('a'), double)),
  chain (assoc ('c'), pipe (prop ('b'), double)),
)

我不认为这个版本对原始版本有改进; 这些内部pipe调用中涉及的重复太复杂了。

但是使用辅助函数,这可能更合理:

const doubleProp = curry (pipe (prop, double))
// or doubleProp = (prop) => (obj) => 2 * obj[prop]

const piped = R.pipe(
  chain (assoc ('b'), doubleProp ('a')),
  chain (assoc ('c'), doubleProp ('b')),
);

现在,在我看来,这是非常易读的代码。 当然,它需要了解chain以及它如何应用于功能,但是我认为它实际上是对原始的改进。

我经常指出,只有当我们的代码更具可读性时,无点代码才是有用的工具。 当它没有指向代码时,它的功能并不比无指向性差。


顺便说一句,我只想指出,我对您问题的质量印象深刻。 阅读经过深思熟虑和精心呈现的问题真的很棒。 谢谢!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM