Javascript 中带有递归的高阶函数

NCL82409

这里的新手...我试图掌握 Javascript 中函数式编程的概念,但我被卡住了。

我正在尝试通过递归(高阶函数)将一个函数应用于另一个函数。假设我有一个可以是变量或数组的输入,例如:

const A = [5, 14, 23, 32, 41];
const B = 50;

我的基本函数应该将华氏度转换为摄氏度(但它实际上可以是任何函数)

const convertF2C = x => (x - 32) / 1.8;

所以我通常解决的方法是:

const result = array => array.map ? array.map(result) : convertF2C(array); // using recursion if the input is an array

上面的问题是,如果我想更改“result”函数中的convertF2C,则必须修改代码

因此,从功能上考虑,我应该能够创建一个具有基本功能的通用功能,如下所示:

const arrayResult = apply2Array(convertF2C);

console.log(arrayResult(A)); // Output: [-15, -10, -5, 0, 5]
console.log(arrayResult(B)); // Output: 10

我猜测通用函数“apply2Array”应该看起来像:

const apply2Array = fn => (...args) => args.map ? args.map(apply2Array) : fn(...args); // does not work

我在这里找到了“某种”类似的问题,但对我没有帮助:递归函数的高阶函数?

任何指导、帮助或将我指向正确的方向将不胜感激。

斯科特·索耶

我对这里的答案有些困惑。我不知道他们是否在响应我实际上没有看到的要求,或者我是否遗漏了一些重要的东西。

但是,如果您只想要一个装饰器,可以将标量上的函数转换为对标量或标量数组进行操作的装饰器,那么这很简单,而且离您不远了。这应该这样做:

const apply2Array = (fn) => (arg) => 
  Array .isArray (arg) ? arg .map (fn) : fn (arg)

const convertF2C = (t) => (t - 32) / 1.8

const A = [5, 14, 23, 32, 41]
const B = 50

const arrayResult = apply2Array(convertF2C);

console .log (arrayResult (A))
console .log (arrayResult (B))
.as-console-wrapper {max-height: 100% !important; top: 0}

我建议您应该Array.isArray用于检查而不是map属性的存在命名的属性map可能不是Array.prototype.map,可能与制图有关。

其他评论和答案表明您也希望在嵌套数组上使用相同的方法,将类似的内容[5, [[14, 23], 32], 41]转换为[-15, [[-10, -5], 0], 5]. 那不会更难。正如 Bergi 建议的那样,您需要做的就是将递归应用的函数包装在同一个装饰器中:

const apply2Array = (fn) => (arg) => 
  Array .isArray (arg) ? arg .map (apply2Array (fn)) : fn (arg)
  //                               ^^^^^^^^^^^
const convertF2C = (t) => (t - 32) / 1.8

const A = [5, 14, 23, 32, 41]
const B = 50
const C = [5, [[14, 23], 32], 41]

const arrayResult = apply2Array(convertF2C);

console .log (arrayResult (A))
console .log (arrayResult (B))
console .log (arrayResult (C))
.as-console-wrapper {max-height: 100% !important; top: 0}

不要这样做

尽管如此,我还是建议这个企业如果充满潜在的陷阱。想象一下,例如,您有一个sum对数字数组进行操作函数,并且您想用它来对数字数组或数字数组进行操作。

如果您使用 的任一版本将apply2Array包装起来,它将无法正常工作。在第一个版本中,如果您提供一组数字数组,该函数将按预期工作,但如果您仅提供一组数字,则会失败。无论哪种方式,第二个都会失败。

问题是有时您的基本函数想要对数组进行操作。创建一个基于其输入类型执行多项操作的函数会失去一些简单性。

相反,我建议您创建多个函数来完成您需要的不同事情。您仍然可以使用装饰器,但比上述更通用。

这里我们使用一个叫做map,它具体化Array.prototype.map

const map = (fn) => (xs) => 
  xs .map (x => fn (x))

const convertF2C = (t) => (t - 32) / 1.8
const convertAllF2C = map (convertF2C)

const A = [5, 14, 23, 32, 41]
const B = 50

console .log (convertAllF2C (A))
console .log (convertF2C (B))
.as-console-wrapper {max-height: 100% !important; top: 0}

如果您还想要深度映射,您可以重命名上面的装饰器,然后执行以下操作:

const map = (fn) => (xs) => 
  xs .map (x => fn(x))
const deepMap = (fn) => (arg) => 
  Array .isArray (arg) ? arg .map (deepMap (fn)) : fn (arg)

const convertF2C = (t) => (t - 32) / 1.8
const convertAllF2C = map (convertF2C)
const deepConvertF2C = deepMap (convertF2C)

const A = [5, 14, 23, 32, 41]
const B = 50
const C = [5, [[14, 23], 32], 41]

const arrayResult = deepMap (convertF2C);

console .log (convertAllF2C (A))
console .log (convertF2C (B))
console .log (deepConvertF2C (C))
.as-console-wrapper {max-height: 100% !important; top: 0}

拥有三个单独的函数来调用您的三种情况通常比一个函数更简单,该函数可以使用三种不同风格的输入和三种不同的输出风格来调用。由于这些是从我们的基本函数构建的,只有一些通用装饰器,因此它们仍然易于维护。

但这不矛盾吗……?

有些人知道我是Ramda的创始人和主要作者而 Ramda 有一个map与此相关功能。但它似乎可以操作多种类型,包括数组、对象、函数等等。这不是矛盾吗?

我会说不。我们只需要向上移动一个抽象层。FantasyLand指定了一个抽象泛型类型,Functor(借用抽象数学)。这些类型以某种方式包含另一个类型的一个或多个值,我们可以通过mapping 提供给每个值的函数来创建类似结构的容器您的map函数必须遵守某些简单的定律才能将其视为 Functor,但如果您这样做了,那么 Ramdamap将在您的类型中正常工作。换句话说,Ramdamap并不专门用于数组,而是用于任何 Functor。Ramda 本身提供数组、对象和函数的实现,但将调用其他类型委托给它们自己的map方法。

不过,基本点是 Ramda 并没有真正在这里强加额外的复杂性,因为 Ramda 的输入类型mapFunctor而不是Array.

简单

函数式编程涉及很多方面。但中心主题之一必须是简单性。如果您还没有看过 Rich Hickey 的演讲Simple Made Easy,我强烈推荐它。它解释了一个客观的简单概念,并描述了如何实现它。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章