不可变性(Immutability)
也许已经注意到,我一直使用“值(value)”来表示一个标识符(identifier),而不是“变量(variable)”。这是由于默认情况下,F#中的类型是不可变的(immutable),也就是说,一经创建即不可修改。看起来这是一个很大的限制,但是不可变性可以避免某种类型的bug。另外,不可变的数据天然地具备线程安全的特性,这意味着您无需在处理并行情况时担心同步锁的发生。
如果您确实需要修改数据,可使用F#的mutable关键字,它会创建一个变量(而不是值)。我们可以通过左箭头操作符(<-)来修改变量的值。
val mutable x : string
> printfn "x's value is '%s'" x;;
x's value is 'the original value.'
val it : unit = ()
> x <- "the new one.";;
val it : unit = ()
> printfn "x's value is now '%s'" x;;
x's value is now 'the new one.'
val it : unit = ()
引用值(Reference values,Microsoft.FSharp.Core.Ref<_>)
引用值是另一种表示可修改数据的方式。但它不是将变量存储在堆栈(stack),引用值其实是一个指向存储在堆(heap)上的变量的指针(pointer)。在F#中使用可修改的值时会有些限制(比如不可以在内部lambda表达式中使用)。而ref对象则可被安全地传递,因为它们是不可变的record值(只是它有一个可修改的字段)。
使用引用值时,用“:=”赋一个新值,使用“!”进行解引用。
val refCell : int ref
> refCell := -1;;
val it : unit = ()
> !refCell;;
val it : int = –1
模块(Modules)
在C#中所有一切都要属于相应的类。尽管在F#中,我们仍然可以用熟悉的方式声明标准的.NET类,但它也有模块的概念,模块是值、函数和类型的集合(可以对比一下命名空间,后者只能包含类型)。
这也是我们能够访问“List.map”的原因。在F#库(FSharp.Core.dll)中,有一个名为“List”的模块,它包含了函数“map”。
在快速开发的过程中,如果不需要花费时间去设计严格的面向对象类型体系,就可以采用模块来封装代码。要声明自己的模块,要使用module关键字。在下面的例子中,我们将为模块添加一个可修改的变量,该变量也是一个全局变量。
let version = "1.0.0.0"
let debugMode = ref false
module MyProgram =
do printfn "Version %s" ProgramSettings.version
open ProgramSettings
debugMode := true
元组(Tuples)
元组(tuple,发音为‘two-pull’)表示值的有序集合,而这些值可看作一个整体。按传统的方式,如果您要传递一组相关的值,需要创建结构(struct)或类(class),或者还需要“out”参数。使用元组我们可以将相关的值组织起来,同时并不需要引入新的类型。
要定义一个元组,只要将一组值用逗号分隔,并用圆括号把它们括起来即可。
val tuple : int * bool * string
> let getNumberInfo (x : int) = (x, x.ToString(), x * x);;
val getNumberInfo : int -> int * string * int
> getNumberInfo 42;;
val it : int * string * int = (42, "42", 1764)
函数甚至可以接受元组为参数:
val printBlogInfo : string * string * string -> unit
> let myBlog = ("Chris", "Completely Unique View", "http://blogs.msdn.com/chrsmith");;
val myBlog : string * string * string
> printBlogInfo myBlog;;
Chris's blog [Completely Unique View] is online at 'http://blogs.msdn.com/chrsmith'
val it : unit = ()
函数柯里化(Function Currying)
F#提供的一个新奇的特性是可以只接受参数的一个子集,而接受部分参数的结果则是一个新的函数。这就是所谓的“函数柯里化”。比如,假设有一个函数接受3个整数,返回它们的和。我们可以只传入第一个参数,假设值为10,这样我们就可以说将原来的函数柯里化了,而它会返回一个新的函数——新函数接受两个整数,返回它们与10的和。
val addThree : int -> int -> int -> int
> let addTwo x y = addThree 10 x y;;
val addTwo : int -> int -> int
> addTwo 1 1;;
val it : int = 12