-
Unit类型
uint类型表示没有值。这类似于C语言中的void。在F#代码中使用圆括号来表示:():
> let x = ();;
val x : unit
> ();;
val it : unit = ()
F#中的函数都必须返回一个值,某些函数即使不需要返回任何数据,也必须返回一个unit值。
想返回unit时,使用ignore函数可以吸收掉函数的返回值。由于F#中函数的返回值是最后一个表达式的值,而有时候表达式具有边界效应(side effect),这时可以用它来忽略掉表达式的值:
> let square x = x * x;;
val square : int -> int
> ignore (square 4);;
val it : unit = ()
-
元组类型
元组是数据的有序集合,使用元组可以方便的将数据分组到一起。比如用来保存运算的中间结果等等。
F#中的元组实际底层使用的是.NET库中的System.Tuple<_>类型,但是在实际运用中不能直接使用System.Tuple<_>。
创建元组的方法是,将一组数据顺序列出,并以逗号分隔。可以用圆括号将元组包含起来,但是这是非强制的。元组的类型表示为其中每个元素的类型的列表,类型之间以星号分隔。下面的例子中,dinner是一个元组的实例,该元组的类型为string * string。
> let dinner = ("green eggs", "ham");;
val dinner : string * string = ("green eggs", "ham")
根据上面的解释,dinner的定义使用下面的方式也是等效的:
> let dinner = "green eggs", "ham";;
元组中可以包含任意数量、任意类型的数据,并且可以进行嵌套:
> let zeros = (0, 0L, 0I, 0.0);;
val zeros : int * int64 * bigint * float = (0, 0L, 0I, 0.0)
> let nested = (1, (2.0, 3M), (4L, "5", '6'));;
val nested : int * (float * decimal) * (int64 * string * char) = ...
使用fst和snd函数可以获得元组中第一、二个成员。
使用let绑定可以提取元组中的值:
> let snacks = ("Soda", "Cookies", "Candy");;
val snacks : string * string * string = ("Soda", "Cookies", "Candy")
> let x, y, z = snacks;;
val z : string = "Soda"
val y : string = "Cookies"
val x : string = "Candy"
> y, z;;
val it : string * string = ("Cookies", "Candy")
但是let绑定提取的值的个数必须刚好等于元组的成员个数,否则会产生一个编译错误。
元组可以作为函数参数进行传递。
-
列表
使用元组可以将一组值绑定到单个实体中,使用列表可以将数据以链的形式组织起来。列表的好处是可以使用聚合操作来批量处理数据。
1、最简单的定义列表的方法是,使用中括号将数据括起来,多个数据之间用分号分隔。空列表直接使用中括号[]。
> // Declaring lists
let vowels = ['a'; 'e'; 'i'; 'o'; 'u']
let emptyList = [];;
val vowels : char list = ['a'; 'e'; 'i'; 'o'; 'u']
val emptyList : 'a list = []
2、和其他很多语言不一样,F#中对列表成员的操作和访问进行了严格的限制。对它们只能进行两种操作:
cons操作:使用::操作符,在列表头上添加一个成员。
> // Using the cons operator
let sometimes = 'y' :: vowels;;
val sometimes : char list = ['y'; 'a'; 'e'; 'i'; 'o'; 'u']
添加操作:使用@操作符将两个列表合并为一个。
let odds = [1; 3; 5; 7; 9]
let evens = [2; 4; 6; 8; 10]
val odds : int list = [1; 3; 5; 7; 9]
val evens : int list = [2; 4; 6; 8; 10]
> odds @ evens;;
val it : int list = [1; 3; 5; 7; 9; 2; 4; 6; 8; 10]
3、定义列表时,可以指定成员的值的范围,避免当成员很多的时候需要书写很多东西。范围使用两个点号..来表示。通过范围来定义列表时,成员必须是有序的数字值。
> let x = [1 .. 10];;
val x : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
还可以为范围指定步长,步长可以为正数和负数:
> // List ranges
let tens = [0 .. 10 .. 50]
let countDown = [5L .. −1L .. 0L];;
val tens : int list = [0; 10; 20; 30; 40; 50]
val countDown : int list = [5L; 4L; 3L; 2L; 1L; 0L]
4、定义列表还可以使用列表描述(List comprehensions)方式,即在列表定义中可以插入一段内联的语句,用来自动生成列表成员。使用中括号[ ]来包含这些内联语句,内联语句会顺序执行,里面可以使用判断、循环等分支语句,在内联语句中,使用yield关键字来向列表中添加成员。如:
> // Simple list comprehensions
let numbersNear x =
[
yield x - 1
yield x
yield x + 1
];;
val numbersNear : int -> int list
> numbersNear 3;;
val it : int list = [2; 3; 4]
列表描述方式定义的列表是立即生成在内存中的,不会等到使用时才生成。
内联语句中也可以使用函数。如:
> // More complex list comprehensions
let x =
[ let negate x = -x
for i in 1 .. 10 do
if i % 2 = 0 then
yield negate i
else
yield i ];;
val x : int list = [1; −2; 3; −4; 5; −6; 7; −8; 9; −10]
内联语句中使用for循环时,可以将do yield简写为->。
// Generate the first ten multiples of a number
let multiplesOf x = [ for i in 1 .. 10 do yield x * i ]
// Simplified list comprehension
let multiplesOf2 x = [ for i in 1 .. 10 -> x * i ]
5、内置的列表操作函数
函数和类型
|
说明
|
List.length
'a list -> int
|
返回列表的长度
|
List.head
'a list -> 'a
|
返回列表的第一个成员
|
List.tail
'a list -> 'a list
|
返回该列表除去第一个成员之外的部分。
|
List.exists
('a -> bool) -> 'a list ->bool
|
返回列表中是否有某个成员满足搜索函数的条件。
|
List.rev
'a list -> 'a list
|
将列表中的成员倒序
|
List.tryfind
('a -> bool) -> 'a list -> 'a option
|
返回第一个使给定函数返回true的成员x的Some(x)。否则返回None。
|
List.zip
'a list -> 'b list -> ('a * 'b) list
|
给定两个相同长度的list,返回一个list,该list的成员是参数的两个list对应位置成员的元组。
|
List.filter
('a -> bool) -> 'a list -> 'a list
|
返回一个列表,成员只包含使得给定函数返回true的那些。
|
List.partition
('a -> bool) -> 'a list -> ('a list * 'a list)
|
给定一个函数和一个列表,返回两个列表。第一个列表中成员是给定函数中返回true的部分,第二个是返回false的部分。
|
> // Using List.partition
let isMultipleOf5 x = (x % 5 = 0)
let multOf5, nonMultOf5 =
List.partition isMultipleOf5 [1 .. 15];;
val isMultipleOf5 : int -> bool
val nonMultOf5 : int list = [1; 2; 3; 4; 6; 7; 8; 9; 11; 12; 13; 14]
val multOf5 : int list = [5; 10; 15]
F#的所有列表操作函数实际上是定义在Microsoft.FSharp.Collections命名空间中的。因为这个模块默认被包含了,所以可以直接使用List来访问它们。
-
聚合操作(Aggregate Operators)
初看之下元组和列表似乎没什么不同,都是一组数据的集合。但是列表有一个非常重要而且非常舒服的特性,就是可以进行聚合操作。类似CPU中的SIMD指令,可以用单个操作符批量操作列表中的所有数据。而在我目前学习过的所有语言中,只有F#具有这样的特性!想象一下,我们可以少写多少循环了!
-
List.map
List.map接受一个函数和一个列表作为参数,返回值是一个列表。该函数用于将源列表中的每个成员映射成新列表中的成员。List.map的类型为('a -> 'b) -> 'a list -> 'b list。如下例:
> let squares x = x * x;;
val squares : int -> int
> List.map squares [1 .. 10];;
val it : int list = [1; 4; 9; 16; 25; 36; 49; 64; 81; 100]
-
List.fold
这个函数非常强大,但是也比较复杂。它用于枚举列表中的所有成员,并对它们进行某种操作。
List中最重要的有两个fold操作,第一个是List.reduce。它的类型为('a -> 'a -> 'a) -> 'a list -> 'a。
List.reduce函数枚举列表中的每个成员,并且产生一个累加值(accumulator value),用于保存对列表进行处理的中间结果。传递的函数('a -> 'a -> 'a)用于将当前的累加值和枚举到的列表成员进行计算,产生新的累加值。当所有成员枚举完成时,给定函数返回的累加值作为整个List.reduce处理的返回值。
下面是一个例子:
Example 2-5. Comma-separating a list of strings using List.reduce
> let insertCommas (acc : string) item = acc + ", " + item;;
val insertCommas : string -> string -> string
> List.reduce insertCommas ["Jack"; "Jill"; "Jim"; "Joe"; "Jane"];;
val it : string = "Jack, Jill, Jim, Joe, Jane"
累加值的初始值是列表的第一个成员,即"Jack",第一次枚举是将它和第二个成员一起传递给函数insertCommas,输出的值作为新的累加值,并且在下一次枚举中使用。整个计算过程如下表:
累加值
|
列表成员
|
"Jack" (第一个列表成员)
|
"Jill" (第二个列表成员)
|
"Jack, Jill"
|
"Jim"
|
"Jack, Jill, Jim"
|
"Joe"
|
"Jack, Jill, Jim, Joe"
|
"Jane"
|
List.reduce函数有一个限制,即累加值的类型必须和列表成员的类型一致。如果想产生不同类型的累加值,可以使用List.fold函数。
fold函数有三个参数:第一个参数是一个函数,用于将累加值和列表成员进行计算,返回新的累加值;第二个是累加值的初始值;第三个是要枚举的列表。最终的返回值是累加值的最终状态。该函数的类型如下:
('acc -> 'b -> 'acc) -> 'acc -> 'b list -> 'acc
下面的示例函数统计一句话中以元音开头的英文单词的情况:
> // Count the number of vowels in a string
let countVowels (str : string) =
let charList = List.ofSeq str
let accFunc (As, Es, Is, Os, Us) letter =
if letter = 'a' then (As + 1, Es, Is, Os, Us)
elif letter = 'e' then (As, Es + 1, Is, Os, Us)
elif letter = 'i' then (As, Es, Is + 1, Os, Us)
elif letter = 'o' then (As, Es, Is, Os + 1, Us)
elif letter = 'u' then (As, Es, Is, Os, Us + 1)
else (As, Es, Is, Os, Us)
List.fold accFunc (0, 0, 0, 0, 0) charList;;
val countVowels : string -> int * int * int * int * int
> countVowels "The quick brown fox jumps over the lazy dog";;
val it : int * int * int * int * int = (1, 3, 1, 4, 2)
-
从右到左的顺序fold处理
List.reduceBack和List.foldBack函数的作用和List.reduce、List.fold函数一样,只是枚举顺序是从右向左。
-
List.iter
iter也是对列表成员进行枚举。它将每个成员都传递给指定的函数。返回值为unit。
该函数的类型为:('a -> unit) -> 'a list -> unit
主要用于不需要返回值,而靠副作用起效的情况。如使用printfn将列表内容打印到控制台。
> // Using List.iter
let printNumber x = printfn "Printing %d" x
List.iter printNumber [1 .. 5];;
val printNumber : int -> unit
Printing 1
Printing 2
Printing 3
Printing 4
Printing 5
-
Option类型
Option类型有两个可能的值:Some('a)和None。它可以用来表示有值和无值两种情况。F#使用专门的Option类型来避免C/C++类语言中使用null来同时表示无值和未初始化变量可能造成的混淆和BUG。
option类型类似于System.Nullable类型。
open System
let isInteger str =
let successful, result = Int32.TryParse(str)
if successful
then Some(result)
else None;;
val isInteger : string -> int option
> isInteger "This is not an int";;
val it : int option = None
> isInteger "400";;
val it : int option = Some 400
可以通过Option.get函数来获得option类型的值,但是对None使用Option.get时会抛出异常。
> // Using Option.get
let isLessThanZero x = (x < 0)
let containsNegativeNumbers intList =
let filteredList = List.filter isLessThanZero intList
if List.length filteredList > 0
then Some(filteredList)
else None;;
val containsNegativeNumbers : int list -> int list option
> let negativeNumbers = containsNegativeNumbers [6; 20; −8; 45; −5];;
val negativeNumbers : int list option = Some [−8; −5]
> Option.get negativeNumbers;;
val it : int list = [−8; −5]
Option模块包含以下几个函数:
函数和类型
|
说明
|
Option.isSome
'a option -> bool
|
如果参数是Some,返回true否则返回false。
|
Option.isNone
'a option -> bool
|
如果参数是Some,返回false,否则返回true。
|
-
打印函数
打印函数主要有三个:printf、printfn和sprintf。
printf将参数打印到控制台窗口中。printfn将参数打印输出并且换行。
打印函数可以使用下面这些格式指示符:
指示符
|
说明
|
示例
|
结果
|
%d, %i
|
打印任意整数
|
printf "%d" 5
|
5
|
%x, %o
|
以16进制或8进制格式打印任意整数
|
printfn "%x" 255
|
ff
|
%s
|
打印字符串
|
printf "%s" "ABC"
|
ABC
|
%f
|
打印浮点数
|
printf "%f" 1.1M
|
1.100000
|
%c
|
打印字符
|
printf "%c" '\097'
|
a
|
%b
|
打印布尔值
|
printf "%b" false
|
false
|
%O
|
打印任意的object
|
printfn "%O" (1,2)
|
(1,2)
|
%A
|
打印任意参数
|
printf "%A" (1,[])
|
(1,[])
|
%O格式指示符会将对象进行装箱操作,并调用Object.ToString函数。%A的运作方式相同,但是在调用Object.ToString之前会检查[<StructuredFormatDisplay>]属性指定的任何特殊打印选项。