函数式编程:返回调用具有特定参数的不同函数列表的第一个真实结果

Mag*_*ode 6 javascript functional-programming typescript lodash ramda.js

我正在尝试在 TypeScript 中使用函数式编程,并且想知道使用函数库(例如 ramda、remeda 或 lodash-fp)执行以下操作的最惯用方法。我想要实现的是将一堆不同的函数应用于特定数据集并返回第一个真实结果。理想情况下,一旦找到了真实的结果,其余的函数就不会运行,因为列表中后面的一些函数在计算上非常昂贵。这是在常规 ES6 中执行此操作的一种方法:

const firstTruthy = (functions, data) => {
    let result = null
    for (let i = 0; i < functions.length; i++) {
        res = functions[i](data)
        if (res) {
            result = res
            break
        }
    }
    return result
}
const functions = [
  (input) => input % 3 === 0 ? 'multiple of 3' : false,
  (input) => input * 2 === 8 ? 'times 2 equals 8' : false,
  (input) => input + 2 === 10 ? 'two less than 10' : false
]
firstTruthy(functions, 3) // 'multiple of 3'
firstTruthy(functions, 4) // 'times 2 equals 8'
firstTruthy(functions, 8) // 'two less than 10'
firstTruthy(functions, 10) // null
Run Code Online (Sandbox Code Playgroud)

我的意思是,这个函数可以完成这项工作,但是在这些库中的任何一个中是否有一个现成的函数可以实现相同的结果,或者我可以将它们的一些现有函数链接在一起来做到这一点吗?最重要的是,我只是想了解函数式编程,并就解决此问题的惯用方法获得一些建议。

Sco*_*yet 7

虽然 Ramda 的anyPass本质相似,但如果任何函数产生 true,它只会返回一个布尔值。Ramda(免责声明:我是 Ramda 的作者)没有这个确切的功能。如果您认为它属于 Ramda,请随时提出问题或为其创建拉取请求。我们不能保证它会被接受,但我们可以保证一个公平的听证会。

Scott Christopher 展示了可能是最干净的 Ramda 解决方案。

一个还没有提出的建议是一个简单的递归版本,(虽然 Scott ChristopherlazyReduce是某种亲戚。)这是一种技术:

const firstTruthy = ([fn, ...fns], ...args) =>
  fn == undefined 
    ? null
    : fn (...args) || firstTruthy (fns, ...args)

const functions = [
  (input) => input % 3 === 0 ? 'multiple of 3' : false,
  (input) => input * 2 === 8 ? 'times 2 equals 8' : false,
  (input) => input + 2 === 10 ? 'two less than 10' : false
]

console .log (firstTruthy (functions, 3)) // 'multiple of 3'
console .log (firstTruthy (functions, 4)) // 'times 2 equals 8'
console .log (firstTruthy (functions, 8)) // 'two less than 10'
console .log (firstTruthy (functions, 10)) // null
Run Code Online (Sandbox Code Playgroud)

我可能会选择使用 Ramdacurry或像这样手动使用咖喱函数:

const firstTruthy = ([fn, ...fns]) => (...args) =>
  fn == undefined 
    ? null
    : fn (...args) || firstTruthy (fns) (...args)

// ...

const foo = firstTruthy (functions);

[3, 4, 8, 10] .map (foo) //=> ["multiple of 3", "times 2 equals 8", "two less than 10", null]
Run Code Online (Sandbox Code Playgroud)

或者,我可能会使用这个版本:

const firstTruthy = (fns, ...args) => fns.reduce((a, f) => a || f(...args), null)
Run Code Online (Sandbox Code Playgroud)

(或者它的咖喱版本)与 Matt Terski 的答案非常相似,除了这里的函数可以有多个参数。请注意,存在细微差别。在原文和上面的答案中,不匹配的结果是null。如果其他函数都不为真,则这是最后一个函数的结果。我想这是一个小问题,我们总是可以通过|| null在末尾添加一个短语来解决它。


Nin*_*olz 5

您可以Array#some在真实值上使用短路。

const
    firstTruthy = (functions, data) => {
        let result;
        functions.some(fn => result = fn(data));
        return result || null;
    },
    functions = [
        input => input % 3 === 0 ? 'multiple of 3' : false,
        input => input * 2 === 8 ? 'times 2 equals 8' : false,
        input => input + 2 === 10 ? 'two less than 10' : false
    ];

console.log(firstTruthy(functions, 3)); // 'multiple of 3'
console.log(firstTruthy(functions, 4)); // 'times 2 equals 8'
console.log(firstTruthy(functions, 8)); // 'two less than 10'
console.log(firstTruthy(functions, 10)); // null
Run Code Online (Sandbox Code Playgroud)


Sco*_*her 5

Ramda 有一种短路R.reduce(和其他几种)方法,使用该R.reduced函数来指示它应该停止遍历列表。这不仅避免了在列表中应用更多函数,而且还避免了进一步遍历列表本身的短路,如果您正在使用的列表可能很大,这可能很有用。

const firstTruthy = (fns, value) =>
  R.reduce((acc, nextFn) => {
    const nextVal = nextFn(value)
    return nextVal ? R.reduced(nextVal) : acc
  }, null, fns)

const functions = [
  (input) => input % 3 === 0 ? 'multiple of 3' : false,
  (input) => input * 2 === 8 ? 'times 2 equals 8' : false,
  (input) => input + 2 === 10 ? 'two less than 10' : false
]

console.log(
  firstTruthy(functions, 3), // 'multiple of 3'
  firstTruthy(functions, 4), // 'times 2 equals 8'
  firstTruthy(functions, 8), // 'two less than 10'
  firstTruthy(functions, 10) // null
)
Run Code Online (Sandbox Code Playgroud)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.min.js"></script>
Run Code Online (Sandbox Code Playgroud)

另一种选择是创建一个“懒惰”版本reduce,只有当您应用作为累积值传递的函数继续递归遍历列表时,该版本才会继续。这使您可以通过应用评估列表中其余值的函数来控制减少函数内部的短路。

const lazyReduce = (fn, emptyVal, list) =>
  list.length > 0
    ? fn(list[0], () => lazyReduce(fn, emptyVal, list.slice(1)))
    : emptyVal

const firstTruthy = (fns, value) =>
  lazyReduce((nextFn, rest) => nextFn(value) || rest(), null, fns)

const functions = [
  (input) => input % 3 === 0 ? 'multiple of 3' : false,
  (input) => input * 2 === 8 ? 'times 2 equals 8' : false,
  (input) => input + 2 === 10 ? 'two less than 10' : false
]

console.log(
  firstTruthy(functions, 3), // 'multiple of 3'
  firstTruthy(functions, 4), // 'times 2 equals 8'
  firstTruthy(functions, 8), // 'two less than 10'
  firstTruthy(functions, 10) // null
)
Run Code Online (Sandbox Code Playgroud)

  • 很高兴在 JS 中看到正确的右折叠。然而,我会用 JS 中的 head/tail 对象来编码惰性,其中后者是一个 getter。这样你就可以省掉调用方的括号。这不再是折叠,而是迈向流的第一步。 (2认同)