F#奇妙游(6):如何不用for循环以及循环
For循环
在F#中,for循环的语法如下:
for <identifier> = <start> to <end> do
<body>
但是所有函数式编程语言都不鼓励使用for循环,而是使用递归来实现循环。
for循环的一个常见用法是遍历一个数组,例如:
let arr = [|1; 2; 3; 4; 5|]
for i = 0 to arr.Length - 1 do
printfn "%d" arr.[i]
从语义上来看,为什么要用for循环呢?是因为我的很大哦不我的很多……遍历才是for循环的本质。那么,在函数式编程中我们该如何考虑这个问题呢?
函数式编程的核心之一是值,那么集合也应该是一个值。在F#中,数组是一个值,列表也是一个值。那么,我们可以把数组和列表看成是一个值,而不是一个变量。我们应该用函数来直接处理这个值,而不是用for循环来遍历这个值。
F#集合的类型
F#原生提供了几类集合,这几类集合的含义与传统意义上的集合完全相同,例如,数组就是长度固定,得到最后一个元素和长度的时间复杂度为O(1);列表、序列的长度和最后一个元素的时间复杂度为O(n)。诸如此类。一般的算法书中都会有介绍。
- 列表:
[1; 2; 3; 4; 5]
- 数组:
[|1; 2; 3; 4; 5|]
- 序列:
seq {1; 2; 3; 4; 5}
- 集合:
set [1; 2; 3; 4; 5]
- 映射:
map [("a", 1); ("b", 2); ("c", 3)]
按照文档上的原文,这几类的集合的特点如下:
- List: 有序,不可变序列,元素类型相同,采用链表实现
- Array: 固定长度,0-based,元素类型相同,元素可变
- Seq: 惰性序列,元素类型相同
- Set: 无序,不可变集合,元素类型相同,采用二叉树实现
- Map: 不可变,字典,用键值key来索引
前面给出了相关字面量的表示方法。定义相应的值的方法如下:
let list = [1; 2; 3; 4; 5]
let arr = [|1; 2; 3; 4; 5|]
let seq = seq {1; 2; 3; 4; 5}
let set = set [1; 2; 3; 4; 5]
let map = map [("a", 1); ("b", 2); ("c", 3)]
既然是值,那么就应该用函数来作用到集合上面,并得到一个新的值。这是函数式编程的核心思想。
方法与管道
对应每个集合类型,F#提供了一系列函数,这些函数的形式如Type.append
,Type.add
,区别与类和对象的成员函数,这些方法都是各个类型的静态函数。另外,这些函数的首字母都是小写的,这也符合.NET的命名规范。完整的方法列表可以参考MSDN。
这些函数的定义有一个共同的特点,他们的最后一个参数都是集合类型,例如:
let list = [1; 2; 3; 4; 5]
List.add 6 list
let list2 = List.append [6; 7; 8] list
let list3 = List.filter (fun x -> x % 2 = 0) list
let list4 = List.map (fun x -> x * x) list
按照这样的方式定义,结合F#中偏应用、F#的管道操作符|>
,我们可以很方便的对集合进行操作,例如:
let list = [1; 2; 3; 4; 5]
let sum_sqrt = list
|> List.add 6
|> List.append [6; 7; 8]
|> List.filter (fun x -> x % 2 = 0)
|> List.map (fun x -> x * x)
|> List.fold (fun acc, elem -> acc + Math.Sqrt(float elem)) 0.0
|> printfn "%f"
可以看到,什么高阶函数,什么map、reduce、filter,都是信手拈来。这就是函数式编程的魅力,针对值和映射编程,而不针对变量、对象、循环、状态编程。实际上F#也能不那么函数,但是这样就不好玩了。
还有一种循环
在程序设计中,还有一种循环,这种循环比较难以抽象为操作一个集合。就比如事件循环,需要操作的是一个队列,当队列里面没有事件的时候,就需要等待。这种循环在F#中可以用while
来实现,例如:
while true do
let input = Console.ReadLine()
if input = "exit" then
break
else
printfn "%s" input
这种循环怎么利用函数式编程的思想来实现呢?这就需要用到递归了。例如:
let rec loop () =
let input = Console.ReadLine()
if input = "exit" then
()
else
printfn "%s" input
loop ()
loop ()
实际上这个问题我现在还是很困扰,虽然我也知道尾递归能够使得这种循环达到一定程度的效率,但是对于这个概念的函数式理解还是不够深刻。这个问题我会在后面的文章中继续探索。等我探索到了,感觉到了有趣,再写。
总结
- F#中的集合类型有列表、数组、序列、集合、映射
- F#中的集合类型首先是值,应该首选函数式的方式来操作集合
- F#中的集合类型的方法都是静态函数,可以用管道操作符
|>
来操作 - F#中的循环可以用递归来实现,这个我也没想明白