c#函数式编程 Functional Programming in C# [20]

4.6 不同抽象层次的编码

  抽象(在英语中,而不是在OOP中)意味着不同具体事物的具体特征被去除,以便出现一个一般的、共同的特征:一个概念。例如,当你说 "你会看到一排房子 "或 "把你的鸭子排成一排 "时,"排 "的概念去掉了任何使鸭子与房子不同的东西,而只是捕捉它们的空间布局。
  IEnumerable和Option的核心就是这种概念上的抽象:它们内部值的所有具体特征都被抽象掉了;这些类型只包含了枚举值的能力,或者说可能不存在一个值的能力,分别如此。对于大多数通用类型也是如此。
  让我们试着归纳一下,这样你为Option所学到的东西可以帮助你理解在本书后面(以及其他库中)看到的其他结构。

4.6.1 常规值与提升值

  当你处理像IEnumerable< int >或Option< Employee >这样的类型时,你是在一个更高的抽象层次上编码,而不是在处理像int或Employee这样的非通用类型。 让我们把我们所处理的值的世界分为两类:

  • 常规值,例如 T
  • 提升值,如A< T >

  在这里,"提升的 "值意味着对相应的常规类型的抽象。这些抽象是一些结构,使我们能够更好地与底层类型一起工作,并表示对底层类型的操作。更技术性地讲,抽象是给底层类型添加 "效果 "的一种方式。
  让我们看一些例子:

  • Option 增加了可选效果的效果——不是 T,而是 T 的可能性
  • IEnumerable 添加了聚合效果——不是一个或两个 T,而是一个 T 的序列。
  • Func 添加了懒惰效应的效果——不是 T,而是可以评估以获得 T 的计算。
  • Task 增加了异步的效果——不是一个 T,而是一个在某个时候你会得到一个 T 的承诺。

  正如你从前面的例子中所看到的,性质非常不同的事物可以被认为是抽象的,所以试图把概念装进一个盒子里没有什么意义。更有趣的是看这些抽象概念如何被运用到工作中。
  再回到常规值与提升值,你可以如图4.4所示,将这些不同类型的值可视化。这张图显示了一个常规类型的例子,int,和一些示例值,以及相应的抽象A< int >,其中A可以是任意的抽象值。将一个普通的值包在一个相应的A中的箭头代表返回函数。

在这里插入图片描述

4.6.2 跨越抽象层次

  现在我们有了这种类型的分类,我们可以对函数进行相应的分类。如图4.5所示,我们有保持在同一抽象层次内的函数和跨越不同抽象层次的函数。
在这里插入图片描述
  让我们看几个例子。函数(int i ) => i.ToString()的签名是int -> string,所以它将一个常规类型映射到另一个常规类型,显然属于第一种。
  我们一直在使用的Int.Parse函数的类型是string -> Option< int >,所以它是一个向上交叉的函数–第三种类型。Scott Wlaschin称这些函数为世界交叉函数,因为它们从常规值T的世界到提升值 E< T >的世界。
  Return函数(对于任何抽象的A来说,它的类型是T -> A< T >),是一个向上交叉函数的特例,它除了向上交叉之外什么都不做;这就是为什么我把Return显示为一个垂直的向上箭头,而把任何其他向上交叉函数显示为一个对角线箭头。
  第二类的函数仍然在抽象范围内。因此,举例来说,下面的函数是一个明显的匹配:

(IEnumerable<int> ints) => ints.OrderBy(i => i)

  它的签名形式是A < T > -> A < R >。但是,我们也应该把任何我们从A< T >开始,有一些额外的参数,并最终得到A < R >的函数纳入这个类别。也就是说,任何函数,它的应用使我们保持在抽象中;它的签名将是(A< T >, …) -> A< R >的形式。 这包括我们已经看过的许多HOF,如Map、Bind、Where、OrderBy,以及其他。
  最后,下行函数–我们从一个提升值开始,以一个常规值结束–包括,对于IEnumerable,Average, Sum, Count;对于Option,Match。
  请注意,给定一个抽象的A,并不总是能够定义一个与Return相对应的向下的东西;也就是说,往往没有垂直的向下的箭头。你总是可以把一个int移到一个Option< int >中,但是你不能把一个Option< int >还原成一个int–如果它是None呢?同样地,你可以把一个Employee包成一个IEnumerable< Employee >,但是没有明显的方法可以把一个IEnumerable< Employee >还原成一个Employee。

4.6.3 重新审视Map与Bind的关系

  让我们看看我们如何使用这个新的分类来更好地理解Map和Bind之间的区别。
在这里插入图片描述
  Map接收一个类型为A< T >的提升值a和一个类型为T -> R的常规函数f,并返回一个类型为A< R >的提升值(通过将f应用于a的内容并将结果提升为A来计算)。这在图4.6中有所说明。
  Bind也接受一个A< T >类型的提升值a和一个T -> A< R >类型的向上交叉函数f,并返回一个A< R >类型的提升值(通过将f应用于a的内容计算)。见图4.7。
在这里插入图片描述
  如果你将Map与T -> A< R >类型的向上交叉函数一起使用,你最终会得到一个A< A< R > >类型的嵌套值。 这通常不是我们想要的效果,你应该用Bind来代替。

4.6.4 在正确的抽象级别上工作

  这种在不同的抽象层次上工作的想法是很重要的。如果你总是处理常规值,你可能会被困在低级别的操作中,比如for 循环、null检查等等。在如此低的抽象层次上工作是低效和容易出错的。
  在一个抽象概念中工作时,有一个明确的甜蜜点,如下面的片段(来自第一章):

Enumerable.Range(1, 100).
	Where(i => i % 20 == 0).
	OrderBy(i => -i).
	Select(i => $"{i}%")
// => ["100%", "80%", "60%", "40%", "20%"]

  一旦你使用Range从常规值到IEnumerable< int >,所有下面的计算都会留在IEnumerable抽象中。也就是说,停留在一个抽象中使你有能力很好地组合几个操作–这一点我们将在下一章深入探讨。
  还有一个危险就是走得太深,你要处理的是A<B<C<D< T >>>>,每一层都增加了一个抽象,很难处理深埋的T。这一点我将在第13章中解决。
  在这一章中,你已经看到了一些用于处理 Option 和 IEnumerable 的核心函数的实现。虽然这些实现很简单,但是你已经看到了这是如何为我们提供了一个丰富的API来处理Option,就像你习惯于处理IEnumerable一样。 可以为 Option 和 IEnumerable 定义几种常见的操作–适用于不同种类的结构的模式。 有了这个API,并对FP的核心功能有了更好的理解,你就可以应对更复杂的场景了。

总结

  • 像Option和IEnumerable这样的结构可以被看作是容器或抽象,允许你更有效地处理T类型的基础值。
  • 你可以区分常规值,例如T,和提升值,像Option或IEnumerable。
  • FP的一些核心功能使你能够有效地工作,提升值:
    • Return 是一个函数,它接受一个常规值并将其提升为一个提升值。
    • Map 将函数应用于结构的内部值,并返回包装结果的新结构。
    • ForEach 是 Map 的一个副作用变体,它采取一个动作,它为容器的每个内部值执行。
    • Bind 将一个返回Option的函数映射到一个Option上,并对结果进行平铺,以避免产生一个嵌套的Option–对于IEnumerable和其他结构也是如此。
    • where 根据给定的谓词过滤结构的内部值。
  • 定义了 Map 的类型称为functors。定义了 Return 和 Bind 的类型称为 monad。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值