【F#2.0系列】F#入门(2)
使用数据结构
> Set.ofList ["b";"a";"b";"b";"c" ];;
val it : Set<string> = seq [ "a"; "b"; "c" ]
> Set.toList (Set.ofList ["abc"; "ABC"]);;
val it : string list = [ "ABC"; "abc" ]
通过上述代码可发现:
· F# Interactive会打印出值的内容例如lists和sets
· 转换会去掉重复的值
· Set中的元素是排过序的
· 默认的排序规则是大小写敏感的
Set是哪里来的
Set定义在Microsoft.FSharp.Collections.Set中。在Microsoft.FSharp命名空间中Core, Collections, Text, 和Control下的类型可直接使用,例如Set,List, Option 及 Array。
使用属性和点表达式(Dot-Notation)
使用属性的例子:
let numWords = words.Length
let numDups = words.Length - wordSet.Count
F#在编译时解析属性名。其通过编译时(compile-time)的点左边的表达式的类型信息(例如words和wordSet)来找出相应的属性(例如Length和Count)。但有些情况下,我们必须显式的定义我们需要的类型,来解决可能的冲突。例如:
let length (inp : 'T list) = inp.Length
在上述代码中,我们将inp标识为list<’T>,这使得F#的类型系统可以明确的知道Length代表了一个list中的值,从而正确的推导其类型。
通过使用点表达式(Dot-Notation),例如words.Length,F#便同时支持了函数式语言与面向对象语言的特性。
有些时候,使用明确命名的函数(functions)来表示成员(member)。例如:
let numWords = List.length words
let numDups = List.length words - Set.count wordSet
上述代码的运行逻辑与本节一开始的程序相同。
如果代码没有提供足够的信息,将导致类型推导失败:
> let length inp = inp.Length;;
let length inp = inp.Length;;
----------------^^^^
stdin(1,17): error: Lookup on object of indeterminate type. A type annotation may
be needed prior to this program point to constrain the type of the object. This
may allow the lookup to be resolved.
使用元组(Tuple)
wordCount函数的结尾使用元组返回了numWords和numDups:
let numWords = words.Length
let numDups = words.Length - wordSet.Count
(numWords,numDups)
元组是F#中最简单,也是最有用的数据结构。一个元组表达式是通过一组表达式来构成一个新的表达式:
let site1 = ("www.cnn.com",10)
let site2 = ("news.bbc.com",5)
let site3 = ("www.msnbc.com",4)
let sites = (site1,site2,site3)
那如何将元组中的值取出呢?对于只含有两个部分的元组,我们可以使用fst和snd函数来取出其first和second两部分:
> fst site1
val it : string = "www.cnn.com"
> let relevance = snd site1
val relevance : int
> relevance;;
val it : int = 10
更通用的,我们可以使用匹配(pattern)来分解元组:
let url,relevance = site1
let site1,site2,site3 = sites
然后,我们就可以直接使用url,relevence等值来访问元组的特定部分。
当你定义的匹配(pattern)与元组定义不匹配时,你会得到一个error:
> let a,b = (1,2,3);;
error: this pattern matches values of type 'int * int' but is here used
with values of type 'int * int * int'. The tuples have different lengths.
元组通常被函数用来返回多个值,也经常被用来表示函数的多个参数。通常一个函数的元组返回值会变成另一个接受元组作为输入值的函数的参数传入。
我们可以使用元组改写showResults函数:
let showResults (numWords,numDups) =
printfn "--> %d words in the text" numWords
printfn "--> %d duplicate words" numDups
let showWordCount text = showResults (wordCount text)
值(Values)与对象
在F#的世界中,所有的东西(everything)都是一个值(value)。在其他的语言中,所有的东西都是object。在实践中,你可以认为这两者在概念上是等同的,尽管F#程序员倾向于认为对象是以下这些特别种类的值(value):
· 那些会在程序执行时改变的外部可访问的属性。一般是显式声明为mutable的属性。
· 那些拥有一个标识符的数据或状态。例如一个独特(unique)的数字标识(stamp)或者那些可能拥有不同标识却指向相同值(value)的.NET基础(underlying)对象标识。
· 那些可以通过声明(casts),转换(conversions)或接口转换(query)出附加的功能的值(value)。
F#支持objects,但不是所有的值都是objects。
使用命令式(imperative)的代码
在前几节定义的showWordCount和showResults函数使用printfn来输出结果:
printfn "--> %d words in the text" numWords
printfn "--> %d duplicate words" numDups
简单的说,就是调用了printfn这个函数。与OCaml,C和C++相同,%d是一个数字型占位符。F#的printf是一个类型安全的字符格式化器 (text formatter)。F#的编译器会检查传入参数是否符合占位符的类型要求。当然,你也可以使用System.Console.WriteLine("--> {0} words in the text", box(numWords)),尽管在运行之前没有任何类型检查。
有时候希望可以把相关的代码写在同一行上,我们可以使用分号(;)来实现这一需求。
let two = (printfn "Hello World"; 1+1)
let four = two + two
当代码执行的时候,Hello World会被输出一次。而four的执行就没那么直观了,printfn会触发F#的边际效果(side effect),结果会被忽略(discard),我试验发现,分号左边任何返回值不是unint的表达式都会引warning:stdin(8,12): warning FS0020: 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. 但程序还是可以继续执行。例如:
> let two = (2+2;
1+1)
let four = two + two
;;
let two = (2+2; 1+1)
-----------^^^
stdin(8,12): warning
FS0020: 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.
val two : int = 2
val
four : int = 4
> let two = (printfn
"Hello World";2+2; 1+1)
let four = two + two
;;
let two = (printfn "Hello
World";2+2; 1+1)
---------------------------------^^^
stdin(5,34): warning
FS0020: 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.
Hello World
val two : int = 2
val four : int = 4
依据试验结果,基本可以确定这个边际效果(side effect)的意思就是整个表达式只取最右边的表达式的值,但其他表达式也会按顺序执行,忽略返回值,但会产生warning。