F#基本数据类型:
数字:
和C#一样,F#的数字类型和.net中的类型是一一对应的,只不过为了方便使用,又对其定义了一套别名而已,可以通过后缀区分其数据类型。
类型 | 后缀 | .NET 类型 |
byte | uy | System.Byte |
sbyte | y | System.SByte |
int16 | s | System.Int16 |
uint16 | us | System.UInt16 |
int,int32 | System.Int32 | |
uint, uint32 | u | System.UInt32 |
int64 | L | System.Int64 |
uint64 | UL | System.UInt64 |
float | System.Double | |
float32 | f | System.Float |
decimal | M | System.Decimal |
另外,也可以通过前缀0x、0o及0b来标识十六进制、八进制和二进制数据。
> let hex = 0xFCAF;;
val hex : int = 64687
> let oct = 0o7771L;;
val oct : int64 = 4089L
> let bin = 0b00101010y;;
val bin : sbyte = 42y
> (hex, oct, bin);;
val it : int * int64 * sbyte = (64687, 4089, 42)
运算:
数学运算是函数式编程的强项,F#提供了非常丰富的运算符
- 基本运算: +、-、*、/、%、**
- 位运算: &&&、|||、^^^、<<<、>>>
- 布尔运算: &&、||、not
- 比较: <、<=、>、>=、=、<>、compare
- 数学函数: abs ceil exp floor sign log log10 cos sin tan pown
字符和字符串:
F#中的字符和字符串和C#类似,就不多做介绍了。
Unit
Unit类型主要用于函数的返回值,用来表示函数不需要返回任何数据,类似于C#中的void。Unit类型可以用()来表示:
> let x = ();;
val x : unit
> ();;
val it : unit = ()
如同C语言里不使用返回值不是void类型的函数的返回值会得到一条pclint告警一样,使用了返回值不是unit的函数也会得到一条告警:
> let square x = x * x;;
val square : int -> int
> square 4;;
val it : int = 16
Warning 1 This expression should have type 'unit', but has type 'int'. Use 'ignore' to discard the result of the expression, or 'let' to bind the result to a name.
为了消除这个告警,我们可以使用系统内置的ignore函数。ignore函数返回一个unit类型,其功能如下:
let ignore x = ()
因此使用了ignore函数后,就改变了返回值为unit,自然就消除了这个告警。
> ignore (square 4);;
val it : unit = ()
但是,这样写不是很优雅,我们常常通过管道符函数‘|>’来写成如下形式
> square 4 |> ignore;;
这种方式让我想起来了在unix的shell重定向时常用的“… > null 0”结构。
Tuples
Tuples两个或多个值的集合,基本形式如下:
> let dinner = ("green eggs", "ham");;
val dinner : string * string = ("green eggs", "ham")
> let zeros = (0, 0L, 0I, 0.0);;
val zeros : int * int64 * bigint * float = (0, 0L, 0I, 0.0)
其中,小括号是可选的,如下形式也合法,但不推荐。
> let dinner = "green eggs", "ham";;
获取Tuples中的元素值通常有如下两种方式:
1. 通过系统内置的fst和snd函数
> let a = fst (1, 2);;
val a : int = 1
> let b = snd (1, 2);;
val b : int = 2
2. 通过let绑定
> let (a, b) = (1, 2);;
val b : int = 2
val a : int = 1
可能有细心的朋友已经注意到了,系统只提供fst和snd函数,如果要对Tuples的第三个或以上元素该如何做呢?当然还是可以通过这两种方法啦:
1. 自定义third函数,然后通过函数获取
> let third (a, b, c) = c;; //由于这里不关心前两个元素,这个函数也可以写成let third (_, _, c) = c;;
val third : 'a * 'b * 'c -> 'c
> third (1, 2, 3);;
val it : int = 3
2. 通过let绑定的方式
> let (_, _, c) = (1, 2, 3);;
val c : int = 3
这两种方式都是异曲同工的,我个人更喜欢let绑定的方式,用起来更加简洁优雅。
Lists
Lists是F#中内置的链表,基本形式如下:
> [];; //empty list
val it : 'a list = []
> [1; 2; 3];;
val it : int list = [1; 2; 3]
注意:List中元素是通过 ‘;’ 来分隔的,不要像C#那样用 ‘,’ 来分隔,一旦用了 ‘,’ ,得到的将是一个包含一个Tuples元素的List。
> [1, 2, 3];;
val it : (int * int * int) list = [(1, 2, 3)]
另外,我们也可以通过推导的方式,写出步长和范围,让系统自动推导List中的其它元素。
> [1 .. 10];;
val it : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
> [1 .. 10 .. 50];;
val it : int list = [1; 11; 21; 31; 41]
用 :: 来连接List的头和尾
一个List有一个“头”(第一个元素)和一个“尾”(剩下的元素)。头是一个元素,而尾则是一个列表。例如,对于[1; 2; 3]来说,表头是整数1,而表尾是list[2; 3]
在F#中,可以通过::运算符来连接头和尾,下面几种的列表写法是完全等价的:
[1; 2; 3]
1 :: [2; 3]
1 :: 2 :: [3]
1 :: 2 :: 3 :: []
这个运算符主要用于模式匹配,在讲模式匹配的时候再介绍其用途。
用 @ 来连接两个List
类似于字符串可以用 + 运算符来连接一样,系统也内置了一个 @ 运算符来连接两个List,生成一个新的List。
> // Using the append operator
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]
用yield来返回一个List
当函数返回值是一个List的时候,用上面的几种方式来构造List往往不是很方便,特别是在不确定元素个数的时候。因此,F#提供了一个关键字yield来方便我们构造List的返回值。
> // 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]
使用方式基本上和C#中的yield差不多,简单而方便。
Option
在C#中,当无法获取到预期对象的时候,我们往往用null来标示这个值为空。这样导致我们再使用它的时候往往需要判断对象是否为null,多加判断会导致代码可读性差,漏加判断常常导致程序的一些bug(C/C++会导致更严重的数据访问异常)。
在F#中,通过引进option类型来解决这一问题:option类型的数据的值有两种类型——Some('a) 或None,通过它们可以区分数据是否为空数据。
> // Using option to return a value (or not)
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中的元素值,可以通过Option.get函数来实现,当值为None的时候,Option.get会抛异常。如果要避免这个异常,可以通过Option.isSome或Option.isNone先判断一下。通过模式匹配可以更好的实现这一功能。
let printNumber str =
match (isInteger str) with
| Some(num) -> printfn "Number: %d" num
| None -> printfn "Invalid Number"
实际上,option也无法避免空值的判断,但是F#通过option明确指出了哪些需要判断,哪些不需要判断,这样对我们的代码无疑是非常有帮助的。
其它数据类型:
除了上述这几种基本类型以外,还有几种类型:Record、struct、class,、Discriminated Unions等,这些的使用方法相对复杂点,在后续讲述函数式编程的时候再详细讲。