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

RuntimeMapMaker3D-Pro在这里插入图片描述


1.2 C#是一种怎样的功能性语言?

  事实上,C#通过Delegate类型从最早的语言版本中就支持函数作为第一公民,随后引入的lambda表达式使语法支持更加完善–我们将在下一节中回顾这些语言特性。
  有一些怪癖和限制,比如当涉及到类型推理时;我们将在第8章讨论这些。但总的来说,对函数作为第一公民的支持是相当好的。
  至于支持避免原地更新的编程模型,这方面的基本要求是语言要有垃圾收集。因为你创建了修改过的版本,而不是在原地更新现有的值,所以你希望旧版本能在需要时被垃圾回收。同样,C#也满足了这个要求。
  理想情况下,语言也应该不鼓励原地更新。这是C#最大的缺点:默认情况下,所有东西都是可变的,程序员必须付出大量的努力来实现不变性。字段和变量必须明确地标记为只读,以防止突变。 (与F#相比,变量在默认情况下是不可变的,必须明确标记为可变才能允许变异)。)
  类型方面呢? 框架中有一些不可变的类型,比如string和DateTime,但是语言对用户定义的不可变类型的支持很差(不过,正如你接下来所看到的,它在C# 6中有所改进,在未来的版本中可能会进一步改进)。 最后,框架中的集合是可变的,但有一个可靠的不可变集合库可用。
  综上所述,C#对一些函数式技术有很好的支持,但对其他技术则没有。在其发展过程中,它已经有所改进,并将继续改进其对功能化技术的支持。在本书中,你将了解哪些功能可以利用,以及如何绕过它的缺点。
  接下来我们将回顾一下C#过去、现在和即将推出的版本中与FP特别相关的一些语言特性。

1.2.1 LINQ 的功能特性

  当C# 3与.NET框架的3.5版本一起发布时,它包括了一系列受函数式语言启发的功能,包括LINQ库(System.Linq)和一些新的语言功能,使你可以用LINQ做什么,或增强你的功能,如扩展方法和表达树。
  LINQ确实是一个函数式库–你可能注意到了,我在前面用LINQ来说明FP的两个信条–随着你在本书中的进展,LINQ的函数式本质将变得更加明显。
  LINQ为许多关于列表(或者更一般地说,关于 “序列”,因为IEnumerable的实例在技术上应该被称为)的常见操作提供了实现,其中最常见的是映射、排序和过滤(见 "序列的常见操作 "侧边栏)。下面是一个结合了这三者的例子:

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

  请注意Where、OrderBy和Select都以函数为参数,并且不改变给定的IEnumerable,而是返回一个新的IEnumerable,这说明了你前面看到的FP的两个原则。
  LINQ不仅有利于查询内存中的对象(LINQ to Objects),还有利于查询其他各种数据源,如SQL表和XML数据。 C#程序员已经接受了LINQ作为处理列表和关系数据的标准工具集(占典型代码库的很大一部分)。 从正面看,这意味着你已经对函数式库的API有了一些了解。
  另一方面,在处理其他类型时,C#程序员通常坚持使用流程控制语句来表达程序的预期行为的命令式风格。因此,我所看到的大多数C#代码库都是函数式(在处理IEnumerables和IQueryables时)和命令式(其他一切)的拼凑。
  这意味着,尽管C#程序员意识到使用LINQ这样的函数式库的好处,但他们还没有充分接触到LINQ背后的设计原则,无法在自己的设计中利用这些技术。

序列的常见操作 LINQ库包含许多对序列进行常见操作的方法,比如说:

  • 映射 – 给定一个序列和一个函数,映射产生一个新的序列,其中的元素是通过对给定序列中的每个元素应用给定的函数得到的(在LINQ中,这是用Select方法完成的)。
    Enumerable.Range(1, 3).Select(i => i * 3) // => [3, 6, 9]
  • 过滤 – 给定一个序列和一个谓词,过滤产生一个新的序列,该序列由给定序列中通过谓词的元素组成(在LINQ中为Where)。 Enumerable.Range(1, 10).Where(i => i % 3 == 0) // => [3, 6, 9]
  • 排序 – 给定一个序列和一个键选择器函数,排序产生一个根据键排序的新序列(在LINQ中,OrderBy和OrderByDescending)。
    Enumerable.Range(1, 5).OrderBy(i => -i) // => [5, 4, 3, 2, 1]
1.2.2 C# 6 和 C# 7 中的函数式特性

  C# 6和C# 7并不像C# 3那样具有革命性,但它们包括了许多较小的语言特性,这些特性加在一起,为函数式编码提供了更好的体验和更多的惯用语法。

注意:C# 6和C# 7中引入的大多数特性提供了更好的语法,而不是新的功能。如果你使用的是旧版本的C#,你仍然可以应用本书所展示的所有技术(有一点额外的类型化)。然而,新的功能大大改善了可读性,使函数式的编程风格更有吸引力。

您可以在下面的列表中看到这些功能的运行情况。

清单 1.4 与 FP 相关的 C# 6 和 C# 7 特性

using static System.Math; //使用static可以对System.Math的静态成员进行无条件的访问,比如PI和Pow。
public class Circle {
    public Circle (double radius) => Radius = radius;
    public double Radius { //仅限getter的自动属性只能在构造函数中设置。
        get;
    }
    public double Circumference => PI * 2 * Radius; //表达式实体属性
    public double Area {
        get {
            double Square(double d) => Pow(d, 2);//一个局部函数是在另一个方法中声明的方法
            return PI * Square(Radius);
        }
    }
    //带有命名元素的 C# 7 元组语法
    public (double Circumference, double Area) Stats => (Circumference, Area);
}

using static 导入静态成员
  C# 6 中的 using static 语句允许您导入类的静态成员(在本例中为 System.Math 类)。因此,在此示例中,您无需进一步限定即可调用 Math 的 PI 和 Pow 成员:

using static System.Math; 
public double Circumference => PI * 2 * Radiu;

  为什么这很重要?在 FP 中,我们更喜欢行为仅依赖于它们的输入参数的函数,因为我们可以独立地推理和测试这些函数(与实例方法形成对比,实例方法的实现通常与实例变量交互)。这些函数在 C# 中作为静态方法实现,因此 C# 中的函数库将主要由静态方法组成。
  使用static可以让你更容易地使用这些库,尽管过度使用可能会导致命名空间污染,但合理的使用可以使代码变得干净、可读。

更加容易的不可变类型,只需获得自动操作就可以了
  当你声明一个getter-only的自动属性,如Radius,编译器隐含地声明了一个只读的支持字段。因此,这些属性只能在构造函数或内联中被分配一个值。

public class Circle{
	public Circle(double radius) => Radius = radius;
	public double Radius { get; }
}

  Getter-only 自动属性简化了不可变类型的定义,您将在第 9 章中更详细地了解这一点。 Circle 类演示了这一点:它只有一个字段(Radius 的支持字段),它是只读的,因此一旦创建,一个 Circle 永远不会改变。

更加协调的功能与表达身体的成员的关系

  Circumference 属性是使用由 => 引入的表达式主体声明的,而不是使用 {} 中的通常语句主体:

public double Circumference => PI * 2 * Radius

  请注意,与Area属性相比,这要简洁得多!

  在FP中,我们倾向于写很多简单的函数,其中很多是单行的,然后将它们组合成更复杂的工作流程。以表达式为主体的方法允许你以最小的语法噪音来完成这些工作。当你想写一个返回函数的函数时,这一点尤其明显–这是你在本书中会经常做的事情。
  在C# 6中,方法和属性引入了表达式结构的语法,在C# 7中,它被概括为也适用于构造函数、析构函数、获取器和设置器。

局部函数
  编写大量的简单函数意味着许多函数只能从一个位置调用。C# 7允许你通过在一个方法的范围内声明方法来明确这一点;例如,Square方法被声明在Area getter的范围内。

get{
	double Square(double d) => Pow(d, 2);
	return PI * Square(Radius);
}

更好的语法
  更好的元组语法是 C# 7 最重要的特性。它允许您轻松创建和使用元组,最重要的是,可以为其元素分配有意义的名称。例如,Stats 属性返回一个 (double,double) 类型的元组,并指定可以访问其元素的有意义的名称:

public (double Circumference, double Area) Stats => (Circumference, Area);

  元组在 FP 中很重要,因为它倾向于将任务分解为非常小的函数。您最终可能会得到一种数据类型,其唯一目的是捕获一个函数返回的信息,而它期望作为另一个函数的输入。为此类结构定义专用类型是不切实际的,因为它们与有意义的域抽象不对应。这就是元组的用武之地。

1.2.3 C#的更多功能的未来?

  当我在2016年初写这一章的初稿时,C# 7的开发还处于早期阶段,有趣的是,语言团队确认的所有 "强烈兴趣 "的功能都是通常与函数式语言相关的功能。它们包括以下内容:

  • 记录类型(无样板的不可变类型)
  • 代数数据类型(类型系统的强大补充)
  • 模式匹配(类似于处理数据形状的 switch 语句,例如它的类型,而不仅仅是值)
  • 更好的元组语法

  一方面,令人失望的是,只有最后一个项目可以交付。C# 7还包括一个有限的模式匹配的实现,但它与函数式语言中的模式匹配相去甚远,而且它通常不足以满足我们在函数式编程时使用模式匹配的方式(见10.2.4节)。
  另一方面,这些功能仍在未来版本的讨论中,并且已经在各自的提案上做了工作。这意味着我们可能会在未来的C#版本中看到记录类型和更完整的模式匹配实现。因此,C#已经准备好继续发展成为一种多范式语言,并具有越来越强的功能成分。
  这本书将为您跟上语言和行业的发展提供良好的基础。它还将让您很好地理解该语言未来版本背后的概念和动机。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值