|
函数柯里化(Function Currying) F#提供的一个新奇的特性是可以只接受参数的一个子集,而接受部分参数的结果则是一个新的函数。这就是所谓的“函数柯里化”。比如,假设有一个函数接受3个整数,返回它们的和。我们可以只传入第一个参数,假设值为10,这样我们就可以说将原来的函数柯里化了,而它会返回一个新的函数——新函数接受两个整数,返回它们与10的和。 > let addThree x y z = x + y + z;; 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 Union类型(Union Types,Discriminated Unions) 考虑下面的枚举值: enum CardSuit { Spade = 1, Club = 2, Heart = 3, Diamond = 4}; 理论上,一个card实例只有一种可能的取值,但由于enum本质上只是整数,您不能确定它的值是否是有效的,在C#中,你可以这么写: CardSuit invalid1 = (CardSuit) 9000; CardSuit invalid2 = CardSuit.Club | CardSuit.Diamond; 另外,考虑下面的情形。如果您需要扩展一个enum: enum Title { Mr, Mrs } Title枚举可以工作地很好,但一段时间后,如果需要添加一个“Ms”值,那么每一个switch语句都面临一个潜在的bug。当然您可以尝试修复所有的代码,却难免会发生遗漏。 枚举可以很好地表达某些概念,但是却无法提供足够的编译器检查。F#中的Union类型可设定为一组有限的值:数据标签(da type MicrosoftEmployee = | BillGates | SteveBalmer | Worker of string | Lead of string * MicrosoftEmployee list 如果有一个MicrosoftEmployee类型的实例,您就知道它必定是{BillGates,SteveBalmer,Worker,Lead}之一。另外,如果它是Worker,您可以知道有一个字符串与之关联,也许是他的名字。我们可以轻松地创建Union类型,而后使用模式匹配(下一小节)来匹配它们的值。 let myBoss = Lead("Yasir", [Worker("Chris"); Worker("Matteo"); Worker("Santosh")]) let printGreeting (emp : MicrosoftEmployee) = match emp with | BillGates -> printfn "Hello, Bill" | SteveBalmer -> printfn "Hello, Steve" | Worker(name) | Lead(name, _) -> printfn "Hello, %s" name 现在假设需要扩展Union类型: type MicrosoftEmployee = | BillGates | SteveBalmer | Worker of string | Lead of string * MicrosoftEmployee list | ChrisSmith 我们会看到一些编译器警告信息:
![]() 图1 警告信息 编译器检测到您没有匹配Union的每一个数据标签,发出了警告。像这样的检查会避免很多bug,要了解 模式匹配(Pattern Matching) 模式匹配看起来像是增强版的switch语句,允许您完成分支型控制流程。除了跟常数值进行比较外,还可以捕获新的值。比如在前面的例子中,我们在匹配Union数据标签时绑定了标识符“name”。 let printGreeting (emp : MicrosoftEmployee) = match emp with | BillGates -> printfn "Hello, Bill" | SteveBalmer -> printfn "Hello, Steve" | Worker(name) | Lead(name, _) -> printfn "Hello, %s" name 还可以对数据的“结构”进行匹配,比如对列表(list)进行匹配。(还记得吗,x :: y表示x为列表的一个元素,y是x之后的元素,而[]则是空列表。) let listLength aList = match aList with | [] -> 0 | a :: [] -> 1 | a :: b :: [] -> 2 | a :: b :: c :: [] -> 3 | _ -> failwith "List is too big!" 在这个匹配的最后,我们使用了通配符“_”(下划线),它匹配任意值。如果aList变量包含多于三个的元素,最后的模式子句将执行,并抛出一个异常。模式匹配还可以我们执行任意表达式来确定模式是否匹配(如果表达式的值为false,则不匹配)。 let isOdd x = match x with | _ when x % 2 = 0 -> false | _ when x % 2 = 1 -> true 我们甚至可以使用动态类型测试进行匹配: let getType (x : obj) = match x with | :? string -> "x is a string" | :? int -> "x is a int" | :? System.Exception -> "x is an exception" | :? _ -> "invalid type" 记录类型(Records) 在声明包含若干个公有属性的类型时,记录类型是一种轻量级的方式。它的一个优势是,借助于类型推演系统,编译器可以通过值的声明得出适当的记录类型。 type Address = {Name : string; Address : string; Zip : int} let whiteHouse = {Name = "The White House"; Address = "1600 Pennsylvania Avenue"; Zip = 20500} 在上面的例子中,首先定义了“Address”类型,那么在声明它的实例时,无须显式地使用类型注解,编译器可根据字段(属性)的名称自行得出类型的信息。所以whiteHouse的类型为Address。 Forward Pipe Operator(|>) |>操作符只是简单地定义为: let (|>) x f = f x 其类型前面信息为: 'a -> ('a -> 'b) -> 'b 可以这么来理解:x的类型为'a,函数f接受'a类型的参数,返回类型为'b,操作符的结果就是将x传递给f后所求得的值。 还是来看个例子吧: // Take a number, square it, then convert it to a string, then reverse that string let square x = x * x let toStr (x : int) = x.ToString() let rev (x : string) = new String(Array.rev (x.ToCharArray())) // 32 -> 1024 -> "1024" -> "4201" let result = rev (toStr (square 32)) 上面的代码是很直白的,但语法看起来却不太好。我们所做的就是将一个运算的结果传给下一个运算。我们可以通过引入几个变量来改写代码为: let step1 = square 32 let step2 = toStr step1 let step3 = rev step2 let result = step3 但是我们需要维护这几个临时变量。|>操作符接受一个值,将其“转交”给一个函数。这会大大地简化F#代码: let result = 32 |> square |> toStr |> rev 序列(Sequence,System.Collections.Generic.IEnumerator<_>) 序列(在F#中为seq)是 System.Collections.Generic.IEnumerator的别名,但它在F#中有另外的作用。不像列表和数组,序列可包含无穷个值。只有当前的值保存在内存中,一旦序列计算了下个值,当前的值就会被忘记(丢弃)。例如,下面的代码生成了一个包含所有整数的序列。 let allIntegers = Seq.init_infinite (fun i -> i) 集合(Collections:Seq,List,Array) 在F#中,如果您想表示一个值的集合,至少有三个好的选择——数组、列表和序列,它们都有各自的优点。而且每种类型都有一系列的模块内置于F#库中。您可以使用VS的智能感知来探究这些方法,这里我们来看看最常用的那些: iter。“iter”函数遍历集合的每一项。这与“foreach”循环是一致的。下面的代码打印列表的每一项: List.iter (fun i -> printfn "Has element %d" i) [1 .. 10] map。像我在上篇文章中所说的,map函数基于一个指定的函数对集合的值进行转换。下面的例子将数组的整数值转换为它们的字符串表示: Array.map (fun (i : int) -> i.ToString()) [| 1 .. 10 |] fold。“fold”函数接受一个集合,并将集合的值折叠为单个的值。像iter和map一样,它接受一个函数,将其应用于集合的每个元素,但它还接受另一个“accumulator”参数。fold函数基于上一次运算不断地累积accumulator参数的值。看下面的例子: Seq.fold (fun acc i -> i + acc) 10 { 1 .. 10 } 该代码的功能是:以10为基数(acculator),累加序列中的每一项。 只有序列有fold方法,列表和数组则有fold_left和fold_right方法。它们的不同之处在于计算顺序的不同。 可选值(Option Values) 基于函数式编程的特点,在F#中很难见到null值。但有些情况下,null值比未初始化变量更有意义。有时可选值则表示值未提供(可选值就像C#中的nullable类型)。 F#中的“可选类型(option type)”有两种状态:“Some”和“None”。在下面的记录类型Person中,中间的字段可能有值,也可能没有值。 type Person = { First : string; MI : string option; Last : string } let billg = {First = "Bill"; MI = Some("H"); Last = "Gates" } let chrsmith = {First = "Chris"; MI = None; Last = "Smith" } 延迟求值(惰性值,Lazy Values,Microsoft.FSharp.Core.Lazy<_>) 延迟初始化表示一些值,它们在需要时才进行计算。F#拥有延迟求值特性。看下面的例子,“x”是一个整数,当对其进行求值时会打印“Computed”。 > let x = lazy (printfn "Computed."; 42);; val x : Lazy<int> > let listOfX = [x; x; x];; val listOfX : Lazy<int> list > x.Force();; Computed. val it : int = 42 可以看到,我们在调用“Force”方法时,对x进行求值,返回的值是42。您可以使用延迟初始化来避免不必要的计算。另外在构造递归值时,也很有用。例如,考虑一个Union值,它用来表示循环列表: type InfiniteList = | ListNode of int * InfiniteList let rec circularList = ListNode(1, circularList) “circularList”拥有对自身的引用(表示一个无限循环)。不使用延迟初始化的话,声明这样类型的值是不可能的。 现在,应该对F#语言有了一定的足够的了解。在后续文章中,回陆续介绍一些F#的使用技巧。
|






骆驼户外男 真皮磨砂日常休闲鞋 低帮 2011秋冬新款 专柜正品特价