12.2.1 递归的序列表达式

函数式编程中主要的控制流结构是递归。我们已经在很多例子中,写的普通函数就使用过递归,它能够解决命令式编程中的循环问题,而不需依赖可变状态。当我们想写一个简单的递归函数时,要使用 let rec 关键字,这样,就能函数以递归方式调用自身。

用于组合序列的 yield! 结构,也可以在序列表达式中执行递归调用,所以,我们同样可以使用函数编程的方法,生成序列。清单 12.4 生成所有的小于 1 百万的阶乘数,与清单 12.1 的 C# 示例一样。

清单 12.4 使用序列表达式生成序列数 (F# Interactive)

> let rec factorialsUtil(num, factorial)=    [1]

seq { if (factorial < 1000000) then

yield sprintf "%d! = %d" numfactorial    [2]

let num = num + 1

yield! factorialsUtil(num,factorial * num) };;    [3]

val factorialsUtil : int * int ->seq<string>

> let factorials = factorialsUtil(0, 1)    [4]

val factorials : seq<string> =

seq["0! = 1"; "1! = 1"; "2! = 2"; "3! =6"; "4! = 24 ...]

清单 12.4 首先创建一个工具函数,参数为一个数字和它的阶乘[1]。当我们在后面的代码中,想要计算阶乘序列时,就调用这个函数,并给它一个最小的数字,把它的阶乘定义为起始序列[4]。这里是0,因为根据定义,0 的阶乘是 1。

整个函数体就是一个 seq 块,因此,函数将返回一个序列。在序列表达式中,我们首先检查最后的阶乘是否小于 1 百万,如果为否,就终止序列;省略了if 表达式的 else 分支,所以,不会产生任何额外的数字。如果条件为值,首先产生一个结果[2],它表示下一个阶乘被格式化为字符串。接下来,递增这个数,并执行递归调用[3]。这将返回从下一个数字开始的阶乘序列,我们用 yield! 把它与当前的序列组合起来。

注意,因为 C# 没有对应于 yield! 的功能,所以,要将这种方法转换为 C# 是很困难的;我们必须使用foreach 循环,遍历所有元素,而这可能会导致堆栈溢出。即使能运行,由于用到大量的嵌套循环,效率也很低。在 F# 中,针对使用 yield! 尾递归进行了优化,类似于常规函数调用。这样,当序列表达式以 yield! 调用结尾,并且没有后续的代码(就像前面的这个示例),即使我们使用了多个嵌套的 yield! 调用,效率也很高。

这个示例说明,我们可以在序列表达式中,使用标准的函数式模式。我们可以在序列表达式内部使用 if 结构,以函数式风格递归地循环;F# 还可以在序列表达式内部使用可变状态(使用引用单元)和命令式循环,比如 while,但是,我们很多时候不需要它们。相反,for 循环使用相当频繁,在本章后面讨论处理序列时会看到。

数组和列表表达式

到目前为止,我们看到的序列表达式,都是括在大括号内,用 seq 标识符表示的。这种表达式生成了 seq<’a> 类型的延迟序列,对应于标准的 .NET IEnumerable<T> 类型。F# 还提供了简单的方法,用来创建不可变的 F# 列表和 .NET 数组。下面的代码段就显示了这两种集合类型:


> let cities =

[ yield "Oslo"

yield! capitals ]

;;

val cities : string list =

[ "Oslo";"London"; "Prague" ]


> let cities =

[| yield "Barcelona"

yield! capitals |]

;;

val cities : string array =

[| "Barcelona";"London"; "Prague" |]

可以发现,我们也可以把序列表达式的主体括在中括号内,就像通常构造 F# 列表时一样,在中括号后加竖线(|)构造数组。F# 把表达式主体看作普通的序列表达式,分别将结果转换为列表或数组。

当我们使用数组或列表表达式时,整个表达式是提前计算的,因为我们需要填充所有的元素。任何副作用(如打印到控制台)将会立即执行。虽然序列可以是无穷的,但是,数组和列表则不行:计算会耗尽内存。

再看一下清单 12.4,我们生成的阶乘到一个特定的限值为止。如果我们取消这个限制,会发生什么(即,删除 if 条件)呢?在通常的 F# 中,可能会无限循环,但是,在序列表达式中会发生什么呢?答案是,我们创建了一个无穷序列,它是有效的、且重要的函数式结构。

时间: 2024-10-11 15:40:08

12.2.1 递归的序列表达式的相关文章

12.1.3 使用 F# 序列表达式 在 C# 中的迭代器非常方便(comfortable),能够在普通的 C# 方法中写复杂的代码 (实现 IEnumerable&lt;T&gt;/IEnumerator

12.1.3 使用 F# 序列表达式 在 C# 中的迭代器非常方便(comfortable),能够在普通的 C# 方法中写复杂的代码(实现 IEnumerable<T>/IEnumerator<T> 接口的类型).开发人员写的代码使用标准的C# 功能,比如环,唯一的改变只是我们可以使用一种新的语句,来做一些非标准的事情,这个新语句用 yield return 表示(或者 yield break 表示终止序列),非标准的行为返回序列中下一个元素的值.在以后需要访问序列的时候(最后,计

12.1.3.1 写序列表达式

在 C# 中,当我们实现返回 IEnumerable<T>.IEnumerator<T>,或对应的非泛型方法时,可以自动使用迭代器.F# 序列表达式使用 seq 标识符显式标记,而且不必要使用方法体或函数体.正如其名字所暗示的,序列表达式是表达式的不同类型,我们可以在代码中的任意位置使用.清单 12.2 演示了使用此语法,创建简单的序列. 清单 12.2 介绍序列表达式的语法 (F# Interactive) > let nums = seq { let n = 10   

12.3.2.2 使用查询和序列表达式

在 C# 3.0 中,我们可以使用新的查询表达式语法,写有关映射和筛选数据的操作.查询表达式还支持许多其他操作,但我们会只关注映射和筛选,来演示函数技术和 F# 功能. F# 虽然没有专门提供的查询表达式,但是,使用序列表达式,仍可以轻松地写出映射和筛选数据的查询.这是因为序列表达式在 F# 中所有地方都可以使用,而不仅仅是返回序列的函数.清单 12.9 显示了使用 C# 中的查询和 F# 中的序列表达式,实现我们前面的示例. 清单 12.9 在 C# 和 F# 中的筛选和映射序列 C# F#

12.3.3.1 序列表达式中的平面映射

假设我们有一个关于城市的元组列表,每个元组包含城市的名字和它所在的国家,另外,我们还有一个列表,是用户所选的城市.因此,我们可以这样表示样本数据: let cities = [ ("New York","USA"); ("London", "UK"); ("Cambridge","UK"); ("Cambridge", "USA") ] let e

第十二章 序列表达式和可选工作流

第十二章序列表达式和可选工作流 本章介绍 ■生成和处理序列值 ■处理 F# 序列表达式 ■理解单子和 LINQ 表达式 ■实现 F# 计算表达式 在我们开始讨论序列表达式之前,必须知道什么是序列(sequence),这也是数学的F# 术语.序列是有序的列表,可能包含无穷的元素.这一切听上去有点抽象,但也不用担心,我们已经熟悉这种类型了,在.NET 中表达同样概念的是:IEnumerable<T>. 在.NET 框架中有IEnumerable<T> 类型的主要原因,是它提供一种统一的

12.6 实现选项的计算表达式

在 12.4 节,我们用选项值作为示例,介绍了用 LINQ 查询和 F# 计算表达式创建非标准计算的概念,处理选项值的代码,有自定义的值绑定读取实际值,如同标准值.既然我们已经知道如何转换计算表达式,也就知道我们的 Bind 成员会接收值和 lambda 函数.因为我们处理的是选项类型计算表达式,只有当值是 Some(x) 而不是 None 时,我们才打算执行 lambda 表达式:后一种情况,我们可以立即返回 None. 要运行前面的例子,我们需要在 C# 中实现 LINQ 查询运算符,在 F

12 实验二 递归下降语法分析 11/26

一.实验目的: 利用C语言编制递归下降分析程序,并对简单语言进行语法分析. 编制一个递归下降分析程序,实现对词法分析程序所提供的单词序列的语法检查和结构分析. 二.实验原理 每个非终结符都对应一个子程序. 该子程序根据下一个输入符号(SELECT集)来确定按照哪一个产生式进行处理,再根据该产生式的右端: 每遇到一个终结符,则判断当前读入的单词是否与该终结符相匹配,若匹配,再读取下一个单词继续分析:不匹配,则进行出错处理 每遇到一个非终结符,则调用相应的子程序 三.实验要求说明 输入单词串,以“#

递归--逆波兰表达式

用递归解决递归形式的问题例题:逆波兰表达式逆波兰表达式是一种把运算符前置的算术表达式(其实一般教科书上称这种表达式为波兰表达式) ,例如普通的表达式2 + 3的逆波兰表示法为+ 2 3.逆波兰表达式的优点是运算符之间不必有优先级关系,也不必用括号改变运算次序,例如(2 + 3) * 4的逆波兰表示法为* + 2 3 4.本题求解逆波兰表达式的值,其中运算符包括+ - * /四个. 输入:输入为一行,其中运算符和运算数之间都用空格分隔,运算数是浮点数 输出:输出为一行,表达式的值. 简单来说,这种

RS查询报错之递归公用表表达式不包含顶级 UNION ALL运算符

在FM里面涉及模型的时候,修改了物理层的查询SQL如下 select * from TARGET_VISIT_GH where ghksdm in(select dept_id from DIM_BI_DEPT_ROLE where #sq(CAMIDList())# like '%g:cn='+role_id+'"%') or #sq(CAMIDList())# like '%g:cn=admingroup"%' 结果在Ruan Report的时候报错,如下图所示 是解决思路: 1: