F#探险之旅(五):透过F#理解函数式编程(中)

简介:

F#系列随笔索引

列表(List)是函数式编程(FP)的基础。事实上,FP的重要代表Lisp的名字即源自“List Processing”,它的发明者John McCarthy于1960年发表的论文向我们展示了,在只给定几个简单的操作符和一个表示函数的记号的基础上,如何构造出一个完整的编程语言,他的主要思想之一是用一种简单的数据结构列表来表示代码和数据。

链表(Linked list)是Lisp的主要数据结构之一,并且Lisp的源代码本身也由列表构成。F#中的列表类型表示为链表,它与C#中的数组、泛型List<T>类型有着明显的不同。链表可以用下面的图表示:

首先我们来看一下FP中列表的基本操作(其中的代码都由F#实现)。

列表的基本操作

cons:它是“construct”的缩写,用于构造列表,意即将一个元素添加到列表的开头。我们先约定空表表示为[],在此基础上再约定操作符“::”表示cons操作,这样我们就可以构造任意的列表了。如:

F# Code - 列表的cons操作
let emptyList = [] // []
let oneItem = 3 :: [] // [3]
let twoItems = 2 :: oneItem // [2; 3]
let threeItems = 1 :: twoItems // [1; 2; 3]


可以看到这里是如何通过“cons”操作来一步一步构造列表的。

car:它表示“Contents of the Address part of the Register”,意即列表的第一个元素。F#中使用List模块的hd(Head)函数来执行car操作:

F# Code - 列表的car操作
let stringList = ["No "; "one "; "really "; "listens to "; "anyone else."]
List.hd stringList
// "No "


cdr:它表示“Contents of the Decrement part of the Register”,意即列表中第一个元素之外的元素。F#中使用List模块的tl(Tail)函数来执行cdr操作:

F# Code - 列表的cdr操作
let stringList = ["No "; "one "; "really "; "listens to "; "anyone else."]
List.tl stringList
// ["one "; "really "; "listens to "; "anyone else."]


有了这三种基本操作,其它的操作都可以推导出来了。比如:

concat:该操作用于连接两个列表。在F#用“@”操作符执行该操作。

F# Code
let list1 = [2; 3; 4]
let list2 = [5; 6; 7]
let largeList = list1 @ list2
print_any largeList
// [2; 3; 4; 5; 6; 7]


length:该检查列表的元素数量,在F#中使用List模块的length函数:

F# Code
let list1 = [2; 3; 4]
List.length list1
// 3


nth:该操作返回列表的第n个元素,在F#中使用List模块的nth函数:

F# Code
let list1 = [2; 3; 4]
List.nth list1
2 // 4


这里代码用来获取list1中的索引(基于0)为2的元素,返回4。

现在再来看看List模块还有哪些重要的函数:

List模块(Microsoft.FSharp.Collections.List)的函数 

List.rev:很明显,它可以翻转一个列表。要注意的是该函数会创建整个列表的一个副本,所以要注意性能问题。

List.zip:该函数的签名为a’ list -> b’ list -> (a’ * b’) list,将两个列表打包为一个元组的列表:

F# Code
print_any(List.zip [1; 2] ["one"; "two"]) // [(1, "one"); (2, "two")]


List.exists:该函数的签名类型为(a’ -> bool) -> a’ list -> ‘a,顾名思义,它用于检查列表是否包含了满足指定谓词函数的元素。

List.find:该函数的签名类型为(a’ -> bool) -> a’ list -> ‘a,可以看到它接受两个参数,第一个参数是谓词函数,第二个参数及传入的列表。可以这么理解,find函数对列表的元素逐一检查,看是否满足上面所说的谓词函数,如果找到了,返回该元素的值,否则抛出异常。

F# Code
let result = List.find (fun i -> i * i = 64) [1..10]
print_int result
// 8


这里检查[1..10]中的每个数字,返回8。但如果找不到任何元素满足的话,会抛出KeyNotFoundException,这时可以使用tryfind,这个类似于C#中TryParse方法。

List.filter:该函数接受的参数与find函数类似,不过它的功能是对列表的元素进行过滤,将所有满足谓词函数的元素构造为一个列表返回:

F# Code
let list3 = List.filter (fun i -> i % 2 = 0) [1..20]
print_any list3
// [2; 4; 6; 8; 10; 12; 14; 16; 18; 20]


另外,还有功能强大的聚合函数(Aggregate Operators),即iter、map和fold。(事实上,F#中的Set、Seq、Option和Array模块都支持这三种操作)

List.iter:该函数将枚举列表中的每个元素,并将每个元素应用于指定的函数,如:

F# Code
List.iter (fun i -> printfn "List contains %d" i) [1..5]


输出结果为:

复制代码
F# Code
List contains 1
List contains
2
List contains
3
List contains
4
List contains
5
复制代码


List.map:map函数用将列表转换为另一个列表。它的签名类型为:

Type Infomation
(‘a –> ‘b) –> ‘a list –> ‘b list


看看这个效果图就容易理解了,对第一个列表的元素逐一应用函数,从而得到一个新的列表:

F# Code
let x = List.map (fun i -> i * (-1)) [1..5]
printfn
"%A" x // [-1; -2; -3; -4; -5]


List.fold:在这三个函数中,fold最为强大,不过也最为复杂。它的功能可以理解为:假定我们有三个值,初始值baseValue,函数fun,列表list,逐一访问list中的每个元素,对其应用函数fun,将fun的执行结果累加到baseValue,fold将baseValue的最终值返回。在逐一访问列表时,可以采用从左到右或从右向左的方式,所以fold函数有两个实现:fold_left和fold_right。

F# Code
let accumulate acc x = acc + x
let totalList = List.fold_left accumulate 0 [1..100]
printfn
"1+2+..+100 = %d" totalList // 5050


这里baseValue是0,函数是accumulate,列表是[1..100],最终结果为5050。

列表与模式匹配和递归的结合 

初学列表时,容易像C#中的集合类型那样去看待它。最近学习了一下Haskell,为它的纯粹和优雅所折服,其中的列表部分大量使用了模式匹配和递归,这个过程也让我重新理解了列表。相比于F#的List模块,Haskell提供了额外的列表操作函数,这里我想通过在F#中实现这些函数来看看如何结合使用列表与模式匹配和递归。

take:接受两个参数,一个数字,一个列表,用于从列表开头获取指定个数的元素组成的新列表:

复制代码
F# Code
let rec take (count: int) (l: 'a list) =
match l with
| _ when count <= 0 -> []
| [] -> []
| x :: xs -> x :: take (count - 1) xs

let list1 = [1; 2; 3; 4; 5]
print_any(take
0 list1) // []
print_any(take 1 list1) // [1]
print_any(take 3 list1) // [1; 2; 3]
复制代码


这里同时使用了递归和模式匹配,如果count小于等于0,返回空列表;否则返回从开头计数的指定个数的元素。

drop:该函数也接受两个参数,从列表开头移除指定个数的元素,将剩下的元素组成的列表返回:

复制代码
F# Code
let rec drop (count: int) (l: 'a list) =
match l with
| _ when count <= 0 -> l
| [] -> []
| head :: tail -> drop (count - 1) tail

let list1 = [1; 2; 3; 4; 5]
print_any(drop
0 list1) // [1; 2; 3; 4; 5]
print_any(drop 1 list1) // [2; 3; 4; 5]
print_any(drop 5 list1) // []
复制代码


如果count小于等于0,返回原列表;否则移除指定个数的元素。这里使用了headtail,这样代码的可读性会更好。

通过take和drop函数,我们可以看到,首先得把列表理解为链表,然后在此基础上应用递归和模式匹配,就可以完成很多复杂的操作

小结 

本文介绍了函数式编程(FP)中的列表操作。首先是函数式编程中列表的三种基本操作,在此基础上我们可以推导出其它的各种操作;随后介绍了F#中List模块中的重要函数;最后通过两个自定义函数来展示如何结合使用列表、递归和模式匹配。顺便提一句,强烈建议你学习一下Haskell来了解FP的基本思想,在F#中很容易就能使用命令式编程的方式编写程序,这种灵活性往往使人偏离FP,尤其是在初学FP时。这就像我们学习英语的过程,想象一下,如果把你空投到美国(或其它英语国家),你的英语的进步是不是会快得多?

F#系列随笔索引

参考
Emacs Lisp基础函数
Lisp简介
Mastering F# Lists
Lisp Programming Language
Learn You a Haskell


本文转自一个程序员的自省博客园博客,原文链接:http://www.cnblogs.com/anderslly/archive/2008/11/03/fsharp-adventure-fp-list-processing.html,如需转载请自行联系原作者。

目录
相关文章
|
2月前
|
程序员 Python
探索编程之旅:从代码到思考的蜕变
【8月更文挑战第33天】在数字世界的迷宫中,编程不仅仅是一种技能,它更是一扇打开思维新境界的大门。本篇文章将带你走进编程的世界,体验从简单代码到复杂逻辑的思考过程,以及这一旅程如何深刻影响我们的思维方式和解决问题的策略。通过具体案例和个人感悟,我们将一起探索编程背后的哲学和生活的启示。
|
15天前
|
人工智能 搜索推荐 算法
编程之舞:从代码到艺术的蜕变
【10月更文挑战第30天】在数字世界的无限舞台上,编程不仅仅是冰冷的逻辑和枯燥的算法。它是创造者手中的画笔,是构建梦想的乐章。本文将带你领略编程背后蕴含的艺术之美,探索如何通过代码示例将技术与创造力结合,从而让程序设计成为一种独特的艺术表达。
27 2
|
1月前
|
自然语言处理 算法 语音技术
探索编程世界的奇妙之旅:从初学者到实践者的蜕变
【10月更文挑战第14天】探索编程世界的奇妙之旅:从初学者到实践者的蜕变
19 0
|
2月前
|
设计模式 算法 数据库连接
代码中汲取智慧,编程之旅与技术
在软件开发中,设计模式提供了解决常见问题的标准化方案,有助于提升代码质量和系统灵活性。本文详细介绍了PHP中的三种设计模式:单例模式、工厂模式和策略模式。单例模式确保类仅有一个实例并提供全局访问点,适用于管理共享资源;工厂模式封装了对象创建过程,降低系统耦合度;策略模式定义了一系列可互换的算法,便于算法切换。通过具体示例展示了每种模式的实现方法及其应用场景,帮助PHP开发者更好地理解和应用这些模式。
30 7
|
2月前
|
存储 容器
从代码中感悟生活:编程与人生哲学的交融
【9月更文挑战第27天】在键盘敲击的节奏中,隐藏着生活的哲理。代码不仅仅是冷冰冰的命令序列,它反映了我们解决问题的方式,甚至揭示了人生的智慧。本文将通过编程的视角,探讨如何将技术经验转化为对生活的深刻理解,用简单的例子说明复杂概念,让你在轻松阅读中获得启发。
|
2月前
|
前端开发 算法 JavaScript
探索编程之海:我的技术感悟之旅
【9月更文挑战第14天】在编程的浩瀚海洋中,我是一位勇敢的探险者。每一次代码的编写,都是对未知领域的挑战。本文将分享我在技术探索中的心得体会,从初识编程的迷茫到逐渐找到自己的航线,再到不断精进技艺的过程。通过这段旅程,我深刻理解了“你必须成为你希望在世界上看到的改变”这句话的内涵,并将它融入到我的学习和实践中。让我们一起跟随这篇文章,揭开编程世界的神秘面纱,找到属于自己的航道。
44 9
|
6月前
|
算法 程序员
探寻技术之美:代码世界的奇妙旅程
在数字化时代,技术已经渗透到生活的方方面面,而作为程序员,我深深感受到了代码世界的奇妙之处。本文将带领读者一起探寻技术之美,感悟代码背后的精妙之处。
代码之舞:我的编程之旅与技术感悟
在数字世界的无限舞台上,每一行代码都像是精心编排的舞步,共同谱写着技术的交响曲。本文将带领读者穿梭于编程的世界,探索那些隐藏在逻辑严谨与创新自由之间的奥秘。从最初的迷茫到渐渐的熟练,每一次的挑战都是自我提升的机会。文章旨在分享个人的技术成长历程,展现编程之美,并鼓励更多的技术爱好者踏上属于自己的代码之旅。我们将一同见证,如何在细节中追求完美,在复杂中寻找简单,最终在技术的海洋里找到自己的航道。
|
5月前
|
机器学习/深度学习 人工智能 自然语言处理
探索编程世界的奇幻旅程
【6月更文挑战第18天】在数字时代的浪潮中,编程不仅是技术操作的体现,更是一种思维的展现。本文将通过一系列生动的故事和实例,揭示编程背后的逻辑美学与创造力的火花,带领读者走进编程世界的奇幻之旅。