1.4 高阶函数
现在你已经了解了什么是FP,我们也回顾了语言的功能特性 , 现在是时候开始探索一些具体的功能技术了 。我们将从函数作为第一公民的最重要的好处开始:它使你有能力定义高阶函数(HOFs) 。
HOFs是将其他函数作为输入或将一个函数作为输出返回的函数,或者两者都是 。我假定你已经在某种程度上使用过HOFs , 比如用LINQ 。在本书中 , 我们会经常使用HOFs,所以本节应作为复习,并可能介绍一些你可能不太熟悉的HOFs用例 。HOFs很有趣,本节中的大多数例子都可以在REPL中运行 。请确保你在这一过程中尝试一些自己的变化 。
1.4.1 依赖于其他函数的函数
一些 HOF 将其他功能作为参数并调用它们以完成其工作,有点像一家公司可能会将其某些工作分包给另一家公司 。您已经在本章前面看到了一些此类 HOF 的示例:Sort(List 上的实例方法)和 Where( 上的扩展方法) 。
List.Sort,当与委托一起调用时,是一个说:"好吧,我自己排序 , 只要你告诉我如何比较我包含的任何两个元素 。"Sort做排序的工作,但调用者可以决定使用什么逻辑进行比较 。
同样地,Where做的是过滤的工作,调用者决定用什么逻辑来判断一个元素是否应该被包含 。你可以用图形来表示Where的类型,如图1.4所示 。
让我们看一下 Where 的理想化实现
清单1.10 Where:一个典型的HOF,反复应用给定的谓词 。
publicstatic IEnumerable Where (this IEnumerable ts, Func predicate){foreach (T t in ts)if (predicate(t))yield return t;}
Where方法负责排序逻辑 , 调用者提供,这是应该被过滤的标准 。
正如你所看到的 , HOF可以在逻辑不容易分离的情况下帮助实现关注点的分离 。Where和Sort是迭代应用的例子–HOF将对集合中的每个元素重复应用给定的函数 。
一个非常粗略的方法是,你传递的参数是一个函数,其代码最终将在HOF中的循环体中执行–这是你只传递静态数据所做不到的 。一般的方案显示在图1.5中
文章插图
可选执行是HOF的另一个好办法 。当你想只在特定的条件下调用一个给定的函数时,这很有用,如图1.6所示 。
例如,设想有一个方法可以从缓存中查找一个元素 。可以提供一个委托,并在缓存丢失的情况下被调用 。
清单 1.11 可选调用给定函数的 HOF
class Cache < T > where T: class {public T Get(Guid id) => //...public T Get(Guid id, Func < T > onMiss) => Get(id) ?? onMiss();}
中的逻辑可能涉及到一个昂贵的操作,如数据库调用,你不会希望这个操作被不必要地执行 。
前面的例子说明了将一个函数作为输入的HOF(通常被称为回调或延续),并使用它来执行一个任务或计算一个值 。这也许是HOF最常见的模式,它有时被称为控制倒置:HOF的调用者通过提供一个函数来决定做什么,而被调用者通过调用给定的函数来决定何时做 。
让我们看看 HOF 派上用场的其他一些场景 。
1.4.2 适配器功能
有些HOF根本不应用给定的函数 , 而是返回一个新的函数,与作为参数的函数有某种联系 。例如,假设你有一个执行整数除法的函数:
Func divide = (x, y) => x / y;divide(10, 2) // => 5
你想改变参数的顺序,使除数排在前面 。这可以看作是一个更普遍的问题的一个特殊案例:改变参数的顺序 。
你可以写一个通用的HOF,通过调换参数的顺序来修改任何二进制函数 。
文章插图
static Func SwapArgs(this Func f)=> (t2, t1) => f(t1, t2);
从技术上讲 , 更正确的说法是返回一个新的函数,该函数以相反的顺序调用给定的函数的参数 。但从直观的角度看,我发现认为我得到的是原始函数的一个修改版本更容易 。
现在你可以通过应用来修改原来的除法函数:
var divideBy = divide.SwapArgs();divideBy(2, 10) // => 5
在玩这种HOF的时候,我想到了一个有趣的想法,那就是函数并不是固定不变的:如果你不喜欢一个函数的接口,你可以通过另一个函数来调用它,这个函数提供了一个更适合你需要的接口 。这就是为什么我把这些函数称为适配器 。
1.4.3 创建其他函数的函数
有时你写的函数的主要目的是创建其他函数–你可以把它们看作是函数工厂 。下面的例子使用一个来过滤一个数字序列,只保留能被2整除的数字:
var range = Enumerable.Range(1, 20);range.Where(i => i % 2 == 0)// => [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
如果你想要更普遍的东西 , 比如能够过滤被任何数字n整除的数字,会怎么样?你可以定义一个函数 , 接收n并产生一个合适的谓词 , 来评估任何给定的数字是否能被n整除:
Func isMod(int n) => i => i % n == 0;
我们以前没有看过这样的HOF:它接收一些静态数据并返回一个函数 。让我们看看你如何使用它:
using static System.Linq.Enumerable;Range(1, 20).Where(isMod(2)) // => [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]Range(1, 20).Where(isMod(3)) // => [3, 6, 9, 12, 15, 18]
请注意 , 你不仅在通用性方面得到了提高 , 而且在可读性方面也得到了提高 在这个例子中,你使用isMod HOF来产生一个函数 , 然后你把它作为输入送给另一个HOF,Where , 如图1.7所示
【c#函数式编程 Functional Programming in C# [4]】你会在书中看到更多的HOF的用法 。最终你会把它们看成是常规函数,而忘记了它们是高阶函数 。现在让我们来看看它们如何在更接近日常开发的情况下使用 。
- excel条件函数公式怎么用 excel条件函数公式怎么用countif
- 4.16 学习小记--函数的类型及说明
- excel函数公式大全 excel函数公式大全详解
- React函数式组件的性能优化
- c++中min函数怎么用 min函数怎么用
- 积分公式基本公式表 积分公式
- excel比较日期大小的函数 excel如何比较日期大小
- 用java编写在界面中画圆_Java编程实现swing圆形按钮实例代码
- 如何用PS制作精美邀请函 ps邀请函制作教程及素材
- 用衣服搭配类比理解 C++基础 004函数重载