2.2.5 计算 (Computation by calculation)

现实世界的函数编程:有 F# 和 C# 示例 1-02-02-5

2.2.5 计算 (Computation by calculation)

前两节中讨论的方法给我们新的思考有关程序的执行方式。要理解如何命令式程序执行,就必须理解它的状态是如何变化的。在面向对象的命令式语言编写程序中,状态不仅是所有对象的内部状态,而且是当前正在执行的语句(在每个线程),以及在每个堆栈帧中的所有的本地变量的状态。当前正在执行的语句是状态的一部分,这一事实是重要的,因为它使跟踪状态困难,当你在纸上编写程序执行时。

在函数编程中,我们可以使用一种做法,叫 computation by calculation“计算所调用的计算方法”(Computation,似乎强调计算的结果,calculation 强调的是计算的方法,以下就简称计算)。这种做法对于 Haskell 是特别重要的(请参阅边栏“Haskell 中的数学纯"),Haskell 在《The Haskell School of Expression》[Hudak, 2000] 有更详细地讲述。使用计算,我们从原始的表达表开始(例如,函数调用),并执行一步(比如,用函数体替换调用,计算原生的数学运算结果)。多次重复这样的步骤,我们可以轻松地分析出程序是如何计算的。

如果我们想要理解函数在边界情况下的行为 ,这项技术是特别有用的。在清单 2.2 中,我们使用它来分析 SumNumber 的行为,当它取与上、下边界相同的数的情况下。

Listing 2.2 Functional evaluation of an expression SumNumbers(5,5)

首先,计算调用 SumNumbers(5, 5):

SumNumbers(5, 5)

用这个函数体去展开这个函数调用,用指定值作为参数,替换函数中所有的参数(from = 5, to = 5):

(5 > 5) ? 0 : {
var sumRest = SumNumbers(5 + 1, 5);
5 + sumRest; };

化简条件运算式。首先,计算条件(5 > 5),然后,继续计算为假的分支:

var sumRest = SumNumbers(5 + 1, 5);
5 + sumRest;

计算赋给变量 sumRest 的值,我们计算函数调用参数的值,并展开 SumNumbers(6, 5):

var sumRest =
return (6 > 5) ? 0 : {
var sumRest = SumNumbers(6 + 1, 5);
6 + sumRest; };
5 + sumRest

继续计算 sumRest 的值。计算条件 (6 > 5),用那个分支的子表达式替换初始表达式:

var sumRest = 0
5 + sumRest

计算了这个变量的值以后,就用变量的的实际值去替换在这个表达式中所有余下的位置:

5 + 0

计算这个原生运行符(+)的调用:

5

正如你所看到的,这种写下计算函数代码方式是容易的。即使函数程序员不花他们的生活如何写其程序执行,它是有用的计算,这种习惯的,因为它给了我们强大的思考函数代码的方式。

当然,因为这个示例很简单,我们没有讨论很多重要细节。请放心,我们会在下一章涉及到所有这些问题。清单 2.2 展示的另一个方面是决定下一步应该计算该表达式哪一部分。在此示例中,我们使用最里面的子表达式,所以我们计算了函数调用的所有参数,或者运算符的使用(有了条件运算符的意外,这会以不同的方式来处理)。这一策略称为,严格或渴望(strict or eager),解决了许多函数语言如何工作,包括 F#,类似于一句一句地执行代码。

Haskell 中的数学纯

Haskell 出现于 1990 年,已在学术界流行。在这一节,你已经看到,在函数语言中,我们使用不可变的数据结构和不可变的值,而不是可变的变量。在 F# 中,这不是严格的正确, 因为你仍可以声明可变的值。这种非严格的的方法对于 .NET 的互操作是特别有用的。大多数的 .NET 库依赖于可变的状态,因为它为命令式的、面向对象语言,如 C# 和 VB.NET 而设计的。

另一方面,Haskell 严格执行数学纯。这意味着在程序执行的顺序上,它可以是很灵活的。在我们前面的示例中,我们提到 F# 首先计算表达式的最内层部分。在 Haskell 中,由于没有副作用,所以计算的顺序不重要。只要我们要重新排序的代码不相互依赖,它不会改变该程序的含义。其结果是,Haskell 使用一种称为延迟计算的技术(lazy evaluation,懒惰计算),直到实际需要时(例如,在控制台上打印),才会计算表达式的结果。

这种改变程序而不改变其含义的能力是很重要的,在 F# 中也有。我们将在第 11 章学习如何用它来重构 F# 程序。你将看到延迟计算也用在 F# 中,可以是一种有价值的优化技术。

在最后的几节中,我们讨论了程序状态,用递归进行计算。我们保证,你能学习如何编写代码中较难的部分,以可重用的方式,这就是我们的下一节的主题

你可能感兴趣的:(F#,C#,职场,休闲,函数编程)