NetRoc's Blog

N-Tech

 

F#学习笔记之四 — 核心类型

NetRoc

http://www.DbgTech.net

除了基本类型之外,F#类库还包括一些核心类型用来操作数据。

类型标记

类型名

说明

示例

unit

Unit

unit值

()

int, float

具体类型(Concrete type)

具体类型

42, 3.14

'a, 'b

普通类型

普通类型

 

'a -> 'b

函数类型

一个具有返回值的函数

fun x -> x + 1

'a * 'b

元组类型(Tuple type)

值的有序集合

(1, 2), ("eggs", "ham")

'a list

列表类型(List type)

值的列表

[1; 2; 3], [1 .. 3]

'a

可选类型

可选的值

Some(3), None

  1. 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 = ()

  2. 元组类型

    元组是数据的有序集合,使用元组可以方便的将数据分组到一起。比如用来保存运算的中间结果等等。

    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绑定提取的值的个数必须刚好等于元组的成员个数,否则会产生一个编译错误。

    元组可以作为函数参数进行传递。

  3. 列表

    使用元组可以将一组值绑定到单个实体中,使用列表可以将数据以链的形式组织起来。列表的好处是可以使用聚合操作来批量处理数据。

    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来访问它们。

  4. 聚合操作(Aggregate Operators)

    初看之下元组和列表似乎没什么不同,都是一组数据的集合。但是列表有一个非常重要而且非常舒服的特性,就是可以进行聚合操作。类似CPU中的SIMD指令,可以用单个操作符批量操作列表中的所有数据。而在我目前学习过的所有语言中,只有F#具有这样的特性!想象一下,我们可以少写多少循环了!

    1. 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]

    2. 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)

    1. 从右到左的顺序fold处理

      List.reduceBack和List.foldBack函数的作用和List.reduce、List.fold函数一样,只是枚举顺序是从右向左。

    2. 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

  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。

     

  6. 打印函数

    打印函数主要有三个: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>]属性指定的任何特殊打印选项。

sprintf用于输出的目标为一个字符串的情况。

posted on 2010-05-24 12:45 NetRoc 阅读(926) 评论(0)  编辑 收藏 引用

只有注册用户登录后才能发表评论。

导航

统计

常用链接

留言簿(7)

随笔档案(99)

文章分类(35)

文章档案(32)

Friends

Mirror

搜索

最新评论

阅读排行榜

评论排行榜