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等,这些的使用方法相对复杂点,在后续讲述函数式编程的时候再详细讲。