这里的新手...我试图掌握 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(借用抽象数学)。这些类型以某种方式包含另一个类型的一个或多个值,我们可以通过map
ping 提供给每个值的函数来创建类似结构的容器。您的map
函数必须遵守某些简单的定律才能将其视为 Functor,但如果您这样做了,那么 Ramdamap
将在您的类型中正常工作。换句话说,Ramdamap
并不专门用于数组,而是用于任何 Functor。Ramda 本身提供数组、对象和函数的实现,但将调用其他类型委托给它们自己的map
方法。
不过,基本点是 Ramda 并没有真正在这里强加额外的复杂性,因为 Ramda 的输入类型map
是Functor
而不是Array
.
函数式编程涉及很多方面。但中心主题之一必须是简单性。如果您还没有看过 Rich Hickey 的演讲Simple Made Easy,我强烈推荐它。它解释了一个客观的简单概念,并描述了如何实现它。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句