簡體   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